1. 程式人生 > >JFinal個人學習筆記之原始碼分析3

JFinal個人學習筆記之原始碼分析3

上篇分析完了initActionMapping()的原始碼。JFinal原始碼裡初始化init方法還有:
boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) {
        this.servletContext = servletContext;
        this.contextPath = servletContext.getContextPath();

        initPathUtil();
    // start plugin and init log factory in
this method Config.configJFinal(jfinalConfig); constants = Config.getConstants(); initActionMapping(); initHandler(); initRender(); initOreillyCos(); initTokenManager(); return true; }
還有四個沒講;

initHandler()

這個方法的原始碼:
private void
initHandler() { Handler actionHandler = new ActionHandler(actionMapping, constants); handler = HandlerFactory.getHandler(Config.getHandlers().getHandlerList(), actionHandler); }
上面程式碼第一句是`new` 了一個ActionHandler物件;傳了兩個引數;
引數一:actionMapping,是在initActionMapping()方法中new出來的。
裡面都封裝了action與路由的對映
引數二:constants。裡面都封裝了DemoConfig配置與jfinal的預設配置資訊。

可以看出這個actionHandler,就是我們使用者請求訪問使用者頁面要走的action。
第二句程式碼:HandlerFactory.getHandler()方法是為了得到Handler鏈。原始碼:
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.next = result;
            temp.nextHandler = result;//這個在未來可能不會再使用,改用next。
            result = temp;
        }

        return result;
    }
從程式碼中可以看出,這個方法會把之前new出來的actionHandler,放到handler鏈的最後;
什麼意思呢!假設我們在DemoConfig.java中配置了我們自己的Handler,
它會按照程式碼的順序,依次組裝成handler鏈,而這條鏈的最後就是actingHandler。
為什麼要把actionHandler放到最後呢?
我認為,是因為我們自己定義的handler的對http沒有做路由處理。比如路徑stock/update
是期望訪問到stock類中update方法。這個在我們自定義的handler是做不到了,這個是jfinal自己來做的。
又由於這是請求的最後一步,待update處理完成後,就是響應請求啦,把結果返回給客戶端。
至此,initHandler()方法處理結束。

initRender()

render()方法是用來渲染檢視的方法。而要使用它,jfinal肯定是早就初始化好了的。
initRender()就是這樣一個方法。原始碼:
private void initRender() {
        RenderFactory.me().init(constants, servletContext);
}
這裡`RenderFactory.me()`就是拿到了,RenderFactory物件;
private static final RenderFactory me = new RenderFactory();

public static RenderFactory me() {
    return me;
}
RenderFactory中的init方法;
public void init(Constants constants, ServletContext servletContext) {
        this.constants = constants;
        this.servletContext = servletContext;

        // init Render
        Render.init(constants.getEncoding(), constants.getDevMode());
        initFreeMarkerRender(servletContext);
        initVelocityRender(servletContext);
        initJspRender(servletContext);
        initFileRender(servletContext);

        // create mainRenderFactory
        if (mainRenderFactory == null) {
            ViewType defaultViewType = constants.getViewType();
            if (defaultViewType == ViewType.FREE_MARKER) {
                mainRenderFactory = new FreeMarkerRenderFactory();
            } else if (defaultViewType == ViewType.JSP) {
                mainRenderFactory = new JspRenderFactory();
            } else if (defaultViewType == ViewType.VELOCITY) {
                mainRenderFactory = new VelocityRenderFactory();
            } else {
                throw new RuntimeException("View Type can not be null.");
            }
        }

        // create errorRenderFactory
        if (errorRenderFactory == null) {
            errorRenderFactory = new ErrorRenderFactory();
        }

        if (xmlRenderFactory == null) {
            xmlRenderFactory = new XmlRenderFactory();
        }
    }
這裡傳了兩個引數;
引數一:constants:這個是常量類的物件,裡面都是DemoConfig自定義與jfinal預設配置。
引數二:servletContext,這個是filter過濾器的上下文。

程式碼分析:`Render.init(constants.getEncoding(), constants.getDevMode());`
Render這是個抽象類。也就是這裡是用抽象類名Render去調裡面靜態方法init();
而這個init方法原始碼:
    private static String encoding = Const.DEFAULT_ENCODING;
    private static boolean devMode = Const.DEFAULT_DEV_MODE;

    static void init(String encoding, boolean devMode) {
        Render.encoding = encoding;
        Render.devMode = devMode;
    }
可以看出,就是設定了字元編碼和開發模式。如果你在自定義配置檔案中配置了這兩個引數,
它就是在這段程式碼裡使其生效,如果沒有就使用jfinal的預設值。預設值:utf-8、false.

