1. 程式人生 > >JFinal初始化過程淺析

JFinal初始化過程淺析

<span style="font-family: Arial, Helvetica, sans-serif;  background-color: rgb(255, 255, 255);"><strong>廢話</strong></span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">:</span>

前段時間粗略看了一下jfinal的原始碼,然後今天在群裡突然有人問jfinal的IOC實現原理是什麼,竟然沒有一點印象,丟人的節奏啊。然後一查,原來jfinal支援IOC要整合ioc外掛,自己並沒有實現IOC,意識到自己應該總結一下了,不然臉都丟完了。哈哈

可能是廢話

所有的容器都是從初始化開始的。所以看原始碼的初始化肯定也是從它的web.xml的那個filter鑽進去的。

<filter>
		<filter-name>jfinal</filter-name>
		<filter-class>com.jfinal.core.JFinalFilter</filter-class>
		<init-param>
			<param-name>configClass</param-name>
			<param-value>demo.DemoConfig</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>jfinal</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
這是jfinal在web.xml中的配置。在servlet容器初始化的時候會載入JFinalFilter,當然JFinalFilter肯定是實現了Servlet的Filter介面的。我們ctrl+滑鼠左鍵跟進去看一下JFinalFilter裡邊都幹了什麼。


上圖是JFinalFilter的屬性和方法列表。可以看到這幾個屬性Handler,encoding,JFinalConfig,Constants,JFinal。為什麼在初始化的Filter裡定義這幾個屬性呢,等一會兒我們自然會分析。我們先看JFinalFilter的init方法。

<span style="white-space: pre;">第一句</span>createJFinalConfig(filterConfig.getInitParameter("configClass"));
filterConfig.getInitParameter("configClass")
這個configClass是什麼呢?相信大家都看開發 文件的。這個就是我們在web.xml中配置的初始化引數,value是我們寫的繼承自JFinalConfig的類。

繼承JFinalConfig要實現以下方法,這些方法的作用我寫了註釋。

/**
	 * 此方法用來配置常量值,如devMode(開發模式),viewType(檢視型別)等
	 */
	@Override
	public void configConstant(Constants me) {
		me.setDevMode(true);
	}

	/**
	 * 此方法用來配置訪問路由
	 */
	@Override
	public void configRoute(Routes me) {
		me.add("/hello", HelloController.class);
		me.add(new FrontRoute());
	}

	/**
	 * 此方法用來配置plugin
	 */
	@Override
	public void configPlugin(Plugins me) {
		loadPropertyFile("jdbc.txt");
		C3p0Plugin c3p0Plugin = new C3p0Plugin(getProperty("url"),
				getProperty("username"),getProperty("password"));
		me.add(c3p0Plugin);
		ActiveRecordPlugin active = new ActiveRecordPlugin(c3p0Plugin);
		me.add(active);
		active.addMapping("user", User.class);
	}

	/**
	 * 此方法用來配置intercept,在此處配置的攔截器將會對所有的請求進行攔截,除非使用@ClearInterceptor在Controller裡清楚
	 */
	@Override
	public void configInterceptor(Interceptors me) {

	}

	/**
	 * 此方法用來配置Handle,handle可以接管所有web請求,並對應用擁有完全的控制權,可以很方便的實現更高層的功能性擴充套件
	 */
	@Override
	public void configHandler(Handlers me) {

	}

可以看到,這5個方法都是用來配置一些東西。很明顯,JFinal作為精小的框架,在初始化的時候配置的東西肯定是整個環境需要(如constants)的或者是處理一個請求所必須的(如路由,外掛,自定義全域性攔截器,自定義處理操作)

我們繼續回到createJFinalConfig(filterConfig.getInitParameter("configClass"));跟進去看它都做了什麼

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);
		}
	}
通過反射初始化一個JFinalConfig物件。

然後init方法中的第二句jfinal.init(jfinalConfig, filterConfig.getServletContext())

看一下JFinal物件的初始化都做了什麼

boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) {
		this.servletContext = servletContext;
		this.contextPath = servletContext.getContextPath();
		
		initPathUtil();
		
		Config.configJFinal(jfinalConfig);	// start plugin and init logger factory in this method
		constants = Config.getConstants();
		
		initActionMapping();
		initHandler();
		initRender();
		initOreillyCos();
		initI18n();
		initTokenManager();
		
		return true;
	}

第一、初始化 path,既初始化path工具類中的webRootPath(專案根路徑)

private void initPathUtil() {
		String path = servletContext.getRealPath("/");
		PathKit.setWebRootPath(path);
	}

 第二、一些基本配置

