Java for Web学习笔记(三七):自定义tag(5)自定义tld文件

我们将学习通过tld文件提供时间日期的本地显示格式的tag。

定义TLD

我们需要对tld进行整体描述,创建/WEB-INF/tld/wei.tld文件,如下:

<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
        version="2.1">
        
    <tlib-version>1.0</tlib-version>
    <short-name>wei</short-name>
    <uri>http://www.flowingflying.wei.cn/jsp/tld/wei</uri>
    <tag>
    	<description><![CDATA[...something...]]></description>
    	<name>formatDate</name>
    	<tag-class>cn.flowingflying.wei.chapter08.FormatDateTag</tag-class>
    	<body-content>empty</body-content>

        <attribute>
            <description>The object representing a date and/or time that should be formatted.</description>
            <name>value</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>

        <attribute>
            <description>"date" (default), "time", or "both"</description>
            <name>type</name>
            <required>false</required>
            <rtexprvalue>true</rtexprvalue>
            <type>java.lang.String</type>
        </attribute>
 
        ...... 从略 ......
        <dynamic-attributes>false</dynamic-attributes>
    </tag>

    ... functions ...具体见后...现略...    
</taglib>

实现tag handler类

继承TagSupport

cn.flowingflying.wei.chapter08.FormatDateTag是tag的handler,具体实现tag的代码。代码有些长,但是有必要读一下,由于当中越界使用了protected方法以及将方法名字作为参数,请先阅读Method和Function的使用,但请不要被这些花俏的技巧障目,重点关注tag handler的实现。

//【1】tag handler是TagSupport的继承类
public class FormatDateTag extends TagSupport{	
    private static final long serialVersionUID = 1L;
    private static final Method GET_LOCALE_METHOD;
    private static final Method GET_TIME_ZONE_METHOD;