我們再看到`initFreeMarkerRender(servletContext)`原始碼:
    private void initFreeMarkerRender(ServletContext servletContext) {
        try {
            Class.forName("freemarker.template.Template");  // detect freemarker.jar
            FreeMarkerRender.init(servletContext, Locale.getDefault(), constants.getFreeMarkerTemplateUpdateDelay());
        } catch (ClassNotFoundException e) {
            // System.out.println("freemarker can not be supported!");
            LogKit.logNothing(e);
        }
    }
這段程式碼主要就兩句被try 包裹著的就是!
第一句`Class.forName("freemarker.template.Template");`這句是為了尋找freemarker.jar。
要是沒有應該該jar包,將會報錯!
第二句
`FreeMarkerRender.init(servletContext, Locale.getDefault(), 
constants.getFreeMarkerTemplateUpdateDelay());`
這句是初始化freemarkerRender。
引數一:web的上下文:
ServletContext@o.e.j.w.WebAppContext{/,file:/E:/JFinal/eclipse/workspace/jfinal_demo/WebRoot/}
可以看出這裡面儲存的是專案根路徑
引數二:獲取本地預設的區域資訊。
引數三:用於生產模式下的(我猜應該是,當模板發生改變時,是否立即更新顯示出來)。
該方法(init)裡面還設定了:
①模板載入器。(setServletContextForTemplateLoading)
②模板異常攔截器(setTemplateExceptionHandler)
③指定模版如何檢視資料模型(setObjectWrapper)
④設定模板編碼(setDefaultEncoding)
⑤設定響應輸出頁面編碼格式(setOutputEncoding),這裡我覺得它是為了防止響應返回資料時,出現亂碼。
⑥設定本地時區,和設定了時間格式,int型輸出格式。去掉int型輸出時的逗號, 例如: 123,456。

我們現在看到:`initVelocityRender(servletContext)`。吐槽下:這一塊我打斷點,沒進入到裡面的原始碼。
它和上面initFreeMarkerRender方法是類似的。先是驗證是否引入了相應的jar包。
再把上下文傳進入進行初始化。裡面init()方法的原始碼:
static void init(ServletContext servletContext) {
    String webPath = servletContext.getRealPath("/");
    properties.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, webPath);
    properties.setProperty(Velocity.ENCODING_DEFAULT, getEncoding()); 
    properties.setProperty(Velocity.INPUT_ENCODING, getEncoding()); 
    properties.setProperty(Velocity.OUTPUT_ENCODING, getEncoding());
}
可以看出它設定了一些基本屬性,比如檔案路徑,預設編碼格式等等。至此結束。

我在再看看`initJspRender(servletContext);`方法。
也是和上面類似。原始碼裡主要就是三行程式碼:
    Class.forName("javax.el.ELResolver");
    Class.forName("javax.servlet.jsp.JspFactory");      com.jfinal.plugin.activerecord.ModelRecordElResolver.init(servletContext);
第一個`javax.el.ELResolver`,jvm去載入這個類,也起到驗證作用,要是沒有就報錯。
第二個同理。
第三個init(servletContext)方法的原始碼:
public synchronized static void init(ServletContext servletContext) {
       JspApplicationContext jac = JspFactory.getDefaultFactory().getJspApplicationContext(servletContext);
       if (jspApplicationContext != jac) {
            jspApplicationContext = jac;
            jspApplicationContext.addELResolver(new ModelRecordElResolver());
       }
    }
就把是注入增強的ModelRecordElResolver。

我們再看到:`initFileRender(servletContext);`方法,原始碼:
private void initFileRender(ServletContext servletContext) {
    String downloadPath = constants.getBaseDownloadPath();
    downloadPath = downloadPath.trim();
    downloadPath = downloadPath.replaceAll("\\\\", "/");

    String baseDownloadPath;
    // 如果為絕對路徑則直接使用,否則把 downloadPath 引數作為專案根路徑的相對路徑
    if (PathKit.isAbsolutelyPath(downloadPath)) {
        baseDownloadPath = downloadPath;
    } else {
        baseDownloadPath = PathKit.getWebRootPath() + File.separator + downloadPath;
    }

    // remove "/" postfix
    if (baseDownloadPath.equals("/") == false) {
        if (baseDownloadPath.endsWith("/")) {
            baseDownloadPath = baseDownloadPath.substring(0, baseDownloadPath.length() - 1);
        }
    }

    FileRender.init(baseDownloadPath, servletContext);
}
可以看出,這個是為檔案下載做準備的!
constants.getBaseDownloadPath();獲取下載路徑,預設是download
這裡的`downloadPath.replaceAll("\\\\", "/")`在java裡兩個\\表示一個\,四個就表示兩個\\。意思就是把路徑中\\替換成/,就行了。
接下來的兩個if判斷很好理解,我們來看`FileRender.init(baseDownloadPath, servletContext);`原始碼
static void init(String baseDownloadPath, ServletContext servletContext) {
    FileRender.baseDownloadPath = baseDownloadPath;
    FileRender.servletContext = servletContext;
}
這段程式碼很簡單,就是設定下載路徑,和web的上下文。
至此`initRender();`執行完畢。

