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