jFinal
的路由解析是在JFinalFilter
中作的,这个Filter也须要在web.xml
中配置。JFinalFilter
实现了javax.servlet.Filter
接口,从这里也能够看出jFinal
是基于Servlet
的。JFinalFilter
在初始化时负责初始化jFinal
项目的配置(com.jfinal.core.Config)、路由表(Route)、映射表(ActionMapping)等;路由解析是在JFinalFilter
的dofilter
方法完成的。java
关键词: Route
Handler
Action
ActionMapping
web
分析jFinal
的路由解析逻辑必须从jFinal
的通常项目配置入手,配置的做用是为路由解析提供支持的。和通常Java Web MVC框架不一样的是jFinal
没有采用xml配置的形式,但不是不须要配置,仍是须要提供一个JFinalConfig
的继承实现类,实现configXXX
方法来支持配置初始化,初始化的入口是JFinalFilter
的init
方法。数据库
jFinal
工程一样须要web.xml配置文件,可是较其余MVC框架的web.xml文件内容或许要简单许多,除了配置welcome-file-list
,只须要配置一个filter
。api
<filter> <filter-name>jfinal</filter-name> <filter-class>com.jfinal.core.JFinalFilter</filter-class> <init-param> <param-name>configClass</param-name> <param-value>com.app.common.Config</param-value> </init-param> </filter> <filter-mapping> <filter-name>jfinal</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
JFinalFilter
是惟一须要配置的filter
,只须要提供一个configClass
参数,它会在JFinalFilter
的init
方法中利用Class.forName(configClass).newInstance();
被实例化。数组
上面的configClass
参数的值com.app.common.Config
是项目定义的JFinalConfig
的实现类,虽然整个项目没有xml配置,可是这里就是,只不过是Java代码的形式。JFinalConfig
只是暴露接口,配置信息最终保存在jFinal
的静态类com.jfinal.core.Config
中。com.jfinal.core.Config
类设计成不能够实例化,它定义的私有静态成员变量能够保证惟一。JFinalConfig
实现的接口就负责填充com.jfinal.core.Config
成员变量。
在本文中会关注JFinalConfig
的如下接口方法:app
/** * Config route */ public abstract void configRoute(Routes me); /** * Config interceptor applied to all actions. */ public abstract void configInterceptor(Interceptors me); /** * Config handler */ public abstract void configHandler(Handlers me);
在Config
的成员变量中咱们关注这几个变量:框架
private static final Routes routes = new Routes(){public void config() {}}; private static final Interceptors interceptors = new Interceptors(); private static final Handlers handlers = new Handlers();
interceptors
拥有全部的Interceptor
,内部结构是List<Interceptor>
;handlers
拥有全部的handler
,内部结构是List<Handler>
。Routes
定义了两个容器,jsp
private final Map<String, Class<? extends Controller>> map = new HashMap<String, Class<? extends Controller>>(); private final Map<String, String> viewPathMap = new HashMap<String, String>();
对外提供了多个重载的add
方法,用于增长路由
,map
和viewMap
的键都是controllerKey
。
关于Interceptor
、Handler
和Routes
下文会继续说明,咱们先来看看自定义的JFinalConfig
实现类com.app.common.Config
作了什么事情。便是咱们关注的JFinalConfig
的抽象方法实现。ide
package com.app.common; public class Config extends JFinalConfig { @Override public void configConstant(Constants me) { //配置默认View类型 } @Override public void configRoute(Routes me) { me.add("/api/user", UserController.class); me.add("/admin/user", ManagerController.class, "/admin"); me.add("/admin/index", IndexController.class, "/admin"); //... } @Override public void configPlugin(Plugins me) { //配置数据库链接 //配置数据表和pojo映射 } @Override public void configInterceptor(Interceptors me) { //配置拦截器 } @Override public void configHandler(Handlers me) { //配置Handler //这里没有配置,JFinal.init()方法也会添加一个ActionHandler } }
在configRoute
实现中咱们使用了两种Routes.add()
方法,向Routes
添加了三个Controller
。jFinal
的路由是REST风格的,这里me.add("/api/user", UserController.class);
的意思大概是请求/api/user
时会交给UserController
来处理。具体地看下文JFinalFilter
的doFilter
方法小节。
这里抽象实现方法何时被调用具体看JFinalFilter
的init
方法小节。ui
在进入JFinalFilter
的init
和doFilter
方法以前,咱们将上面的提到的几个概念梳理一下。
Routes
是jFinal
的路由,有两个路由映射的容器,请求路径到Controller
的映射和请求路径到渲染页面的映射。Routes
在项目中是做为com.jfinal.core.Config
的成员变量出现的,负责维护jFinal
项目的路由映射。整个jFinal
项目只有一个com.jfinal.core.Config
,做为静态类能够保证它是惟一的,而它的静态成员也是整个项目中惟一的。routes
就是其中之一。Routes
提供了多个重载的add
方法,咱们来看看我使用到的其中两个。
/** * Add route * @param controllerKey A key can find controller * @param controllerClass Controller Class * @param viewPath View path for this Controller */ public Routes add(String controllerKey, Class<? extends Controller> controllerClass, String viewPath) { //不少不少的corner check //处理controllerKey的前缀,前缀加SLASH / //处理viewPath的前缀和后缀,都加上SLASH //若是viewPath的根路径baseViewPath不为空则在viewPath前拼接 map.put(controllerKey, controllerClass); viewPathMap.put(controllerKey, viewPath); return this;//为了链式写法 }
另一个
public Routes add(String controllerkey, Class<? extends Controller> controllerClass) { return add(controllerkey, controllerClass, controllerkey); }
其实调用了上面的方法的。
public Routes add(String controllerKey, Class<? extends Controller> controllerClass, String viewPath) { }
通常使用过程当中经过controllerKey
找到Controller
,这很是容易理解。而经过controllerKey
在viewPathMap
中找到viewPath
,这个是用渲染页面是使用的路径,例如:
请求/api/user/edit
执行成功后渲染到/api/user/edit.jsp
页面。
通常咱们定义controllery
为/api/user
,viewPath
为/api/user/
或者其余,而/edit
和edit.jsp
映射是约定好的。(但并非直接映射的。)
最终的结果咱们能够获得两个配置好的map
和viewPathMap
。
与Routes
同理,Interceptors
也做为com.jfinal.core.Config
的成员变量出现的,它自己是一个List
容器,记录的是项目的全部拦截器。在示例中com.app.common.Config
并无设置拦截器,在实现的configInterceptor
方法中并无作什么事情,若有须要咱们能够调用Interceptors
的add
方法添加全局的拦截器。
final public class Interceptors { private final List<Interceptor> interceptorList = new ArrayList<Interceptor>(); public Interceptors add(Interceptor globalInterceptor) { if (globalInterceptor != null) this.interceptorList.add(globalInterceptor); return this; } //... }
在com.jfinal.core.Config
有一个成员变量handlers
,记录的是项目全部的Handler
,能够向它添加Handler
。在示例中com.app.common.Config
实现的configHandler
方法中也没有作具体的配置。Handler
有一个成员变量nextHandler
指向下一个Handler
,这样能够用链表形式将全部的Handler
链接起来。Handler
链表的头节点最后保存在JFinal
的handler
变量,见JFinalFilter
的init方法小节。这里先提一下如何得到链表的头节点:在HandlerFacotry
中提供的getHandler方法传入原有的全部Handler
和一个新的Handler
,最终构造一条Handler
链,新的Handler
被添加到链表的尾部,最终返回头节点。
/** * Build handler chain */ public static Handler getHandler(List<Handler> handlerList, Handler actionHandler) { Handler result = actionHandler; for (int i=handlerList.size()-1; i>=0; i--) { Handler temp = handlerList.get(i); temp.nextHandler = result; result = temp; } return result; }
Handler
链的使用是在JFinalFilter
的doFilter
方法中,下文会说起。
ActionMapping
负责将Routes
和Interceptors
组织起来,整合后的结果存到在ActionMapping
的mapping
成员变量(Map<String, Action> mapping),Action
是最终用于处理HTTP请求的Action(不知道怎么翻译才恰当)。
具体过程则是,遍历Routes
全部Controller
、遍历Controller
全部method
,将类级别(Controller
)和方法(method
)级别对应的key
或者名字链接起来做为键actionKey
,将类级别(Controller
)和方法(method
)级别对应的Interceptor
整合计算后获得Action
的拦截器数组actionInters
。
最后用于实例化Action
须要的变量以下所示:
new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, routes.getViewPath(controllerKey));
具体的能够参照ActionMapping
的buildActionMapping
方法。
void buildActionMapping() { mapping.clear(); Set<String> excludedMethodName = buildExcludedMethodName(); InterceptorBuilder interceptorBuilder = new InterceptorBuilder(); Interceptor[] defaultInters = interceptors.getInterceptorArray(); interceptorBuilder.addToInterceptorsMap(defaultInters); for (Entry<String, Class<? extends Controller>> entry : routes.getEntrySet()) { Class<? extends Controller> controllerClass = entry.getValue(); Interceptor[] controllerInters = interceptorBuilder.buildControllerInterceptors(controllerClass); Method[] methods = controllerClass.getMethods(); for (Method method : methods) { String methodName = method.getName(); if (!excludedMethodName.contains(methodName) && method.getParameterTypes().length == 0) { Interceptor[] methodInters = interceptorBuilder.buildMethodInterceptors(method); Interceptor[] actionInters = interceptorBuilder.buildActionInterceptors(defaultInters, controllerInters, controllerClass, methodInters, method); String controllerKey = entry.getKey(); ActionKey ak = method.getAnnotation(ActionKey.class); if (ak != null) { String actionKey = ak.value().trim(); if ("".equals(actionKey)) throw new IllegalArgumentException(controllerClass.getName() + "." + methodName + "(): The argument of ActionKey can not be blank."); if (!actionKey.startsWith(SLASH)) actionKey = SLASH + actionKey; if (mapping.containsKey(actionKey)) { warnning(actionKey, controllerClass, method); continue; } Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, routes.getViewPath(controllerKey)); mapping.put(actionKey, action); } else if (methodName.equals("index")) { String actionKey = controllerKey; Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, routes.getViewPath(controllerKey)); action = mapping.put(actionKey, action); if (action != null) { warnning(action.getActionKey(), action.getControllerClass(), action.getMethod()); } } else { String actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName; if (mapping.containsKey(actionKey)) { warnning(actionKey, controllerClass, method); continue; } Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, routes.getViewPath(controllerKey)); mapping.put(actionKey, action); } } } } // support url = controllerKey + urlParas with "/" of controllerKey Action actoin = mapping.get("/"); if (actoin != null) mapping.put("", actoin); }
这个方法会是整篇文章提到的最复杂的方法,因此这里所有列出。
主要的逻辑是拼接${controlerKey}/methodName
做为actionKey
,${controllerKey}
相似/api/user
是咱们在JFinalConfig
实现类中添加的。actionKey
最后会和请求的URL比较,匹配时就返回其对应的Action
。拼接actionKey
的过程当中有两个须要注意的地方,一个是Controller
的方法不能有参数,一个是若是方法名是index
就将controllerKey
做为actionKey
,便是若是请求是/api/user
最终调用的是UserController.index()
。最后也作了请求是/
的支持。
另一个重要的是逻辑是整合计算Action
的最终的拦截器数组actionInters
。jFinal
提供了Before
注解的形式来在Controller
类级别和method
方法级别引入Interceptor
,还有ClearInterceptor
做为规则用于排除上层层次的Interceptor
。这些细节就不展开了。
2.4 ActionMapping
已经提到了Action
,这里提一下Action
是怎么调用的。咱们注意到实例化Action
时传入了不少参数。
new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, routes.getViewPath(controllerKey));
其中controllerClass
能够提供实例化一个Controller
,methodName
能够肯定调用Controller
的哪一个方法,actionInters
能够在调用Controller
方法前执行拦截过滤等,拦截过滤后再回到Action
去调用真正的methodName
方法。整个调用过程是ActionInvocation
封装完成的,具体细节就不展开了。
最后来看看两个重要的流程。直接上代码
public void init(FilterConfig filterConfig) throws ServletException { //实例化JFinalConfig实现类 createJFinalConfig(filterConfig.getInitParameter("configClass")); //配置初始化 //初始化Handler ActionMapping if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false) throw new RuntimeException("JFinal init error!"); //Handler链头节点 handler = jfinal.getHandler(); constants = Config.getConstants(); encoding = constants.getEncoding(); jfinalConfig.afterJFinalStart(); String contextPath = filterConfig.getServletContext().getContextPath(); contextPathLength = (contextPath == null || "/".equals(contextPath) ? 0 : contextPath.length()); }
createJFinalConfig
是JFinalFilter
内部方法,filterConfig.getInitParameter("configClass")
是从web.xml
得到配置的JFinalConfig
实现类,目的是实例化JFinalConfig
。
private void createJFinalConfig(String configClass) { if (configClass == null) throw new RuntimeException("Please set configClass parameter of JFinalFilter in web.xml"); try { Object temp = Class.forName(configClass).newInstance(); if (temp instanceof JFinalConfig) jfinalConfig = (JFinalConfig)temp; else throw new RuntimeException("Can not create instance of class: " + configClass + ". Please check the config in web.xml"); } catch (InstantiationException e) { throw new RuntimeException("Can not create instance of class: " + configClass, e); } catch (IllegalAccessException e) { throw new RuntimeException("Can not create instance of class: " + configClass, e); } catch (ClassNotFoundException e) { throw new RuntimeException("Class not found: " + configClass + ". Please config it in web.xml", e); } }
接下来是调用
jfinal.init(jfinalConfig, filterConfig.getServletContext())
这部分是在JFinal
类中完成的。
boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) { this.servletContext = servletContext; this.contextPath = servletContext.getContextPath(); initPathUtil(); //调用JFinalConfig实现类的configXXX方法 Config.configJFinal(jfinalConfig); // start plugin and init logger factory in this method constants = Config.getConstants(); //初始化actionMapping initActionMapping(); //新建一个ActionHandler而且构造一条Handler链并保存头节点 initHandler(); initRender(); initOreillyCos(); initI18n(); initTokenManager(); return true; }
这个方法中开始作整个项目的配置初始化,具体能够看Config.configJFinal(jfinalConfig)
的实现。
/* * Config order: constant, route, plugin, interceptor, handler */ static void configJFinal(JFinalConfig jfinalConfig) { jfinalConfig.configConstant(constants); initLoggerFactory(); jfinalConfig.configRoute(routes); jfinalConfig.configPlugin(plugins); startPlugins(); // very important!!! jfinalConfig.configInterceptor(interceptors); jfinalConfig.configHandler(handlers); }
基本就是调用JFinalConfig
的configXXX
,具体如何作能够参考前面Routes
、Interceptors
和Handler
小节。
接着来关注initActionMapping
部分逻辑。
private void initActionMapping() { actionMapping = new ActionMapping(Config.getRoutes(), Config.getInterceptors()); actionMapping.buildActionMapping(); }
基本就是调用ActionMapping
的buildActionMapping
方法了,buildActionMapping
能够参考前面ActionMapping
小节。
最后关注initHandler
部分逻辑。
private void initHandler() { Handler actionHandler = new ActionHandler(actionMapping, constants); handler = HandlerFactory.getHandler(Config.getHandlers().getHandlerList(), actionHandler); }
关于HandlerFactory
的使用能够参考Handler
小节。
执行完JFinalFilter
的init
就为整个项目的路由解析作好了准备了。
仍是直接上代码
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; request.setCharacterEncoding(encoding); //得到请求URL String target = request.getRequestURI(); if (contextPathLength != 0) //切掉上下文路径,contextPathLength是上下文路径的长度 target = target.substring(contextPathLength); boolean[] isHandled = {false}; try { //Handler链调用 handler.handle(target, request, response, isHandled); } catch (Exception e) { if (log.isErrorEnabled()) { String qs = request.getQueryString(); log.error(qs == null ? target : target + "?" + qs, e); } } if (isHandled[0] == false) chain.doFilter(request, response); }
这里的handler
是JFinal.initHanlder()
方法得到Handler
链的头节点,若是整个项目没有其余Handler
,头节点应该是一个ActionHandler
类型实例。
接下来看ActionHandler.handle
方法
/** * handle * 1: Action action = actionMapping.getAction(target) * 2: new ActionInvocation(...).invoke() * 3: render(...) */ public final void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) { if (target.indexOf(".") != -1) { return ; } isHandled[0] = true; String[] urlPara = {null}; 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 { Controller controller = action.getControllerClass().newInstance(); controller.init(request, response, urlPara[0]); if (devMode) { boolean isMultipartRequest = ActionReporter.reportCommonRequest(controller, action); new ActionInvocation(action, controller).invoke(); if (isMultipartRequest) ActionReporter.reportMultipartRequest(controller, action); } else { new ActionInvocation(action, controller).invoke(); } 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.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).render(); } catch (Exception e) { if (log.isErrorEnabled()) { String qs = request.getQueryString(); log.error(qs == null ? target : target + "?" + qs, e); } renderFactory.getErrorRender(500).setContext(request, response).render(); } }
render
部分暂且不看。
从做者的代码注释中能够看出这个handle
方法的主要逻辑。
咱们就看其中两个。
* 1: Action action = actionMapping.getAction(target) * 2: new ActionInvocation(...).invoke()
target
是减去了contextPath部分的请求路径,在ActionMapping.getAction(target)
方法中将与ActionMapping
维护的mapping
表中的全部actionKey
做比较,若是匹配就得到一个Action
。
看下实现代码
/** * Support four types of url * 1: http://abc.com/controllerKey ---> 00 * 2: http://abc.com/controllerKey/para ---> 01 * 3: http://abc.com/controllerKey/method ---> 10 * 4: http://abc.com/controllerKey/method/para ---> 11 */ Action getAction(String url, String[] urlPara) { Action action = mapping.get(url); if (action != null) { return action; } // -------- int i = url.lastIndexOf(SLASH); if (i != -1) { action = mapping.get(url.substring(0, i)); urlPara[0] = url.substring(i + 1); } return action; }
简单解释下,这个方法支持四种形式的请求,见注释。
首先尝试mapping.get(url)
,
若是结果不为空,结合前面ActionMapping.buildActionMapping()
,
咱们知道这时/controllerKey
或者/controllery/method
匹配到了。
进一步截取并尝试mapping.get(url.substring(0,i))
即将/controllerKey/para
和/controllerKey/method/para
减去/para
再执行匹配。para
用urlPara[0]
收集起来。
最后不论是否匹都配返回。
回到ActionHandler.handle()
方法,用得到的Action
进行调用处理请求。
new ActionInvocation(action, controller).invoke();
至此,jFinal
的路由解析模块就分析完了。
以上用于分析的jFinal版本是jfinal-1.8-bin-with-src.jar。
感谢转神提供的案例
感谢豪的提点和帮助
支持一下文章做者吧!