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。
感謝轉神提供的案例
感謝豪的提點和幫助
支持一下文章做者吧!