    /* 我们希望直接利用Apache/GlassFish JSTL中处理本地时间和时区的现成的static protected方法,通过refection方式来使用它们。*/
    static {
        try {
            GET_LOCALE_METHOD = SetLocaleSupport.class.getDeclaredMethod(
                    "getFormattingLocale", PageContext.class, Tag.class,boolean.class,boolean.class);
            GET_LOCALE_METHOD.setAccessible(true);

            GET_TIME_ZONE_METHOD = TimeZoneSupport.class.getDeclaredMethod(
                    "getTimeZone", PageContext.class, Tag.class);
            GET_TIME_ZONE_METHOD.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    //【2】定义属性,具体对应tld中的attribute,相应的定义相关的setters和getters(见后)
    /** The object representing a date and/or time that should be formatted.*/
    protected Object value; 
    /** "date" (default), "time", or "both" */
    protected Type type;    
    /** "default" (default), "short", "medium", "long", or "full" in accordance with java.text.DateFormat.*/
    protected Style dateStyle; 
    /** "default" (default), "short", "medium", "long", or "full" in accordance with java.text.DateFormat. */
    protected Style timeStyle; 
    /** Defaults to false, specify true to put the time before the date. */
    protected boolean timeFirst; 
    /** Defaults to a single space ( ), used to specify a custom value to separate the date and time with. */
    protected String separateDateTimeWith; 
    protected String pattern;
    protected TimeZone timeZone;
    /** Scope variable to save the formatted date to instead of inlining.
     *  Exported scope variable will be of type java.lang.String.*/
    protected String var; 
    protected int scope;

    public FormatDateTag() {
        super();
        init();
    }

    //【3】init()在构造函数和release()中调用,对参数进行初始化。
    // 这很重要,因为容器会重用tag handler来提高效率(也就是不一定新建实例,即没有新建的初始化),之间会调用release()。我们需确保初始化。
    private void init() {
        this.type = Type.DATE;
        this.dateStyle = this.timeStyle = Style.DEFAULT;
        this.timeFirst = false;
        this.separateDateTimeWith = " ";
        this.pattern = this.var = null;
        this.timeZone = null;
        this.scope = PageContext.PAGE_SCOPE;
    }

    @Override
    public void release() {
        this.init();
    }


    /* 【4】doStartTag(),doAfterBody(),doEndTag()是在不同阶段对tag的处理。
     * FormatDate中没有body,所有不需要doAfterBody(),如果带有body,通常需要重写doStartTag()和doAfterBody()。
     * doEndTag()是在关闭tag时调用的,本例就是进行时间日期的翻译,直接打印到页面或者存放在var中。
     */

    @Override
    public int doEndTag() throws JspException{
    	// 4.1】如果value为null,在scope中删除这个var属性,返回Tag.EVAL_PAGE,继续往下走。
        if(this.value == null){
            if(this.var != null)
                pageContext.removeAttribute(this.var, this.scope);
            return Tag.EVAL_PAGE;
        }        

        //4.2】获得formatted。如果有给出locale,需要根据locale进行转换
        String formatted;
        Locale locale = this.getLocale();
        if(locale != null){
            if(this.timeZone == null)
                this.timeZone = this.getTimeZone();
            if(this.timeZone == null)
                this.timeZone = TimeZone.getDefault();

            if(this.value instanceof Date)
                formatted = this.formatDate((Date)this.value, locale);
            else if(this.value instanceof Calendar)
                formatted = this.formatDate(((Calendar)this.value).getTime(), locale);
            else if(this.value instanceof TemporalAccessor)
                formatted = this.formatDate((TemporalAccessor)this.value, locale);
            else
                throw new JspTagException("Unsupported date type " + this.value.getClass().getCanonicalName() + ".");
        }else{
            formatted = this.value.toString();
        }

        //4.3】如果有var就将结果存进去,如无,就输出。
        if(this.var != null){
            this.pageContext.setAttribute(this.var, formatted, this.scope);
        }else{
            try {
                this.pageContext.getOut().print(formatted);
            } catch (IOException e) {
                throw new JspTagException(e.toString(), e);
            }
        }

        return Tag.EVAL_PAGE;
    }

    //进行Date类型的翻译
    private String formatDate(Date value, Locale locale){
        if(this.pattern != null) {
            SimpleDateFormat format = new SimpleDateFormat(this.pattern, locale);
            format.setTimeZone(this.timeZone);
            return format.format(value);
        }

        Function<Date, String> dateFormat = null;
        Function<Date, String> timeFormat = null;

        if(this.type == Type.DATE || this.type == Type.BOTH){
            DateFormat format = DateFormat.getDateInstance(this.dateStyle.getDateFormat(), locale);
            format.setTimeZone(this.timeZone);
            dateFormat = format::format;
        }

        if(this.type == Type.TIME || this.type == Type.BOTH){
            DateFormat format = DateFormat.getTimeInstance(this.timeStyle.getDateFormat(), locale);
            format.setTimeZone(this.timeZone);
            timeFormat = format::format;
        }

        return this.formatDate(dateFormat, timeFormat, value);
    }

    private String formatDate(TemporalAccessor value, Locale locale){
        ZoneId zoneId = this.timeZone.toZoneId();

        if(this.pattern != null)
            return DateTimeFormatter.ofPattern(this.pattern, locale).withZone(zoneId).format(value);

        Function<TemporalAccessor, String> dateFormat = null;
        Function<TemporalAccessor, String> timeFormat = null;

        if(this.type == Type.DATE || this.type == Type.BOTH)
            dateFormat = DateTimeFormatter.ofLocalizedDate(this.dateStyle.getFormatStyle())
                                          .withLocale(locale).withZone(zoneId)::format;
        if(this.type == Type.TIME || this.type == Type.BOTH)
            timeFormat = DateTimeFormatter.ofLocalizedTime(this.timeStyle.getFormatStyle())
                                          .withLocale(locale).withZone(zoneId)::format;

        return this.formatDate(dateFormat, timeFormat, value);
    }

    private <T> String formatDate(Function<T, String> dateFormat,
                                  Function<T, String> timeFormat, T value){
        String formatted = "";
        if(timeFormat != null && this.timeFirst){
            formatted += timeFormat.apply(value);
            if(dateFormat != null)
                formatted += this.separateDateTimeWith;
        }

        if(dateFormat != null)
            formatted += dateFormat.apply(value);

        if(timeFormat != null && !this.timeFirst){
            if(dateFormat != null)
                formatted += this.separateDateTimeWith;
            formatted += timeFormat.apply(value);
        }

        return formatted;
    }

    private Locale getLocale() throws JspTagException{
        try {         
            return (Locale)GET_LOCALE_METHOD.invoke(null, this.pageContext, this, true, true);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new JspTagException(e.toString(), e);
        }
    }

    private TimeZone getTimeZone() throws JspTagException{
        try {
            return (TimeZone)GET_TIME_ZONE_METHOD.invoke(null, this.pageContext, this);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new JspTagException(e.toString(), e);
        }
    }

    public void setValue(Object value){
        this.value = value;
    }

    public void setType(String type){
        this.type = StringUtils.isBlank(type) ? null : Type.valueOf(type.toUpperCase());
    }

    public void setDateStyle(String style){
        this.dateStyle = StringUtils.isBlank(style) ? null : Style.valueOf(style.toUpperCase());
    }

    public void setTimeStyle(String style){
        this.timeStyle = StringUtils.isBlank(style) ? null : Style.valueOf(style.toUpperCase());
    }

    public void setTimeFirst(boolean timeFirst){
        this.timeFirst = timeFirst;
    }

    public void setSeparateDateTimeWith(String separate){
        this.separateDateTimeWith = StringUtils.isBlank(separate) ? " " : separate;
    }

    public void setPattern(String pattern){
        this.pattern = StringUtils.isBlank(pattern) ? null : pattern;
    }

    public void setTimeZone(Object timeZone){
        if(timeZone instanceof String)
            this.timeZone = StringUtils.isBlank((String)timeZone) ? null : TimeZone.getTimeZone((String)timeZone);
        else if(timeZone instanceof TimeZone)
            this.timeZone = (TimeZone)timeZone;
        else if(timeZone instanceof ZoneId)
            this.timeZone = TimeZone.getTimeZone((ZoneId)timeZone);
        else if(timeZone == null)
            this.timeZone = null;
        else
            throw new IllegalArgumentException("Time zone type " 
                  + timeZone.getClass().getCanonicalName() + " not recognized");
    }

    public void setVar(String var){
        this.var = StringUtils.isBlank(var) ? null : var;
    }

    public void setScope(String scope){
        this.scope = Util.getScope(scope);
    }

    private static enum Type{
        DATE, TIME, BOTH
    }

    private static enum Style{
        DEFAULT(DateFormat.MEDIUM, FormatStyle.MEDIUM),
        SHORT(DateFormat.SHORT, FormatStyle.SHORT),
        MEDIUM(DateFormat.MEDIUM, FormatStyle.MEDIUM),
        LONG(DateFormat.LONG, FormatStyle.LONG),
        FULL(DateFormat.FULL, FormatStyle.FULL);

        private final int dateFormat;

        private final FormatStyle formatStyle;

        private Style(int dateFormat, FormatStyle formatStyle){
            this.dateFormat = dateFormat;
            this.formatStyle = formatStyle;
        }

        public int getDateFormat() {
            return dateFormat;
        }

        public FormatStyle getFormatStyle() {
            return formatStyle;
        }
    }
}

其他方式

Tag Handler需要实现interface Tag或者SimpleTag,它们都是JspTag maker interface,前面的TagSupport实现了Tag。SimpleTag比较简单,但有个缺点,SimpleTag类创建,使用一次,然后丢弃,也就是更为消耗资源,但是可以很方便地使用循环,例子如下:

public void doTag() throws JspException, IOException{
    while(condition-is-true){
        this.getJspContext().setAttribute(“someElVariable”, value);
        //invoke()用于将body写到JSP的output。如果不是null,而是其他的Writer,则写到该writer中。
        this.getJspBody().invoke(null);
    }
}

如果我们采用IterationTag(是Tag的继承),则需要:

public int doStartTag() throws JspException{
    //只唤醒1次。doStartTag()可以返回Tag.SKIP_BODY或Tag.EVAL_BODY_INCLUDE
    if(condition-is-true){
        this.pageContext.setAttribute(“someElVariable”, value);
        return Tag.EVAL_BODY_INCLUDE;
    }
    return Tag.SKIP_BODY;
}
public int doAfterBody() throws JspException{
    // 根据需要可唤醒多次,doAfterBody()可以返回Tag.SKIP_BODY或IterationTag.EVAL_BODY_AGAIN
    if(condition-is-true){
        this.pageContext.setAttribute(“someElVariable”, value);
        return Tag.EVAL_BODY_AGAIN; //再次执行body
    }
    return Tag.SKIP_BODY;
}
public int doEndTag() throws JspException{
    // 只唤醒1次,doEndTag()可以返回Tag.SKIP_PAGE或Tag.EVAL_PAGE
    return Tag.EVAL_PAGE;
}
  • SimpleTag用于不要求性能,不需要在池中重用实例,具体实现时通常继承SimpleTagSupport;
  • Tag除了循环获取body内容外,可以覆盖SimpleTag,具体实现通常继承TagSupport;
    • LoopTag是Tag的继承,不常用,主要用于对象如collection或者arrays的循环,具体实现通常继承LoopTagSupport;
    • IterationTag是Tag的集成,可以基于条件进行迭代,具体实现通常继承IterationTagSupport;
      • BodyTag继承IterationTag,允许输出缓存在一个BodyContent对象中,用作某种用途。在doStartTag()中返回 BodyTag.EVAL_BODY_BUFFERED而非Tag.EVAL_BODY_INCLUDE,并在doEndTag()中获取body内容。一般通过继承BodyTagSupport实现。

tld的EL function

定义function

我们在tld中增加两个function的定义,位于定义的后面。这两个function,一个使用现有的工具,一个要通过代码实现。

    ...... 前略 ......
    </tag>

    <!-- 第一个function,我们直接利用StringUtils提供的方法StringUtils.abbreviate(String,int)来实现。-->
    <function>
        <description>
            Abbreviates a string using ellipses (...) if the string is too long.
            The string parameter is the string to shorten, the int parameter is
            the maximum length the string should be, after which it will be
            shortened.
        </description>
        <name>abbreviateString</name>
        <function-class>org.apache.commons.lang3.StringUtils</function-class>
        <function-signature>
            java.lang.String abbreviate(java.lang.String,int)
        </function-signature>
    </function>

    <!-- 第一个function,我们将通过自己的代码实现。-->
    <!-- 类为cn.wei.flowingflying.chapter08.TimeUtils,静态方法为intervalToString(long)-->
    <function>
        <description>
            Formats a time interval in an attractive way, such as "less than one
            second" or "ten seconds" or "about 12 minutes".
        </description>
        <name>timeIntervalToString</name>
        <function-class>cn.wei.flowingflying.chapter08.TimeUtils</function-class>
        <function-signature>
            java.lang.String intervalToString(long)
        </function-signature>
    </function>
</taglib>

代码实现

public class TimeUtils {
	public static String intervalToString(long timeInterval){
		if(timeInterval < 1_000)
			return "less than one second";
		if(timeInterval < 60_000)
			return (timeInterval / 1_000) + " seconds";
		return "about " + (timeInterval / 60_000) + " minutes";
	}
}

EL使用function

例子中,我们使用了之前的template的tag。

<%--@elvariable id="myText" type="java.lang.String"--%>
<%@ taglib prefix="wei" uri="http://www.flowingflying.wei.cn/jsp/tld/wei" %>
<%@ taglib prefix="template" tagdir="/WEB-INF/tags/template" %>
<template:main htmlTitle="Abbreviating Text">
   <b>Text:</b> ${wei:abbreviateString(myText, 32)}<br />
</template:main>

使用tag

结合之前的template,jsp的代码如下:

<%--@elvariable id="date" type="java.util.Date"--%>
<%--@elvariable id="calendar" type="java.util.Calendar"--%>
<%--@elvariable id="instant" type="java.time.Instant"--%>
<%@ taglib prefix="template" tagdir="/WEB-INF/tags/template" %>
<%@ taglib prefix="wei" uri="http://www.flowingflying.wei.cn/jsp/tld/wei" %>

<template:main htmlTitle="Displaying Dates Properly">
    <b>Date:</b>
    <wei:formatDate value="${date}" type="both" dateStyle="full" timeStyle="full" /><br />

    <b>Date, time first with separator:</b>
    <wei:formatDate value="${date}" type="both" dateStyle="full" timeStyle="full" timeFirst="true" 
        separateDateTimeWith=" on " /><br />
    <hr>
    <b>Calendar:</b>
    <wei:formatDate value="${calendar}" type="both" dateStyle="full" timeStyle="full" /><br />
       
    <hr>
    <b>Instant:</b>
    <wei:formatDate value="${instant}" type="both" dateStyle="full" timeStyle="full" /><br />
       
    <b>Instant, time first with separator:</b>
    <wei:formatDate value="${instant}" type="both" dateStyle="full" timeStyle="full" timeFirst="true"
        separateDateTimeWith=" 于 " /><br />
</template:main>


相关链接: 我的Professional Java for Web Applications相关文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值