上一篇博客中我已經講解了傳統標籤,想要開發自定義標籤,大多數狀況下都要重寫doStartTag(),doAfterBody()和doEndTag()方法,而且還要知道SKIP_BODY,EVAL_BODY等等的變量表明着什麼,在什麼方法中使用。這樣實在是太麻煩了!html
所以,爲了簡化標籤開發的複雜度,在JSP 2.0中定義了一個更爲簡單、便於編寫和調用的SimpleTag接口來實現標籤的功能。。java
通常來講,實現了SimpeTag接口的標籤稱之爲簡單標籤數組
public interface SimpleTag extends JspTag {
void doTag() throws JspException, IOException;
void setParent(JspTag var1);
JspTag getParent();
void setJspContext(JspContext var1);
void setJspBody(JspFragment var1);
}
複製代碼
void doTag() throws JspException, IOException;
void setJspContext(JspContext var1);
void setJspBody(JspFragment var1);
複製代碼
通常地,咱們作開發都是繼承SimpleTagSupport類(該類實現了SimpleTag)來編寫自定義標籤瀏覽器
下面咱們就來個快速入門吧:微信
目標:傳入字符串格式就能夠顯示想要的格式日期,對比以前傳統標籤的,看有什麼不一樣之處app
標籤處理器類:less
public class Demo1 extends SimpleTagSupport {
String format = null;
@Override
public void doTag() throws JspException, IOException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
this.getJspContext().getOut().write(simpleDateFormat.format(new Date()));
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
}
複製代碼
<tag>
<name>formatDate</name>
<tag-class>tag.Demo1</tag-class>
<body-content>tagdependent</body-content>
<attribute>
<name>format</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
複製代碼
在咱們講解傳統標籤的時候,配合着SKIP_BODY、SKIP_PAGE等變量能夠實現以下的功能:jsp
簡單標籤可沒有這些變量呀,那它怎麼才能實現上面那些功能呢?ide
在doTag方法中能夠拋出javax.servlet.jsp.SkipPageException異常,用於通知WEB容器再也不執行JSP頁面中位於結束標記後面的內容,這等效於在傳統標籤的doEndTag方法中返回Tag.SKIP_PAGE常量的狀況,咱們來測試一下,在上面例子的代碼中添加:函數
throw new SkipPageException();
複製代碼
SimpleTagSupport也能夠帶標籤體,可是處理方法和傳統標籤徹底不一樣。
咱們來看一下JspFragment對象的源碼吧:
public abstract class JspFragment {
public JspFragment() {
}
public abstract void invoke(Writer var1) throws JspException, IOException;
public abstract JspContext getJspContext();
}
複製代碼
JspFragment對象十分簡單,重要的只有invoke(Writer var1)方法(獲取JspContext對象並不重要,在標籤描述器上就能夠獲取到了)
public abstract void invoke(java.io.Writer out) :
public class Demo1 extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
//獲得表明標籤體的對象
JspFragment jspFragment = getJspBody();
//invoke方法接收的是一個Writer,若是爲null,就表明着JspWriter(),將標籤體的數據寫給瀏覽器!
jspFragment.invoke(null);
}
}
複製代碼
既然標籤體的內容是經過JspFragment對象的invoke()方法寫給瀏覽器的,那麼那麼那麼,我只要控制好invoke()方法,我想幹什麼就幹什麼!
也就是說:
來來來,咱們來試驗一下:
public void doTag() throws JspException, IOException {
//獲得表明標籤體的對象
JspFragment jspFragment = getJspBody();
//jspFragment.invoke(null);
}
複製代碼
標籤體的內容沒有輸出
調用兩次invoke()方法
public void doTag() throws JspException, IOException {
//獲得表明標籤體的對象
JspFragment jspFragment = getJspBody();
jspFragment.invoke(null);
jspFragment.invoke(null);
}
複製代碼
//獲得表明標籤體的對象
JspFragment jspFragment = getJspBody();
//建立能夠存儲字符串的Writer對象
StringWriter stringWriter = new StringWriter();
//invoke()方法把標籤體的數據都寫給流對象中
jspFragment.invoke(stringWriter);
//把流對象的數據取出來,流對象的數據就是標籤體的內容
String value = stringWriter.toString();
//將數據改爲是大寫的,寫到瀏覽器中
getJspContext().getOut().write(value.toUpperCase());
複製代碼
咱們能夠發現,傳統標籤能完成的功能,簡單標籤均可以完成,而且更爲簡單!
既然咱們學了簡單標籤,咱們就用簡單標籤來作開發吧!
在講解request對象的時候,咱們講解過怎麼實現防盜鏈的功能。如今咱們使用標籤來進行防盜鏈!
模擬下場景:1.jsp頁面是海賊王資源,2.jsp頁面提示非法盜鏈,index1.jsp是個人首頁。別人想要看個人海賊王資源,就必須經過個人首頁點進去看,不然就是非法盜鏈!
@Override
public void doTag() throws JspException, IOException {
//若是想要作成更加靈活的,就把站點設置和資源設置成標籤屬性傳遞進來!
//等會咱們要獲取獲得request對象,須要使用到JspContext的子類PageContext
PageContext pageContext = (PageContext) this.getJspContext();
//獲取request對象
HttpServletRequest httpServletRequest = (HttpServletRequest) pageContext.getRequest();
//獲取到referer
String referer = httpServletRequest.getHeader("Referer");
//獲取到response對象,等會若是是非法盜鏈,就重定向別的頁面上
HttpServletResponse httpServletResponse = (HttpServletResponse) pageContext.getResponse();
//非法盜鏈!
if (referer == null || !referer.startsWith("http://localhost:8080/zhongfucheng")) {
//2.jsp提示了非法盜鏈!
httpServletResponse.sendRedirect("/zhongfucheng/2.jsp");
//不執行頁面下面的內容了,保護頁面
throw new SkipPageException();
}
}
複製代碼
<zhongfucheng:Referer/>
<html>
<head>
<title></title>
</head>
<body>
海賊王最新資源
</body>
</html>
複製代碼
<h1>這是首頁!</h1>
<a href="${pageContext.request.contextPath}/1.jsp"> 海賊王最新資源</a>
複製代碼
<body>
你是非法盜鏈的!!!!!!
</body>
複製代碼
在JSTL中,咱們已經使用過了<c:if/>
標籤了,如今咱們學習了自定義標籤,能夠開發相似於JSTL的if標籤了!
既然是if標籤,那麼就須要編寫帶屬性和帶標籤體的標籤(須要判斷是true仍是false呀!,經過判斷是否爲真值來決定是否執行標籤體的內容)
public class Demo1 extends SimpleTagSupport {
//定義一個Boolean類型的變量
boolean test ;
@Override
public void doTag() throws JspException, IOException {
//獲取到表明標籤體內容的對象
JspFragment jspFragment = this.getJspBody();
//若是爲真值才執行標籤體的內容
if (test == true) {
jspFragment.invoke(null);
}
}
public boolean isTest() {
return test;
}
public void setTest(boolean test) {
this.test = test;
}
}
複製代碼
<tag>
<name>if</name>
<tag-class> tag.Demo1</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>test</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
複製代碼
forEach標籤最基本的功能:遍歷集合、數組
public class Demo2 extends SimpleTagSupport {
//遍歷的是List集合,因而標籤的屬性就爲List
private List items;
//遍歷出來的對象就用Object存着,由於咱們不知道List集合保存的是什麼元素
private Object var;
@Override
public void doTag() throws JspException, IOException {
//獲取到迭代器
Iterator iterator = items.iterator();
//遍歷集合
while (iterator.hasNext()) {
//獲取到集合的元素
var = iterator.next();
//.....var屬性表明的就是集合的元素,如今問題來了,好像在標籤體內沒法獲取到這個對象....
//作到這裏完成不下去了....
}
}
public void setItems(List items) {
this.items = items;
}
public void setVar(Object var) {
this.var = var;
}
}
複製代碼
上面的思路是正常的,可是作不下去!咱們換一個思路唄。上面的問題主要是在標籤體獲取不到被遍歷出來的對象!
咱們這樣作:把var定義成String類型的,若是遍歷獲得對象了,就設置PageContext的屬性,var爲關鍵字,對象爲值。在標籤體用EL表達式搜索以var爲關鍵字的對象!每遍歷出一個對象,就執行一次標籤體!
public class Demo1 extends SimpleTagSupport {
//遍歷的是List集合,定義List集合成員變量
private List items;
//以var爲關鍵字存儲到PageContext
private String var;
@Override
public void doTag() throws JspException, IOException {
//獲取到集合的迭代器
Iterator iterator = items.iterator();
//獲取到表明標籤體內容的對象
JspFragment jspFragment = this.getJspBody();
//遍歷集合
while (iterator.hasNext()) {
Object o = iterator.next();
//把遍歷出來的對象存儲到page範圍中,關鍵字爲標籤的屬性var(在標籤體中使用EL表達式${var},就可以獲取到集合的對象了!)
this.getJspContext().setAttribute(var, o);
//每設置了一個屬性,我就執行標籤體
jspFragment.invoke(null);
}
}
public void setItems(List items) {
this.items = items;
}
public void setVar(String var) {
this.var = var;
}
}
複製代碼
<tag>
<name>forEach</name>
<tag-class>tag.Demo1</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>var</name>
<rtexprvalue>true</rtexprvalue>
<!--字符串便可,不須要EL表達式的支持-->
<required>false</required>
</attribute>
<attribute>
<name>items</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
複製代碼
<%
List list = new ArrayList();
list.add("zhongfucneng");
list.add("1");
list.add("2");
list.add("3");
request.setAttribute("list",list);
%>
<zhongfucheng:forEach items="${list}" var="str">
${str}
</zhongfucheng:forEach>
複製代碼
上面寫的僅僅可以遍歷List集合,作一個通用的forEach標籤麻煩的是在:不知道傳進來的是什麼類型的數組、什麼類型集合!,須要逐一去判斷
咱們的實現思路就是將全部的集合或數組都轉成是Collection類型的!
咱們來嘗試着寫一下:
//若是items是Collection類型的,就強轉爲Colletion
if (items instanceof Collection) {
collection = (Collection) items;
}
//若是itmes是Map類型的,那麼就強轉爲Map,再獲取到<Map.Entry<K, V>,這個是Set集合的!
if (items instanceof Map) {
Map map = (Map) items;
collection = (Collection) map.entrySet();
}
//對象數組
if (items instanceof Object[]) {
Object[] objects = (Object[]) items;
collection = Arrays.asList(objects);
}
//int[],Byte[],char[]等八大基本數據類型.....
複製代碼
還有int[],byte[],char[]等八大基本數據類型,這八大基本數據類型就不能用Arrays.asList()把引用傳進去了。由於JDK5之後會把引用自動裝箱成Interger[]、Byte[]等等,而不是獲取到數組的元素數據。
public static void main(String[] args) {
int[] ints = new int[]{1, 2, 3};
Object[] objects = new Object[]{"1", "2", "3"};
if (objects instanceof Object[]) {
Collection collection = Arrays.asList(objects);
System.out.println(collection);
}
if (ints instanceof int[]) {
Collection collection1 = Arrays.asList(ints);
System.out.println(collection1);
}
}
複製代碼
if (items instanceof int[]) {
int[] ints = (int[]) items;
collection = new ArrayList();
for (int anInt : ints) {
collection.add(anInt);
}
}
//......這裏還要寫7個
複製代碼
因爲JDK5的新特性,咱們又有另外的解決方案,Class對象可以判斷是否爲數組類,reflect反射包下Array類:
其實,不管Map集合、仍是任何類型的數組、均可以使用Colletion進行遍歷!。
若是是八大基本數據類型的數組,咱們就使用反射來進行構建出Collection集合。
標籤處理器的代碼:
public class Demo1 extends SimpleTagSupport {
//遍歷的是未知的集合或數組,定義成Object
private Object items;
//每次被遍歷的對象存儲關鍵字
private String var;
//Colletion
private Collection collection;
//在WEB容器設置標籤的屬性的時候,判斷是什麼類型的數組和集合
public void setItems(Object items) {
this.items = items;
//若是items是Collection類型的,就強轉爲Colletion
if (items instanceof Collection) {
collection = (Collection) items;
}
//若是itmes是Map類型的,那麼就強轉爲Map,再獲取到<Map.Entry<K, V>,這個是Set集合的!
if (items instanceof Map) {
Map map = (Map) items;
collection = (Collection) map.entrySet();
}
//能夠這樣解決,Class對象判斷是不是一個數組類
if (items.getClass().isArray()) {
//建立Collection集合添加數組的元素!
collection = new ArrayList();
//再利用reflect包下的Array類獲取到該數組類的長度
int len = Array.getLength(items);
//遍歷並添加到集合中
for (int i = 0; i < len; i++) {
collection.add(Array.get(items, i));
}
}
}
public void setVar(String var) {
this.var = var;
}
@Override
public void doTag() throws JspException, IOException {
//獲取到表明標籤體內容的對象
JspFragment jspFragment = this.getJspBody();
Iterator iterator = collection.iterator();
//遍歷集合
while (iterator.hasNext()) {
Object o = iterator.next();
//把遍歷出來的對象存儲到page範圍中(在標籤體中使用EL表達式${var},就可以獲取到集合的對象了!)
this.getJspContext().setAttribute(var, o);
jspFragment.invoke(null);
}
}
}
複製代碼
<%
/*list集合*/
List list = new ArrayList();
list.add("zhongfucneng");
list.add("1");
list.add("2");
list.add("3");
request.setAttribute("list",list);
/*基本數據類型數組*/
int[] ints = new int[]{1, 2, 3, 4, 5};
request.setAttribute("ints", ints);
/*對象數組*/
Object[] objects = new Object[]{2, 3, 4, 5, 6};
request.setAttribute("objects", objects);
/*map集合*/
Map map = new HashMap();
map.put("aa", "aa");
map.put("bb", "bb");
map.put("cc", "cc");
request.setAttribute("map",map);
%>
List集合:
<zhongfucheng:forEach items="${list}" var="str">
${str}
</zhongfucheng:forEach>
<hr>
<hr>
基本數據類型數組:
<zhongfucheng:forEach items="${ints}" var="i">
${i}
</zhongfucheng:forEach>
<hr>
<hr>
對象數組:
<zhongfucheng:forEach items="${objects}" var="o">
${o}
</zhongfucheng:forEach>
<hr>
<hr>
map集合:
<zhongfucheng:forEach items="${map}" var="me">
${me.key} = ${me.value}
</zhongfucheng:forEach>
複製代碼
要開發這個標籤就很簡單了,只要獲取到標籤體的內容,再經過通過方法轉義下標籤體內容,輸出給瀏覽器便可!
public class Demo1 extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
//獲取到標籤體的內容再修改
StringWriter stringWriter = new StringWriter();
JspFragment jspFragment = this.getJspBody();
jspFragment.invoke(stringWriter);
String content = stringWriter.toString();
//通過filter()轉義,該方法在Tomcat能夠找到
content = filter(content);
//再把轉義後的內容輸出給瀏覽器
this.getJspContext().getOut().write(content);
}
private String filter(String message) {
if (message == null)
return (null);
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuffer result = new StringBuffer(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("<");
break;
case '>':
result.append(">");
break;
case '&':
result.append("&");
break;
case '"':
result.append(""");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
}
複製代碼
<zhongfucheng:filter><a href="2.jsp">你好啊</a> </zhongfucheng:filter>
<br>
<a href="2.jsp">你好啊
複製代碼
在JSTL中並無if else的標籤,JSTL給予咱們的是choose,when,otherwise標籤,如今咱們模仿choose,when,otherwise開發標籤
思路:when標籤有個test屬性,但otherwise怎麼判斷標籤體是執行仍是不執行呢?這時就須要choose標籤的支持了!choose標籤默認定義一個Boolean值爲false,。當when標籤體被執行了,就把Boolean值變成true,只要Boolean值爲false就執行otherwise標籤體的內容。
看程序就容易理解上面那句話了:!
public class Choose extends SimpleTagSupport {
private boolean flag;
@Override
public void doTag() throws JspException, IOException {
this.getJspBody().invoke(null);
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
複製代碼
public class When extends SimpleTagSupport {
private boolean test ;
@Override
public void doTag() throws JspException, IOException {
Choose choose = (Choose) this.getParent();
//若是test爲true和flag爲false,那麼執行該標籤體
if (test == true && choose.isFlag() == false) {
this.getJspBody().invoke(null);
//修改父標籤的flag
choose.setFlag(true);
}
}
public void setTest(boolean test) {
this.test = test;
}
}
複製代碼
public class OtherWise extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
Choose choose = (Choose) this.getParent();
//若是父標籤的flag爲false,就執行標籤體(若是when標籤沒執行,flag值就不會被修改!when標籤沒執行,就應該執行otherwise標籤!)
if (choose.isFlag() == false) {
getJspBody().invoke(null);
//改父標籤的flag爲false
choose.setFlag(true);
}
}
}
複製代碼
<zhongfucheng:choose>
<zhongfucheng:when test="${user!=null}">
user爲空
</zhongfucheng:when>
<zhongfucheng:otherwise>
user不爲空
</zhongfucheng:otherwise>
</zhongfucheng:choose>
複製代碼
此接口的主要功能是用於完成動態屬性的設置!前面咱們講解屬性標籤的時候,屬性都是寫多少個,用多少個的。如今若是我但願屬性能夠動態的增長,只須要在標籤處理器類中實現DynamicAttribute接口便可!
如今我要開發一個動態加法的標籤:
public class Demo1 extends SimpleTagSupport implements DynamicAttributes {
//既然有動態屬性和動態的值,那麼咱們就用一個Map集合存儲(1-1對應的關係),作的加法運算,值爲Double類型的。
Map<String, Double> map = new HashMap<>();
@Override
public void doTag() throws JspException, IOException {
//定義一個sum變量用於計算總值
double sum = 0.0;
//獲取到Map集合的數據
Iterator iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Double> entry = (Map.Entry<String, Double>) iterator.next();
sum += entry.getValue();
}
//向瀏覽器輸出總和是多少
this.getJspContext().getOut().write(String.valueOf(sum));
}
//對於這個要實現的方法,咱們只要關注第2個參數和第3個參數便可
//第二個參數表示的是動態屬性的名稱,第三個參數表示的是動態屬性的值
@Override
public void setDynamicAttribute(String s, String localName, Object value) throws JspException {
//將動態屬性的名字和值加到Map集合中
map.put(localName, Double.valueOf(Float.valueOf(value.toString())));
}
}
複製代碼
<tag>
<name>dynamicAttribute</name>
<tag-class> tag.Demo1</tag-class>
<body-content> empty</body-content>
<!--這個必需要設置爲true-->
<dynamic-attributes>true</dynamic-attributes>
</tag>
複製代碼
<zhongfucheng:dynamicAttribute num="1.1" num2="2.2" num3="1"/>
複製代碼
至於怎麼開發自定義函數,在EL表達式的博客中有!
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章的同窗,能夠關注微信公衆號:Java3y