前言
這篇文章的前提是對JFnial有了大致的瞭解,不熟悉的話可以看一下官網文件https://www.jfinal.com/doc,非常簡單的一個框架,回到原題,老大說有一個JFinal的專案以後有可能讓我維護,於是讓我熟悉一下JFinal的原理,看了JFinal的官網文件和原始碼才發現JFinal真是短小精悍,言簡意賅。因為之前只用過Spring的mvc和公司之前封裝的MVC,話不多說,上原始碼...
一.啟動
JFinal.start("src/main/resources", 9527, "/");
複製程式碼
這時候JFinal就啟動了,指定埠,這時候會載入resources目錄下的WEB-INF/的web.xml檔案,正式環境可以自己封裝指定具體的檔案,在這裡就不具體說了
<filter>
<filter-name>jfinal</filter-name>
<filter-class>com.jfinal.core.JFinalFilter</filter-class>
<init-param>
<param-name>configClass</param-name>
<param-value>com.jf.Config</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jfinal</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
複製程式碼
可以看到JFinal是使用過濾器的原理攔截所有請求這也是和SpringMVC的不同之一,同樣可以看到我們需要制定init-param,這相當於JFinal的配置檔案,這也是需要我們自己去實現的。
二.配置檔案
import com.jfinal.config.*;
import com.jfinal.template.Engine;
public class Config extends JFinalConfig {
@Override
public void configConstant(Constants me) {
//配置全域性常量
}
@Override
public void configRoute(Routes me) {
//配置路由,所有的Controller需要在這裡配置,這也是我們今天所講的主題
me.add("/user", UserController.class);
}
@Override
public void configEngine(Engine me) {
//配置模板
}
@Override
public void configPlugin(Plugins me) {
//配置外掛
//druid 資料庫連線池外掛
DruidPlugin druidPlugin = new DruidPlugin("url", "username", "password");
me.add(druidPlugin);
//配置ActiveRecord外掛
ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
_MappingKit.mapping(arp);//這裡是將資料庫裡的實體類指定連線池(_MappingKit這裡是自動生成的實體類所對應對映類)
me.add(arp);
}
@Override
public void configInterceptor(Interceptors me) {
//這裡配置攔截器
}
@Override
public void configHandler(Handlers me) {
//這裡是配置自定義handler,因為JFnial是鏈式呼叫,所以允許我們自定義handler(Handler是選擇並呼叫Controller,後面會講)
}
}
複製程式碼
三.路由配置
public void configRoute(Routes me) {
//配置路由,所有的Controller需要在這裡配置,這也是我們今天所講的主題
me.add("/user", UserController.class);
}
複製程式碼
我們點開me.add方法看下怎麼封裝的
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) {
routeItemList.add(new Route(controllerKey, controllerClass, viewPath));
return this;
}
複製程式碼
可以看到controllerKey和viewPath就是我們之前配置的"/user"路徑,controllerClass就是我們傳入的UserController.class物件,這裡封裝成Route物件並集合在routeItemList集合裡,好,我們看一下routeItemList會在什麼時候被呼叫。
protected void buildActionMapping() {
mapping.clear();
Set<String> excludedMethodName = buildExcludedMethodName();
InterceptorManager interMan = InterceptorManager.me();
for (Routes routes : getRoutesList()) {
for (Route route : routes.getRouteItemList()) {
Class<? extends Controller> controllerClass = route.getControllerClass();
Interceptor[] controllerInters = interMan.createControllerInterceptor(controllerClass);
boolean sonOfController = (controllerClass.getSuperclass() == Controller.class);
Method[] methods = (sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods());
for (Method method : methods) {
String methodName = method.getName();
if (excludedMethodName.contains(methodName) || method.getParameterTypes().length != 0)
continue ;
if (sonOfController && !Modifier.isPublic(method.getModifiers()))
continue ;
Interceptor[] actionInters = interMan.buildControllerActionInterceptor(routes.getInterceptors(), controllerInters, controllerClass, method);
String controllerKey = route.getControllerKey();
ActionKey ak = method.getAnnotation(ActionKey.class);
String actionKey;
if (ak != null) {
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;
}
else if (methodName.equals("index")) {
actionKey = controllerKey;
}
else {
actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName;
}
Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, route.getFinalViewPath(routes.getBaseViewPath()));
if (mapping.put(actionKey, action) != null) {
throw new RuntimeException(buildMsg(actionKey, controllerClass, method));
}
}
}
}
routes.clear();
// support url = controllerKey + urlParas with "/" of controllerKey
Action action = mapping.get("/");
if (action != null) {
mapping.put("", action);
}
}
複製程式碼
在ActionMapping我們找到了routes.getRouteItemList()方法,這個封裝的方法有點長,我們慢慢來講. 首先將mapping物件清空,找到excludedMethodName不需要執行的方法(這裡是Controller的方法,我們不需要對它的方法進行快取,因為我們需要快取的是它子類的方法),這裡會找到所配置的攔截器管理InterceptorManager(在這裡就不詳細解析了)。
mapping.clear();
Set<String> excludedMethodName = buildExcludedMethodName();
InterceptorManager interMan = InterceptorManager.me();
複製程式碼
遍歷routes.getRouteItemList(),就是我們之前看到的將UserController封裝成Route讓後放入的list裡,route.getControllerClass()獲取我們配置的Controller物件,這裡會獲取我們配置的Controller方法,如果是Controller子類則獲取它宣告的方法,反之獲取所有的方法,然後遍歷。
Class<? extends Controller> controllerClass = route.getControllerClass();
Interceptor[] controllerInters = interMan.createControllerInterceptor(controllerClass);
boolean sonOfController = (controllerClass.getSuperclass() == Controller.class);
Method[] methods = (sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods());
複製程式碼
下面這段程式碼做的就是匹配攔截器和將方法的key提取出來,預設的是我們之前傳入的controllerKey加methodName方法的名字或者可以用註解ActionKey指定名字
String methodName = method.getName();
if (excludedMethodName.contains(methodName) || method.getParameterTypes().length != 0)
continue ;
if (sonOfController && !Modifier.isPublic(method.getModifiers()))
continue ;
Interceptor[] actionInters = interMan.buildControllerActionInterceptor(routes.getInterceptors(), controllerInters, controllerClass, method);
String controllerKey = route.getControllerKey();
ActionKey ak = method.getAnnotation(ActionKey.class);
String actionKey;
if (ak != null) {
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;
}
else if (methodName.equals("index")) {
actionKey = controllerKey;
}
else {
actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName;
}
複製程式碼
最後封裝成Action物件,並放入map裡。
Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, route.getFinalViewPath(routes.getBaseViewPath()));
if (mapping.put(actionKey, action) != null) {
throw new RuntimeException(buildMsg(actionKey, controllerClass, method));
}
複製程式碼
這裡做的事情其實就是將Controller的方法封裝Action物件一個map裡,這裡的程式碼先告一段落,也許到了這裡大家會有一些思路了,和springmvc類似,springmvc使用註解將方法注入進去。
四.JFinalFilter過濾請求
相信大家對filter都不陌生了,這裡我們直接進入handler.handle方法,這裡預設的實現是ActionHandler,我們直接進入
handler.handle(target, request, response, isHandled);
複製程式碼
進入handle方法,我們又看到了一個熟悉的物件actionMapping,我們根據請求的url獲取到Action(封裝了我們請求的方法),
Action action = actionMapping.getAction(target, urlPara);
複製程式碼
我們可以看到,我們得到了Controller 的class物件,點進去getController方法可以看到controllerClass.newInstance(),也就是說我們每次請求都會建立一個Controller物件,這也就是說Controller物件必須是無參構造,init對Controller進行初始化,將request, response對Controller進行繫結。
Controller controller = null;
// Controller controller = action.getControllerClass().newInstance();
controller = controllerFactory.getController(action.getControllerClass());
controller.init(action, request, response, urlPara[0]);
複製程式碼
下面這個Controller例子可以看到,getRequest()可以獲得request物件,並從request物件讀取資訊,這也就解釋了Controller為什麼方法都是沒有引數的,同理返回資料也是通過我們傳入的response物件。
public class UserController extends Controller {
public void getById() {
String param = HttpKit.readData(getRequest());
JSONObject json = JSON.parseObject(param);
Integer id = json.getInteger("id");
User user = getUserById(id);//這裡不做業務處理了
renderJson(user);
}
private User getUserById(Integer id) {
return new User();
}
}
複製程式碼
下面就會執行Controller裡我們指定的Method方法了new Invocation(action, controller).invoke();點進去可以看到會先執行過濾器,過濾器執行完才會執行Controller方法,最後可以看到Render render = controller.getRender(),我們得到Render物件,然後對請求是進行轉發還是給客戶端返回資料。
五.淺談
首先ActionHandler是鏈式呼叫的,我們可以自己實現一個ActionHandler,這也是一個擴充套件的方向。我們發現我們每次的請求都是建立一個新的Controller物件,並繫結request和response物件,這裡我也是看了其他大佬的部落格,這裡這麼做是典型的空間換取時間,這樣會加快每次的請求速度,但同時會佔用較大的記憶體。其實這裡還有一個有意思的模組是ActiveRecordPlugin,大家有興趣的話可以自己看看,非常簡單易懂,操作起來也非常簡單,但同時對於複雜的sql不太友好,這也是ActiveRecordPlugin的弊端。