JFinal框架中的web.xml可以实现Filter的解析。
JFinal中的Filter(web.xml,自己写的Filter实现了Filter);
Filter中的详细逻辑可以引入Handler和JFinal中的,Interceptor;
权限问题:
权限问题比较理想的办法是使用 AOP,所以可以选择 Interceptor、Handler或Filter。JFinal提供了 Interceptor与Handler可供选择。
通常情况下使用 Interceptor 比较合适。
Interceptor 中可以方便地获得 actionKey、controllerKey来做权限控制的依据,
还可以使用render控制页面跳转以及使用redirect来重定向。
Handler可以在更高层次来做权限,但失去了 interceptor中在上面提到的好处。
jfinal 的入口是 JFinalFilter,没有使用 servlet , 在 com.jfinal.core 包下面可以找到源代码
Filter:
JFinalFilter和其他的Filter一样,都是实现了Filter接口;
在web.xml中配置Filter可以对所有的请求及响应资源进行拦截过滤,如web.xml中的配置;
filter中的和filter-mapping中的必须一致;
中的类必须是定义的Filter实现类并且填写完成的包名;
是对请求及响应的URL资源拦截过滤。
JFinalFilter类三个方法init(),doFilter(),destroy()及生命周期介绍
1、Filter过滤器都是通过web容器作为实例化基础的,及它们是以web服务器作为容器承托。
2、init和destroy方法在整个生命周期中分别只执行一次:
init是在web容器启动时执行一次,对需要的服务实体进行初始化;
destroy是在web容器关闭是执行一次,对服务实例进行摧毁。
3、doFilter是最重要的方法,
当web应用正常启动之后前台的请求都跳进doFilter方法进行拦截处理,如果需要在Filter进行做文章,即可从此方法入手,doFilter方法中可进行资源链往下跳。
源码分析:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// 获取 request、response,设置编码
HttpServletRequest request = (HttpServletRequest)req
;
HttpServletResponse response = (HttpServletResponse)res
;
request.setCharacterEncoding(encoding);
// 初始化的时候可以看出 contextPathLength 为 0 或者为 项目名称的长度
// 比如 target = webapp/xx/yy/zz,则截取后的 target = /xx/yy/zz
String target = request.getRequestURI()
;
if
(
contextPathLength
!=
0
) { target = target.substring(
contextPathLength
)
;
}
// 在调用了 ActionHandler的 handle 方法之后,isHandled[0] 才会被置为 true。
boolean[] isHandled = {false}
;
try
{
// 重头戏,handler 链首到链尾 ActionHandler 依次进行处理
handler
.handle(target
,
request
,
response
,
isHandled)
;
}
catch (Exception e) {
if (log.isErrorEnabled()) {
String qs = request.getQueryString();
log.error(qs == null ? target : target + "?" + qs, e); } }
// 若最后 isHandled[0] = false,会执行下一个 Filter。
if
(isHandled[
0
] ==
false
) {
chain.doFilter(request, response);
}}doFilter是接收HTTP资源请求的方法:
1、首先对request请求进行设置编码;
2、然后获取请求路径,并调用handle方法,调用此方法可以根据请求路径作为key去查找init初始化Controller的Map集合里查找对应的Controller实体并调用对应的处理方法;
3、如果请求路径不满足判断条件(即未找到对应的Controller),会调用chain.doFilter按默认条件寻找下一个资源。
Handler:
doFilter - Handler 链中每个 handler.handle(...)
JFinal 初始化过程中可以 add JFinal 库中的Handler 或自定义的 Handler。
例如:ContextPathHandler,JFinal 自身扩展的 Handler。
访问项目时就会走过 handler 方法设置 contextPath。这样在前端就可以通过 ${CONTEXT_PATH} 得到项目根路径。
ContextPathHandler,JFinal 自身扩展的 Handler。
public class ContextPathHandler extends Handler {
private String contextPathName;
public ContextPathHandler() {
contextPathName = "CONTEXT_PATH";
}
public ContextPathHandler(String contextPathName) {
if (StrKit.isBlank(contextPathName))
throw new IllegalArgumentException("contextPathName can not be blank.");
this.contextPathName = contextPathName;
}
public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
request.setAttribute(contextPathName, request.getContextPath());
System.out.println("哈哈哈");
next.handle(target, request, response, isHandled);
}
}
FakeStaticHandler,也是 JFinal 自身扩展的 Handler。new FakeStaticHandler 时可定义后缀,访问路径 target 必须是以这个后缀结尾才可以进行下去。
public class FakeStaticHandler extends Handler {
private String viewPostfix;
public FakeStaticHandler() {
viewPostfix = ".html";
}
public FakeStaticHandler(String viewPostfix) {
if (StrKit.isBlank(viewPostfix))
throw new IllegalArgumentException("viewPostfix can not be blank.");
this.viewPostfix = viewPostfix;
}
public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
if ("/".equals(target)) {
next.handle(target, request, response, isHandled);
return;
}
if (target.indexOf('.') == -1) {
HandlerKit.renderError404(request, response, isHandled);
return ;
}
int index = target.lastIndexOf(viewPostfix);
if (index != -1)
target = target.substring(0, index);
next.handle(target, request, response, isHandled);
}
}
到达 Handler 链尾 ActionHandler 处理
访问交给 Handler 链尾 ActionHandler,调用 handle 方法进行处理:
根据访问路径 target 得到 Action,由 Action 得到 Controller。
接着 new Invocation(action, controller).invoke() 进入责任链,
反射机制调用 Controller 的方法处理(此方法可根据 Action 得到)。
public final void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
if (target.indexOf('.') != -1) {
return ;
}
isHandled[0] = true;
String[] urlPara = {null};
// actionMapping 根据 target 得到对应的 action
Action action = actionMapping.getAction(target, urlPara);
if (action == null) {
if (log.isWarnEnabled()) {
String qs = request.getQueryString();
log.warn("404 Action Not Found: " + (qs == null ? target : target + "?" + qs));
}
renderFactory.getErrorRender(404).setContext(request, response).render();
return ;
}
try {
// 由 action 得到对应的 Controller
Controller controller = action.getControllerClass().newInstance();
// Controller 初始化
controller.init(request, response, urlPara[0]);
if (devMode) {
if (ActionReporter.isReportAfterInvocation(request)) {
new Invocation(action, controller).invoke();
ActionReporter.report(controller, action);
} else {
ActionReporter.report(controller, action);
new Invocation(action, controller).invoke();
}
}
else {
// 调用 Controller 相应的处理方法
new Invocation(action, controller).invoke();
}
// 获取对应的 Render,如果是一个 ActionRender,就再交给 handler 处理;如果 Render == null会按照默认Render处理;
Render render = controller.getRender();
if (render instanceof ActionRender) {
String actionUrl = ((ActionRender)render).getActionUrl();
if (target.equals(actionUrl))
throw new RuntimeException("The forward action url is the same as before.");
else
handle(actionUrl, request, response, isHandled);
return ;
}
if (render == null)
render = renderFactory.getDefaultRender(action.getViewPath() + action.getMethodName());
// 调用 Render 实现类 数据写入页面
render.setContext(request, response, action.getViewPath()).render();
}
catch (RenderException e) {
if (log.isErrorEnabled()) {
String qs = request.getQueryString();
log.error(qs == null ? target : target + "?" + qs, e);
}
}
catch (ActionException e) {
int errorCode = e.getErrorCode();
if (errorCode == 404 && log.isWarnEnabled()) {
String qs = request.getQueryString();
log.warn("404 Not Found: " + (qs == null ? target : target + "?" + qs));
}
else if (errorCode == 401 && log.isWarnEnabled()) {
String qs = request.getQueryString();
log.warn("401 Unauthorized: " + (qs == null ? target : target + "?" + qs));
}
else if (errorCode == 403 && log.isWarnEnabled()) {
String qs = request.getQueryString();
log.warn("403 Forbidden: " + (qs == null ? target : target + "?" + qs));
}
else if (log.isErrorEnabled()) {
String qs = request.getQueryString();
log.error(qs == null ? target : target + "?" + qs, e);
}
e.getErrorRender().setContext(request, response, action.getViewPath()).render();
}
catch (Throwable t) {
if (log.isErrorEnabled()) {
String qs = request.getQueryString();
log.error(qs == null ? target : target + "?" + qs, t);
}
renderFactory.getErrorRender(500).setContext(request, response, action.getViewPath()).render();
}
}
Interceptor:拦截器链如何处理数据
package com.jfinal.aop; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import com.jfinal.core.Action; import com.jfinal.core.Controller; import net.sf.cglib.proxy.MethodProxy; /** * Invocation is used to invoke the interceptors and the target method */ @SuppressWarnings("unchecked") public class Invocation { private Action action; private static final Object[] NULL_ARGS = new Object[0]; // Prevent new Object[0] by jvm for paras of action invocation. boolean useInjectTarget; private Object target; private Method method; private Object[] args; private MethodProxy methodProxy; private Interceptor[] inters; private Object returnValue = null; private int index = 0; // InvocationWrapper need this constructor protected Invocation() { this.action = null; } public Invocation(Action action, Controller controller) { this.action = action; this.inters = action.getInterceptors(); this.target = controller; this.args = NULL_ARGS; } public Invocation(Object target, Method method, Object[] args, MethodProxy methodProxy, Interceptor[] inters) { this.action = null; this.target = target; this.method = method; this.args = args; this.methodProxy = methodProxy; this.inters = inters; } public void invoke() { if (index < inters.length) { inters[index++].intercept(this); } else if (index++ == inters.length) { // index++ ensure invoke action only one time try { // Invoke the action if (action != null) { returnValue = action.getMethod().invoke(target, args); } // Invoke the method else { // if (!Modifier.isAbstract(method.getModifiers())) // returnValue = methodProxy.invokeSuper(target, args); if (useInjectTarget) returnValue = methodProxy.invoke(target, args); else returnValue = methodProxy.invokeSuper(target, args); } } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(e); } catch (RuntimeException e) { throw e; } catch (Throwable t) { throw new RuntimeException(t); } } } public Object getArg(int index) { if (index >= args.length) throw new ArrayIndexOutOfBoundsException(); return args[index]; } public void setArg(int index, Object value) { if (index >= args.length) throw new ArrayIndexOutOfBoundsException(); args[index] = value; } public Object[] getArgs() { return args; } /** * Get the target object which be intercepted * <pre> * Example: * OrderService os = getTarget(); * </pre> */ public <T> T getTarget() { return (T)target; } /** * Return the method of this action. * <p> * You can getMethod.getAnnotations() to get annotation on action method to do more things */ public Method getMethod() { if (action != null) return action.getMethod(); return method; } /** * Return the method name of this action's method. */ public String getMethodName() { if (action != null) return action.getMethodName(); return method.getName(); } /** * Get the return value of the target method */ public <T> T getReturnValue() { return (T)returnValue; } /** * Set the return value of the target method */ public void setReturnValue(Object returnValue) { this.returnValue = returnValue; } // --------- /** * Return the controller of this action. */ public Controller getController() { if (action == null) throw new RuntimeException("This method can only be used for action interception"); return (Controller)target; } /** * Return the action key. * actionKey = controllerKey + methodName */ public String getActionKey() { if (action == null) throw new RuntimeException("This method can only be used for action interception"); return action.getActionKey(); } /** * Return the controller key. */ public String getControllerKey() { if (action == null) throw new RuntimeException("This method can only be used for action interception"); return action.getControllerKey(); } /** * Return view path of this controller. */ public String getViewPath() { if (action == null) throw new RuntimeException("This method can only be used for action interception"); return action.getViewPath(); } /** * return true if it is action invocation. */ public boolean isActionInvocation() { return action != null; } }
package com.tspace.common.interceptor; import com.jfinal.aop.Interceptor; import com.jfinal.aop.Invocation; import com.jfinal.core.Controller; import com.tspace.common.util.UrlUtil; import javax.servlet.http.HttpServletRequest; import java.util.List; import java.util.Map; /** * 权限处理拦截器 * @author 2016.2.5 */ public class AuthInterceptor implements Interceptor { @Override public void intercept(Invocation inv) { //TODO 注解标识不拦截 //Annotation[] as = inv.getMethod().getAnnotations(); //System.out.println("********Annotation: " + as.length); Controller c = inv.getController(); HttpServletRequest request = c.getRequest(); int contextLength = request.getContextPath().length(); String currUrl = request.getRequestURI().substring(contextLength); System.out.println(UrlUtil.formatBaseUrl(currUrl)); List<String> noAuthUrl = c.getSessionAttr("noAuthUrl"); if(noAuthUrl != null) { //页面权限处理,拦截action/method链接的所有/action/*页面 for (String url : noAuthUrl) { if(UrlUtil.formatBaseUrl(currUrl).equals(UrlUtil.formatBaseUrl(url))) { c.renderText("没有权限访问该页面!"); return; } } //按钮权限 Map<String, Object> authBtn = c.getSessionAttr("noAuthBtnUrl"); // TODO 强制类型装换看看是不是有好的解决方案 @SuppressWarnings("unchecked") List<String> noAuthBtnUrl = (List<String>) authBtn.get("btnUrlList"); @SuppressWarnings("unchecked") Map<String, String> noAuthBtnMap = (Map<String, String>) authBtn.get("pageBtnMap"); request.setAttribute("noAuthBtn", noAuthBtnMap.get(currUrl)); for (String btnUrl : noAuthBtnUrl) { if(currUrl.equals(btnUrl)) { c.renderText("没有权限访问该页面!"); return; } } } inv.invoke(); } }
new Invocation(action, controller).invoke()由action 得到拦截器数组,如果数组为空,就可以利用反射机制进入 Controller 的方法进行处理的了;
不为空,会按照顺序执行拦截器,最后通过反射进入Controller方法进行处理。
如果拦截器数组不为空,
就会遇到拦截器1 的拦截 - inters[0].intercept(this),
数组下标 +1,拦截器1 继续调用这个 Invocation 实例的 invoke() 方法,并且会在前后加上一些目的性操作;
若下标未越界,接着会遇到拦截器2 的拦截 - inters[1].intercept(this),
数组下标 +1,拦截器2 继续调用这个 Invocation 实例的 invoke() 方法,并且会在前面加上一些目的性操作;
如此继续直到下标越界,接着反射机制进入 Controller 的方法进行处理。
render(...)
接着上面 ActionHandler.handle 继续,在 Controller 方法处理的最后,
往往要调用 render 方法来实例化 render 变量。
例如,我自定义的一个 IndexController,在最后一步调用 render("next.html")
public void getMenu() { //admin用户拥有所有页面 if("admin".equals(getSessionUser().get("usr_name"))) { renderJson(Menu.me.findAll()); }else { Object menuList = getSessionAttr("menuList"); renderJson(menuList); } }
用到父类 Controller 的 render 方法,
通过 renderFactory.getRender 得到 render 实例。
还能使用 renderJosn、renderText 等多种方法
Filter:过滤器;
Handler:处理器;
Interceptor:拦截器