/*
	 * 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的那個類的方法了,configConstant就沒有什麼說了,就是載入配置檔案,然後讀取裡邊的值設定給Constant相應的屬性。

initLoggerFactory就是初始化日誌工廠,這裡可以自定義使用喜歡的日誌工具,如果沒有自定義的話則會預設使用Log4j。自定義我也不知道在哪兒自定義,不過在configConstant中加上這句程式碼Logger.setLoggerFactory((ILogerFactory) 初始化)應該就ok了吧。如果不多希望您指正一下

configRoute配置路由

配置路由主要是幹什麼用的呢?就是對映URL地址和controller中的具體的方法。看我們自己定義的DemoConfig中的me.add("/hello", HelloController.class);。跟進去往下看

public Routes add(String controllerkey, Class<? extends Controller> controllerClass) {
		return add(controllerkey, controllerClass, controllerkey);
	}
繼續往裡邊跟,程式碼太長就不貼了,主要就是判斷引數的合法性維護引數的正確性,然後將controllerKey作為鍵和controllerClass值放進一個map裡,將controllerKey作為鍵和處理後的controllerKey作為值放進一個map裡。有什麼用?我現在還不知道,不過肯定是後邊要用的。

configPlugin配置外掛,沒有什麼好說的將你需要的外掛統一放進一個list裡,然後就startPlugins()了。

configInterceptor和configHandle也沒有什麼,和plugin一樣將你自定義的interceptor和handle放進各自的list以備後邊使用。

接下來幾個動作才是重頭戲,程式碼如下

initActionMapping();
		initHandler();
		initRender();
		initActiveRecord();
		initOreillyCos();
		initI18n();
		initTokenManager();

我們一個一個看,

一、先是actionMapping

private void initActionMapping() {
		actionMapping = new ActionMapping(Config.getRoutes(), Config.getInterceptors());
		actionMapping.buildActionMapping();
	}
Config.getRoutes(),Config.getInterceptors()就是拿到我們剛剛在上邊說的那個configJfinal裡初始化的routes和interceptors。然後buildActionMapping()

跟進去老長一個方法了。哈哈不過做的事情還是很清晰的,我們看看它做了什麼吧
這個方法還真是蛋疼,看了半天才看懂。收回上邊說的那句話!分析程式碼吧!

mapping.clear();準備工作要做好,把箱子給我騰空了,我要裝東西了。buildExcludedMethodName();將不需要處理的方法給列出來。這裡要講一下,因為我們自己的controller是繼承自Controller類的,但是我們自己定義的interceptor只需要攔截自己的方法(url可能請求的資源)就ok了。但是通過反射怎麼過濾掉父類的方法呢?這裡好像有點問題哎,我再看看。。。



看上邊兩張圖片,第一個圖片中紅框內,拿到“不包括的方法名”。這個方法內容就是第二張圖片裡的,將所有引數長度為0的方法返回。再看第二個紅框:不包含在excludedMethodName中並且引數長度為0的進入if語句塊。這個判斷的最終目的也就是因為DemoController是繼承自Controller,而自定義Interceptor只需要加在DemoController的方法上,所以這裡過濾掉它繼承自Controller的方法。但是為什麼不直接拿到Controller的所有方法,if判斷裡做過濾呢?還要這麼繞。不知道是不是自己的理解不到位,大家給指正一下。

扯了這麼多繼續說程式碼吧 。這兩句是重要的

Interceptor[] methodInters = interceptorBuilder.buildMethodInterceptors(method);
					Interceptor[] actionInters = interceptorBuilder.buildActionInterceptors(defaultInters, controllerInters, controllerClass, methodInters, method);
					

拿到為該方法定義的interceptor,將系統預設的、全域性的(action)、為該方法定義的interceptor合在一起。接下來就是判斷路由,例項化action,放進路由和action對映的map裡。

二、initHandler

private void initHandler() {
		Handler actionHandler = new ActionHandler(actionMapping, constants);
		handler = HandlerFactory.getHandler(Config.getHandlers().getHandlerList(), actionHandler);
	}

將actionMapping放進Handler中,然後好處理它,哈哈。看看HandlerFactory的getHandler方法中都做了什麼
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一個一個的加到系統定義的Handler後邊,對,就是這麼簡單。

下邊的步驟先大致說一下是幹什麼的吧,寫部落格還是有點累。下次再把下面幾個步驟分析一下,然後再分析一下一次請求所做的操作。

三、initRender

這裡初始化檢視型別

四、初始化上傳元件

五、國際化

六、初始化令牌管理