jFinal路由解析源码分析
2018-01-29 22:00
429 查看
转自https://segmentfault.com/a/1190000004141126
关键词:
分析
Web MVC框架不同的是
上面的
在本文中会关注
在
对外提供了多个重载的
关于
在
这里抽象实现方法什么时候被调用具体看
在进入
另外一个
其实调用了上面的方法的。
一般使用过程中通过
请求
一般我们定义
最终的结果我们可以得到两个配置好的
与
在
Action> mapping),
具体过程则是,遍历
最后用于实例化
具体的可以参照
这个方法会是整篇文章提到的最复杂的方法,所以这里全部列出。
主要的逻辑是拼接
另外一个重要的是逻辑是整合计算
2.4
其中
最后来看看两个重要的流程。直接上代码
接下来是调用
这部分是在
这个方法中开始做整个项目的配置初始化,具体可以看
基本就是调用
接着来关注
基本就是调用
最后关注
关于
执行完
还是直接上代码
这里的
接下来看
从作者的代码注释中可以看出这个
我们就看其中两个。
看下实现代码
简单解释下,这个方法支持四种形式的请求,见注释。
首先尝试
如果结果不为空,结合前面
我们知道这时
进一步截取并尝试
即将
最后不管是否匹都配返回。
回到
至此,
以上用于分析的jFinal版本是jfinal-1.8-bin-with-src.jar。
感谢转神提供的案例
感谢豪的提点和帮助
支持一下文章作者吧!
2015年12月13日发布
jFinal的路由解析是在
JFinalFilter中做的,这个Filter也需要在
web.xml中配置。
JFinalFilter实现了
javax.servlet.Filter接口,从这里也可以看出
jFinal是基于
Servlet的。
JFinalFilter在初始化时负责初始化
jFinal项目的配置(com.jfinal.core.Config)、路由表(Route)、映射表(ActionMapping)等;路由解析是在
JFinalFilter的
dofilter方法完成的。
关键词:
Route
Handler
Action
ActionMapping
1. 项目配置
分析jFinal的路由解析逻辑必须从
jFinal的一般项目配置入手,配置的作用是为路由解析提供支持的。和一般Java
Web MVC框架不同的是
jFinal没有采用xml配置的形式,但不是不需要配置,还是需要提供一个
JFinalConfig的继承实现类,实现
configXXX方法来支持配置初始化,初始化的入口是
JFinalFilter的
init方法。
1.1 web.xml
jFinal工程同样需要web.xml配置文件,但是较其他MVC框架的web.xml文件内容或许要简单许多,除了配置
welcome-file-list,只需要配置一个
filter。
<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();被实例化。
1.2 JFinalConfig
上面的configClass参数的值
com.app.common.Config是项目定义的
JFinalConfig的实现类,虽然整个项目没有xml配置,但是这里就是,只不过是Java代码的形式。
JFinalConfig只是暴露接口,配置信息最终保存在
jFinal的静态类
com.jfinal.core.Config中。
com.jfinal.core.Config类设计成不可以实例化,它定义的私有静态成员变量可以保证唯一。
JFinalConfig实现的接口就负责填充
com.jfinal.core.Config成员变量。
在本文中会关注
JFinalConfig的以下接口方法:
/** * 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);
1.3 com.jfinal.core.Config
在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定义了两个容器,
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的抽象方法实现。
1.4 JFinalConfig抽象方法实现
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方法小节。
2 路由和拦截器及Handler链
在进入JFinalFilter的
init和
doFilter方法之前,我们将上面的提到的几个概念梳理一下。
2.1 Routes
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。
2.2 Interceptors
与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; } //... }
2.3 Handler
在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方法中,下文会提及。
2.4 ActionMapping
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.5 Action
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封装完成的,具体细节就不展开了。
3 JFinalFilter init
最后来看看两个重要的流程。直接上代码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就为整个项目的路由解析做好了准备了。
4 JFinalFilter doFilter
还是直接上代码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的路由解析模块就分析完了。
5 Thanks
以上用于分析的jFinal版本是jfinal-1.8-bin-with-src.jar。感谢转神提供的案例
感谢豪的提点和帮助
支持一下文章作者吧!
2015年12月13日发布
相关文章推荐
- 分布式消息队列RocketMQ源码分析之1 -- Topic路由数据结构解析 -- topicRoute与topicPublishInfo与queueId
- 分布式消息队列RocketMQ源码分析之1 -- Topic路由数据结构解析 -- topicRoute与topicPublishInfo与queueId
- 分布式消息队列RocketMQ源码分析之1 -- Topic路由数据结构解析 -- topicRoute与topicPublishInfo与queueId
- jQuery 2.0.3 源码分析Sizzle引擎解析原理
- EventBus源码分析(二):编译库源码解析
- 源码分析: Android AsyncTask完全解析
- Spring源码分析--Ioc容器定位解析资源文件并注册BeanDefinition
- JFinal源码解析一:JFinal是做什么用的,有什么特点
- Redis源码分析系列二十四: 7 set---setCommand解析
- MyBatis 源码分析 - 配置文件解析过程
- HashMap、HashTable、TreeMap 深入分析及源码解析
- android 解析未安装apk中的AndroidManifest.xml以及系统源码分析
- Mybatis3源码分析(三):解析mapper的xml配置文件
- JFinal源码解析与思想理解
- alibaba fastjson(json序列化器)序列化部分源码解析-1-总体分析
- 分析spring源码第五(三)篇:Spring中Bean的解析、加载、创建 过程总结
- 数据库分库分表中间件 Sharding-JDBC 源码分析 —— SQL 解析(六)之删除SQL
- android播放器(music player)源码分析3(页面解析,ArrayListCursor)
- 数据库分库分表中间件 Sharding-JDBC 源码分析 —— SQL 路由(一)分库分表配置
- Android源码分析--Audio设备文件解析(07)