我们知道,在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); } } }