Handler的使用实战小技巧

我们知道,在JFinal中主要通过Interceptor来实现AOP,可以很方便的对Controller甚至Service进行@Before,来实现各种切面需求,比如会话处理、鉴权处理、输入校验等功能。

但Interceptor也不是万能的,某些情况还需要不起眼的Handler

之前遇到一个问题,在Interceptor中,初始化了一个ThreadLocal线程对象,其中封装了会话相关的重要信息,原本是这么做的:

public class EInterceptorClient implements Interceptor {
    public void intercept(Invocation inv) {
        try {
            ClientTool.setTreadLocalClient(client);
            inv.invoke();
        }finally{
            ClientTool.removeTreadLocalClient();
        }
    }
}

可是在调试时发现,模板中无法访问这个ThreadLocal对象

把finally中的remove语句注释掉就可以了,原来这里只能拦截到Controller执行完成后,Render执行前的切面,而我要的效果是Render执行后才删除

ThreadLocal不手动清除肯定是不行的,里面的Map会迅速增长。既然Interceptor中做不到,就只能在Render里想办法了。

于是自定义了RenderFactory:

public class ERenderFactory extends RenderFactory {
    @Override
    public Render getRender(String view) {
        return new ERender(view);
    }
}
public class ERender extends TemplateRender {
    public ERender(String view) {
        super(view);
    }
    @Override
    public void render() {
        try {
            super.render();
        }finally{
            ClientTool.removeTreadLocalClient();
        }
    }
}

果然,在模板中可以正常引用ThreadLocal对象,并且视图渲染之后执行finally中的语句,删除了线程对象,貌似问题解决。

但紧接着发现,在Controller中如果执行了redirect、renderJson、或者其他的render,因为没有执行自定义的ERender,ThreadLocal还是不会被清除

难道只能把这些个xxxRender全都@Override个遍吗,心想我们亲爱的@JFinal肯定不会允许这种情况发生的,于是波总一句话提醒了我:在Handler中即可简单搞定。

真是一句话点醒梦中人,怎么就把Handler忘了呢,于是:

public class EHandler extends Handler {
    public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
        try {
            next.handle(target, request, response, isHandled);
        } finally {
            ClientTool.removeTreadLocalClient();
        }
    }
}

一句话就搞定了!

原来Handler不仅能做自定义路由,还能作为整个请求的切面拦截器,又学到一招。

再附上一段本人用Handler实现的二级域名解析的代码:

/**
 * 二级域名解析
 * 支持两种域名模式:
 * 1)前缀:(http://[domain].xxxxx.com/...),要求域名开通泛解析
 * 2)后缀:(http://www.xxxxx.com/[domain]/...),无需域名泛解析
 * 
 * @author netwild
 *
 */
public class EHandleSecDomain extends Handler {
    /**
     * 是否启用二级域名解析功能
     */
    private final static boolean secDomainUsed = PropKit.get("secDomainUsed", false);
    /**
     * 可用的域名解析模式列表
     * 前缀(http://[domain].xxxxx.com/...),要求域名开通泛解析
     * 后缀(http://www.xxxxx.com/[domain]/...),无需域名泛解析
     */
    private final static String[] secDomainModeArr = {"prefix", "postfix"};
    /**
     * 实际采用的域名解析模式
     */
    private static String secDomainMode = PropKit.get("secDomainMode", secDomainModeArr[1]); 

    public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
        //静态资源直接跳出
        if (target.indexOf('.') != -1) return;
        //解析二级域名
        Ret ret = formatDomain(target, request);
        String domain = ret.getStr("domain"); //解析出来的二级域名[domain]
        target = ret.getStr("target"); //解析处理后的target,仅后缀模式时发生改变
        //将解析出来的[domain]存入会话,便于在Interceptor或Controller中处理
        request.setAttribute(Const.ATTR_KEY_DOMAIN, domain);
        //访问资源
        next.handle(target, request, response, isHandled);
    }
    /**
     * 动态解析二级域名
     * @category 动态解析二级域名
     * @param target
     * @param request
     * @return
     */
    private Ret formatDomain(String target, HttpServletRequest request){
        String domain = null;
        if(!secDomainUsed){
            return Ret.create("domain", domain).set("target", target);
        }else{
            //返回主机头
            String url = request.getServerName();
            //分析当前的主机头信息,如果是IP地址或者计算机名,只能采用后缀模式
            if(ERegex.checkIP(url) || url.indexOf(".") < 1) secDomainMode = secDomainModeArr[1];
            if(secDomainMode.equalsIgnoreCase(secDomainModeArr[0])){
                //前缀模式(格式:http://[domain].xxxxx.com/...)
                String str = ERegex.findFirst(url, "^([^.]+)\\.");
                if(EStr.isEmpty(str)){
                    LogBackTool.warn(getClass(), "不是有效的域名地址:{},域名模式:{}", url, secDomainMode);
                }else{
                    domain = str;
                }
            }else{
                //后缀模式(格式:http://www.xxxxx.com/[domain]/...)
                String str = ERegex.findFirst(target, "^\\/([^/]+)");
                if(EStr.isEmpty(str)){
                    LogBackTool.warn(getClass(), "不是有效的域名地址:{},域名模式:{}", url, secDomainMode);
                }else{
                    domain = str;
                    //后缀模式时,需要将[domain]部分从url中移除,才能匹配到Controller的路由
                    target = target.replaceFirst("\\/" + domain + "(\\/?)", "$1");
                }
            }
            return Ret.create("domain", domain).set("target", target);
        }
    }

}


评论区

JFinal

2017-06-18 12:41

全是干货啊,代码简洁清晰,感谢你的分享

马小酱

2017-06-23 15:49

感谢分享

我要做菜鸟

2017-07-06 10:54

感谢分享,顺便回顾了下handler链的设计,有时间的话,希望能请波总再介绍下handler 链。

热门分享

扫码入社