淺談JFinal源碼,短小精悍之道

前言

這篇文章的前提是對JFnial有了大體的瞭解,不熟悉的話能夠看一下官網文檔https://www.jfinal.com/doc,很是簡單的一個框架,回到原題,老大說有一個JFinal的項目之後有可能讓我維護,因而讓我熟悉一下JFinal的原理,看了JFinal的官網文檔和源碼才發現JFinal真是短小精悍,言簡意賅。由於以前只用過Spring的mvc和公司以前封裝的MVC,話很少說,上源碼...web

一.啓動

JFinal.start("src/main/resources", 9527, "/");
複製代碼

這時候JFinal就啓動了,指定端口,這時候會加載resources目錄下的WEB-INF/的web.xml文件,正式環境能夠本身封裝指定具體的文件,在這裏就不具體說了spring

<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的配置文件,這也是須要咱們本身去實現的。sql

二.配置文件

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);
}
複製代碼

繼續點進去json

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(在這裏就不詳細解析了)。bash

mapping.clear();
Set<String> excludedMethodName = buildExcludedMethodName();
InterceptorManager interMan = InterceptorManager.me();
複製代碼

遍歷routes.getRouteItemList(),就是咱們以前看到的將UserController封裝成Route讓後放入的list裏,route.getControllerClass()獲取咱們配置的Controller對象,這裏會獲取咱們配置的Controller方法,若是是Controller子類則獲取它聲明的方法,反之獲取全部的方法,而後遍歷。mvc

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指定名字app

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的弊端。

相關文章
相關標籤/搜索