initOreillyCos()

原始碼:
OreillyCos.init(constants.getBaseUploadPath(), constants.getMaxPostSize(), constants.getEncoding());
從這段程式碼裡可以判斷出,這個應該是設定檔案上傳的。
引數一:`constants.getBaseUploadPath()`獲取檔案上傳路徑
引數二:`constants.getMaxPostSize()`設定上傳檔案的大小預設是10M
引數三:`constants.getEncoding()`設定編碼格式預設UTF-8。
而init(...)原始碼:
Class.forName("com.oreilly.servlet.MultipartRequest");
doInit(uploadPath, maxPostSize, encoding);
可以看出程式碼也很簡單,先是去載入com.oreilly.servlet.MultipartRequest類,
其中這個MultipartRequest類是自定義的,並繼承了`HttpServletRequestWrapper`類。
這個`HttpServletRequestWrapper`類百度了下,是servlet中的。網上的說應用場景之一是:
統一字元編碼,在重寫的`getParameter`方法中進行處理就行了。

參考地址

我們再看到`doInit()`的方法。原始碼:
        uploadPath = uploadPath.trim();
        uploadPath = uploadPath.replaceAll("\\\\", "/");

        String baseUploadPath;
        if (PathKit.isAbsolutelyPath(uploadPath)) {
            baseUploadPath = uploadPath;
        } else {
            baseUploadPath = PathKit.getWebRootPath() + File.separator + uploadPath;
        }

        // remove "/" postfix
        if (baseUploadPath.equals("/") == false) {
            if (baseUploadPath.endsWith("/")) {
                baseUploadPath = baseUploadPath.substring(0, baseUploadPath.length() - 1);
            }
        }

        MultipartRequest.init(baseUploadPath, maxPostSize, encoding);
程式碼很簡單,主要是`MultipartRequest.init(baseUploadPath, maxPostSize, encoding);`方法初始化上傳檔案的引數(路徑、大小、編碼)。
至此`initOreillyCos()`執行完畢。

initTokenManager

顧名思義:初始化token。
原始碼
    private void initTokenManager() {
        ITokenCache tokenCache = constants.getTokenCache();
        if (tokenCache != null)
            TokenManager.init(tokenCache);
    }
這個預設是null,應該是在httpSession中會使用到。
至此。`jfinal.init(jfinalConfig, filterConfig.getServletContext())`方法執行完畢。
初始化成功後,會返回true。

我們現在回到JFina.java中的init方法中的`handler = jfinal.getHandler();`
這裡獲取到初始化好的handler。`constants = Config.getConstants();`
獲取到初始化好的constants.`constants.getEncoding()`獲取編碼。
由於DemoConfig沒有過載`jfinalConfig.afterJFinalStart();`這個方法,所以暫時不講。
接下來的程式碼:
String contextPath = filterConfig.getServletContext().getContextPath();
contextPathLength = (contextPath == null || "/".equals(contextPath) ? 0 : contextPath.length());
這兩行程式碼作用其實就是為了計算出專案名稱的長度。
第一句意思:獲取上下文的路徑。與request.getContextPath()類似。
也就是說,假設不要request等9大內建物件,還可以使用filter中方法來獲取上下文。
filterConfig.getServletContext().getContextPath();//獲取上下文

參考

第二句,計算上下文長度,上下文其實就是路徑。這個其實就是為了獲取專案名的路徑。
在jetty容器中獲取到的是""空字串,要是在tomcat等其他容器中,可能獲取的路徑是帶專案名的`/專案名`。這麼做是為了在`doFilter()`方法中的
        String target = request.getRequestURI();
        if (contextPathLength != 0)
            target = target.substring(contextPathLength);
做準備的,因為`request.getRequestURI()`可能會獲取到帶專案名的資源路徑,
再`target.substring(contextPathLength);`把專案名去除掉,這樣,就只剩下,資源路徑。
至此JFinal中的`init`方法執行完畢。接下來就是等待請求啦!