1. 程式人生 > >Jfinal源碼分析-Render系列方法設計模式

Jfinal源碼分析-Render系列方法設計模式

info 工廠 max otherwise dep instance email 改變 ajax

在學習Jfinal的Render系列方法的設計模式之前,有必要熟悉傳統的簡單工廠模式、工廠模式以及抽象工廠模式
Jfinal的Render系列方法中綜合了三種工廠的優點,保證了充分的可擴展性。

技術分享圖片
當然上圖還省略了其他系列的Render類,如:JsonRender、TextRender、ErrorRender、FileRender、RedirectRender、Redirect301Render、NullRender、JavascriptRender、HtmlRender、XmlRender、QrCodeRender

配置型+抽象工廠:Constants的抽象工廠

Constants中的常量設置代碼:

/**
 * The constant for JFinal runtime.
 */
final public class Constants {
    
    private boolean devMode = Const.DEFAULT_DEV_MODE;
    
    private String baseUploadPath = Const.DEFAULT_BASE_UPLOAD_PATH;
    private String baseDownloadPath = Const.DEFAULT_BASE_DOWNLOAD_PATH;
    
    private String encoding = Const.DEFAULT_ENCODING;
    private String urlParaSeparator = Const.DEFAULT_URL_PARA_SEPARATOR;
    private ViewType viewType = Const.DEFAULT_VIEW_TYPE;
    private String viewExtension = Const.DEFAULT_VIEW_EXTENSION;
    private int maxPostSize = Const.DEFAULT_MAX_POST_SIZE;
    private int freeMarkerTemplateUpdateDelay = Const.DEFAULT_FREEMARKER_TEMPLATE_UPDATE_DELAY; // just for not devMode
    
    private ControllerFactory controllerFactory = Const.DEFAULT_CONTROLLER_FACTORY;
    private int configPluginOrder = Const.DEFAULT_CONFIG_PLUGIN_ORDER;
    
    private boolean injectDependency = Const.DEFAULT_INJECT_DEPENDENCY;
    
    private ITokenCache tokenCache = null;
    
    /**
     * Set development mode.
     * @param devMode the development mode
     */
    public void setDevMode(boolean devMode) {
        this.devMode = devMode;
    }
    
    public boolean getDevMode() {
        return devMode;
    }
    
    /**
     * 配置 configPlugin(Plugins me) 在 JFinalConfig 中被調用的次序.
     * 
     * 取值 1、2、3、4、5 分別表示在 configConstant(..)、configRoute(..)、
     * configEngine(..)、configInterceptor(..)、configHandler(...)
     * 之後被調用
     * 
     * 默認值為 2,那麽 configPlugin(..) 將在 configRoute(...) 調用之後被調用
     * @param 取值只能是 1、2、3、4、5
     */
    public void setConfigPluginOrder(int configPluginOrder) {
        if (configPluginOrder < 1 || configPluginOrder > 5) {
            throw new IllegalArgumentException("configPluginOrder 只能取值為:1、2、3、4、5");
        }
        this.configPluginOrder = configPluginOrder;
    }
    
    public int getConfigPluginOrder() {
        return configPluginOrder;
    }
    
    /**
     * Set the renderFactory
     */
    public void setRenderFactory(IRenderFactory renderFactory) {
        if (renderFactory == null) {
            throw new IllegalArgumentException("renderFactory can not be null.");
        }
        RenderManager.me().setRenderFactory(renderFactory);
    }
    
    /**
     * 設置 Json 轉換工廠實現類,目前支持:JFinalJsonFactory(默認)、JacksonFactory、FastJsonFactory
     * 分別支持 JFinalJson、Jackson、FastJson
     */
    public void setJsonFactory(IJsonFactory jsonFactory) {
        if (jsonFactory == null) {
            throw new IllegalArgumentException("jsonFactory can not be null.");
        }
        JsonManager.me().setDefaultJsonFactory(jsonFactory);
    }
    
    /**
     * 設置json轉換時日期格式,常用格式有:"yyyy-MM-dd HH:mm:ss"、 "yyyy-MM-dd"
     */
    public void setJsonDatePattern(String datePattern) {
        if (StrKit.isBlank(datePattern)) {
            throw new IllegalArgumentException("datePattern can not be blank.");
        }
        JsonManager.me().setDefaultDatePattern(datePattern);
    }
    
    public void setCaptchaCache(ICaptchaCache captchaCache) {
        CaptchaManager.me().setCaptchaCache(captchaCache);
    }
    
    public void setLogFactory(ILogFactory logFactory) {
        if (logFactory == null) {
            throw new IllegalArgumentException("logFactory can not be null.");
        }
        LogManager.me().setDefaultLogFactory(logFactory);
    }
    
    /**
     * Set encoding. The default encoding is UTF-8.
     * @param encoding the encoding
     */
    public void setEncoding(String encoding) {
        if (StrKit.isBlank(encoding)) {
            throw new IllegalArgumentException("encoding can not be blank.");
        }
        this.encoding = encoding;
    }
    
    public String getEncoding() {
        return encoding;
    }
    
    /**
     * 設置自定義的 ControllerFactory 用於創建 Controller 對象
     */
    public void setControllerFactory(ControllerFactory controllerFactory) {
        if (controllerFactory == null) {
            throw new IllegalArgumentException("controllerFactory can not be null.");
        }
        this.controllerFactory = controllerFactory;
    }
    
    public ControllerFactory getControllerFactory() {
        return controllerFactory;
    }
    
    /**
     * 設置對 Controller、Interceptor 進行依賴註入,默認值為 false
     * 
     * 被註入對象默認為 singleton,可以通過 Aop.setSingleton(boolean) 配置
     * 該默認值。
     * 
     * 也可通過在被註入的目標類上使用 Singleton 註解覆蓋上述默認值,註解配置
     * 優先級高於默認配置
     */
    public void setInjectDependency(boolean injectDependency) {
        this.injectDependency = injectDependency;
        InterceptorManager.me().setInjectDependency(injectDependency);
    }
    
    public boolean getInjectDependency() {
        return injectDependency;
    }
    
    /**
     * Set ITokenCache implementation otherwise JFinal will use the HttpSesion to hold the token.
     * @param tokenCache the token cache
     */
    public void setTokenCache(ITokenCache tokenCache) {
        this.tokenCache = tokenCache;
    }
    
    public ITokenCache getTokenCache() {
        return tokenCache;
    }
    
    public String getUrlParaSeparator() {
        return urlParaSeparator;
    }
    
    public ViewType getViewType() {
        return viewType;
    }
    
    /**
     * Set view type. The default value is ViewType.JFINAL_TEMPLATE
     * Controller.render(String view) will use the view type to render the view.
     * @param viewType the view type 
     */
    public void setViewType(ViewType viewType) {
        if (viewType == null) {
            throw new IllegalArgumentException("viewType can not be null");
        }
        this.viewType = viewType;
    }
    
    /**
     * Set urlPara separator. The default value is "-"
     * @param urlParaSeparator the urlPara separator
     */
    public void setUrlParaSeparator(String urlParaSeparator) {
        if (StrKit.isBlank(urlParaSeparator) || urlParaSeparator.contains("/")) {
            throw new IllegalArgumentException("urlParaSepartor can not be blank and can not contains \"/\"");
        }
        this.urlParaSeparator = urlParaSeparator;
    }
    
    public String getViewExtension() {
        return viewExtension;
    }
    
    /**
     * Set view extension for the IRenderFactory.getDefaultRender(...)
     * The default value is ".html"
     * 
     * Example: ".html" or ".ftl"
     * @param viewExtension the extension of the view, it must start with dot char "."
     */
    public void setViewExtension(String viewExtension) {
        this.viewExtension = viewExtension.startsWith(".") ? viewExtension : "." + viewExtension;
    }
    
    /**
     * Set error 404 view.
     * @param error404View the error 404 view
     */
    public void setError404View(String error404View) {
        errorViewMapping.put(404, error404View);
    }
    
    /**
     * Set error 500 view.
     * @param error500View the error 500 view
     */
    public void setError500View(String error500View) {
        errorViewMapping.put(500, error500View);
    }
    
    /**
     * Set error 401 view.
     * @param error401View the error 401 view
     */
    public void setError401View(String error401View) {
        errorViewMapping.put(401, error401View);
    }
    
    /**
     * Set error 403 view.
     * @param error403View the error 403 view
     */
    public void setError403View(String error403View) {
        errorViewMapping.put(403, error403View);
    }
    
    private Map<Integer, String> errorViewMapping = new HashMap<Integer, String>();
    
    public void setErrorView(int errorCode, String errorView) {
        errorViewMapping.put(errorCode, errorView);
    }
    
    public String getErrorView(int errorCode) {
        return errorViewMapping.get(errorCode);
    }
    
    public String getBaseDownloadPath() {
        return baseDownloadPath;
    }
    
    /**
     * Set file base download path for Controller.renderFile(...)
     * 設置文件下載基礎路徑,當路徑以 "/" 打頭或是以 windows 磁盤盤符打頭,
     * 則將路徑設置為絕對路徑,否則路徑將是以應用根路徑為基礎的相對路徑
     * <pre>
     * 例如:
     * 1:參數 "/var/www/download" 為絕對路徑,下載文件存放在此路徑之下
     * 2:參數 "download" 為相對路徑,下載文件存放在 PathKit.getWebRoot() + "/download" 路徑之下
     * </pre>
     */
    public void setBaseDownloadPath(String baseDownloadPath) {
        if (StrKit.isBlank(baseDownloadPath)) {
            throw new IllegalArgumentException("baseDownloadPath can not be blank.");
        }
        this.baseDownloadPath = baseDownloadPath;
    }
    
    /**
     * Set file base upload path.
     * 設置文件上傳保存基礎路徑,當路徑以 "/" 打頭或是以 windows 磁盤盤符打頭,
     * 則將路徑設置為絕對路徑,否則路徑將是以應用根路徑為基礎的相對路徑
     * <pre>
     * 例如:
     * 1:參數 "/var/www/upload" 為絕對路徑,上傳文件將保存到此路徑之下
     * 2:參數 "upload" 為相對路徑,上傳文件將保存到 PathKit.getWebRoot() + "/upload" 路徑之下
     * </pre>
     */
    public void setBaseUploadPath(String baseUploadPath) {
        if (StrKit.isBlank(baseUploadPath)) {
            throw new IllegalArgumentException("baseUploadPath can not be blank.");
        }
        this.baseUploadPath = baseUploadPath;
    }
    
    public String getBaseUploadPath() {
        return baseUploadPath;
    }
    
    public int getMaxPostSize() {
        return maxPostSize;
    }
    
    /**
     * Set max size of http post. The upload file size depend on this value.
     */
    public void setMaxPostSize(int maxPostSize) {
        this.maxPostSize = maxPostSize;
    }
    
    /**
     * Set default base name to load Resource bundle.
     * The default value is "i18n".<tr>
     * Example:
     * setI18nDefaultBaseName("i18n");
     */
    public void setI18nDefaultBaseName(String defaultBaseName) {
        I18n.setDefaultBaseName(defaultBaseName);
    }
    
    /**
     * Set default locale to load Resource bundle.
     * The locale string like this: "zh_CN" "en_US".<br>
     * Example:
     * setI18nDefaultLocale("zh_CN");
     */
    public void setI18nDefaultLocale(String defaultLocale) {
        I18n.setDefaultLocale(defaultLocale);
    }
    
    /**
     * 設置 devMode 之下的 action report 是否在 invocation 之後,默認值為 true
     */
    public void setReportAfterInvocation(boolean reportAfterInvocation) {
        ActionReporter.setReportAfterInvocation(reportAfterInvocation);
    }
    
    /**
     * FreeMarker template update delay for not devMode.
     */
    public void setFreeMarkerTemplateUpdateDelay(int delayInSeconds) {
        if (delayInSeconds < 0) {
            throw new IllegalArgumentException("template_update_delay must more than -1.");
        }
        this.freeMarkerTemplateUpdateDelay = delayInSeconds;
    }
    
    public int getFreeMarkerTemplateUpdateDelay() {
        return freeMarkerTemplateUpdateDelay;
    }
}

RenderFactory是個簡單工廠

    public void init(Engine engine, Constants constants, ServletContext servletContext) {
        this.engine = engine;
        this.constants = constants;
        this.servletContext = servletContext;
        
        // create mainRenderFactory
        switch (constants.getViewType()) {
        case JFINAL_TEMPLATE:
            mainRenderFactory = new MainRenderFactory();
            break ;
        case FREE_MARKER:
            mainRenderFactory = new FreeMarkerRenderFactory();
            break ;
        case JSP:
            mainRenderFactory = new JspRenderFactory();
            break ;
        case VELOCITY:
            mainRenderFactory = new VelocityRenderFactory();
            break ;
        }
    }

抽象的工廠:

public abstract class Render {
    
    protected String view;
    protected HttpServletRequest request;
    protected HttpServletResponse response;
    
    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;
    }
    
    public static String getEncoding() {
        return encoding;
    }
    
    public static boolean getDevMode() {
        return devMode;
    }
    
    public Render setContext(HttpServletRequest request, HttpServletResponse response) {
        this.request = request;
        this.response = response;
        return this;
    }
    
    public Render setContext(HttpServletRequest request, HttpServletResponse response, String viewPath) {
        this.request = request;
        this.response = response;
        if (view != null && view.length() > 0 && view.charAt(0) != ‘/‘) {
            view = viewPath + view;
        }
        return this;
    }
    
    public String getView() {
        return view;
    }
    
    public void setView(String view) {
        this.view = view;
    }
    
    /**
     * Render to client
     */
    public abstract void render();
}

具體系列產品的實現(以TemplateRender為例):

/**
 * TemplateRender
 */
public class TemplateRender extends Render {
    
    protected static Engine engine;
    
    private static final String contentType = "text/html; charset=" + getEncoding();
    
    static void init(Engine engine) {
        if (engine == null) {
            throw new IllegalArgumentException("engine can not be null");
        }
        TemplateRender.engine = engine;
    }
    
    public TemplateRender(String view) {
        this.view = view;
    }
    
    public String getContentType() {
        return contentType;
    }
    
    public void render() {
        response.setContentType(getContentType());
        
        Map<Object, Object> data = new HashMap<Object, Object>();
        for (Enumeration<String> attrs=request.getAttributeNames(); attrs.hasMoreElements();) {
            String attrName = attrs.nextElement();
            data.put(attrName, request.getAttribute(attrName));
        }
        
        try {
            OutputStream os = response.getOutputStream();
            engine.getTemplate(view).render(data, os);
        } catch (RuntimeException e) {  // 捕獲 ByteWriter.close() 拋出的 RuntimeException
            Throwable cause = e.getCause();
            if (cause instanceof IOException) { // ClientAbortException、EofException 直接或間接繼承自 IOException
                String name = cause.getClass().getSimpleName();
                if ("ClientAbortException".equals(name) || "EofException".equals(name)) {
                    return ;
                }
            }
            
            throw e;
        } catch (IOException e) {
            throw new RenderException(e);
        }
    }
    
    public String toString() {
        return view;
    }
}

Render的調用

    // ----------------
    // render below ---
    
    public Render getRender() {
        return render;
    }
    
    /**
     * Render with any Render which extends Render
     */
    public void render(Render render) {
        this.render = render;
    }
    
    /**
     * Render with view use default type Render configured in JFinalConfig
     */
    public void render(String view) {
        render = renderManager.getRenderFactory().getRender(view);
    }
    
    /**
     * Render template to String content, it is useful for:
     * 1: Generate HTML fragment for AJAX request
     * 2: Generate email, short message and so on
     */
    public String renderToString(String template, Map data) {
        if (template.charAt(0) != ‘/‘) {
            template = action.getViewPath() + template;
        }
        return renderManager.getEngine().getTemplate(template).renderToString(data);
    }
    
    /**
     * Render with JFinal template
     */
    public void renderTemplate(String template) {
        render = renderManager.getRenderFactory().getTemplateRender(template);
    }
    
    /**
     * Render with jsp view
     */
    public void renderJsp(String view) {
        render = renderManager.getRenderFactory().getJspRender(view);
    }
    
    /**
     * Render with freemarker view
     */
    public void renderFreeMarker(String view) {
        render = renderManager.getRenderFactory().getFreeMarkerRender(view);
    }
    
    /**
     * Render with velocity view
     */
    public void renderVelocity(String view) {
        render = renderManager.getRenderFactory().getVelocityRender(view);
    }
    
    /**
     * Render with json
     * <p>
     * Example:<br>
     * renderJson("message", "Save successful");<br>
     * renderJson("users", users);<br>
     */
    public void renderJson(String key, Object value) {
        render = renderManager.getRenderFactory().getJsonRender(key, value);
    }
    
    /**
     * Render with json
     */
    public void renderJson() {
        render = renderManager.getRenderFactory().getJsonRender();
    }
    
    /**
     * Render with attributes set by setAttr(...) before.
     * <p>
     * Example: renderJson(new String[]{"blogList", "user"});
     */
    public void renderJson(String[] attrs) {
        render = renderManager.getRenderFactory().getJsonRender(attrs);
    }
    
    /**
     * Render with json text.
     * <p>
     * Example: renderJson("{\"message\":\"Please input password!\"}");
     */
    public void renderJson(String jsonText) {
        render = renderManager.getRenderFactory().getJsonRender(jsonText);
    }
    
    /**
     * Render json with object.
     * <p>
     * Example: renderJson(new User().set("name", "JFinal").set("age", 18));
     */
    public void renderJson(Object object) {
        render = object instanceof JsonRender ? (JsonRender)object : renderManager.getRenderFactory().getJsonRender(object);
    }
    
    /**
     * Render with text. The contentType is: "text/plain".
     */
    public void renderText(String text) {
        render = renderManager.getRenderFactory().getTextRender(text);
    }
    
    /**
     * Render with text and content type.
     * <p>
     * Example: renderText("&lt;user id=‘5888‘&gt;James&lt;/user&gt;", "application/xml");
     */
    public void renderText(String text, String contentType) {
        render = renderManager.getRenderFactory().getTextRender(text, contentType);
    }
    
    /**
     * Render with text and ContentType.
     * <p>
     * Example: renderText("&lt;html&gt;Hello James&lt;/html&gt;", ContentType.HTML);
     */
    public void renderText(String text, ContentType contentType) {
        render = renderManager.getRenderFactory().getTextRender(text, contentType);
    }
    
    /**
     * Forward to an action
     */
    public void forwardAction(String actionUrl) {
        render = new ForwardActionRender(actionUrl);
    }
    
    /**
     * Render with file
     */
    public void renderFile(String fileName) {
        render = renderManager.getRenderFactory().getFileRender(fileName);
    }
    
    /**
     * Render with file, using the new file name to the client
     */
    public void renderFile(String fileName, String downloadFileName) {
        render = renderManager.getRenderFactory().getFileRender(fileName, downloadFileName);
    }
    
    /**
     * Render with file
     */
    public void renderFile(File file) {
        render = renderManager.getRenderFactory().getFileRender(file);
    }
    
    /**
     * Render with file, using the new file name to the client
     */
    public void renderFile(File file, String downloadFileName) {
        render = renderManager.getRenderFactory().getFileRender(file, downloadFileName);
    }
    
    /**
     * Redirect to url
     */
    public void redirect(String url) {
        render = renderManager.getRenderFactory().getRedirectRender(url);
    }
    
    /**
     * Redirect to url
     */
    public void redirect(String url, boolean withQueryString) {
        render = renderManager.getRenderFactory().getRedirectRender(url, withQueryString);
    }
    
    /**
     * Render with view and status use default type Render configured in JFinalConfig
     */
    public void render(String view, int status) {
        render = renderManager.getRenderFactory().getRender(view);
        response.setStatus(status);
    }
    
    /**
     * Render with url and 301 status
     */
    public void redirect301(String url) {
        render = renderManager.getRenderFactory().getRedirect301Render(url);
    }
    
    /**
     * Render with url and 301 status
     */
    public void redirect301(String url, boolean withQueryString) {
        render = renderManager.getRenderFactory().getRedirect301Render(url, withQueryString);
    }
    
    /**
     * Render with view and errorCode status
     */
    public void renderError(int errorCode, String view) {
        throw new ActionException(errorCode, renderManager.getRenderFactory().getErrorRender(errorCode, view));
    }
    
    /**
     * Render with render and errorCode status
     */
    public void renderError(int errorCode, Render render) {
        throw new ActionException(errorCode, render);
    }
    
    /**
     * Render with view and errorCode status configured in JFinalConfig
     */
    public void renderError(int errorCode) {
        throw new ActionException(errorCode, renderManager.getRenderFactory().getErrorRender(errorCode));
    }
    
    /**
     * Render nothing, no response to browser
     */
    public void renderNull() {
        render = renderManager.getRenderFactory().getNullRender();
    }
    
    /**
     * Render with javascript text. The contentType is: "text/javascript".
     */
    public void renderJavascript(String javascriptText) {
        render = renderManager.getRenderFactory().getJavascriptRender(javascriptText);
    }
    
    /**
     * Render with html text. The contentType is: "text/html".
     */
    public void renderHtml(String htmlText) {
        render = renderManager.getRenderFactory().getHtmlRender(htmlText);
    }
    
    /**
     * Render with xml view using freemarker.
     */
    public void renderXml(String view) {
        render = renderManager.getRenderFactory().getXmlRender(view);
    }
    
    public void renderCaptcha() {
        render = renderManager.getRenderFactory().getCaptchaRender();
    }
    
    /**
     * 渲染二維碼
     * @param content 二維碼中所包含的數據內容
     * @param width 二維碼寬度,單位為像素
     * @param height 二維碼高度,單位為像素
     */
    public void renderQrCode(String content, int width, int height) {
        render = renderManager.getRenderFactory().getQrCodeRender(content, width, height);
    }
    
    /**
     * 渲染二維碼,並指定糾錯級別
     * @param content 二維碼中所包含的數據內容
     * @param width 二維碼寬度,單位為像素
     * @param height 二維碼高度,單位為像素
     * @param errorCorrectionLevel 糾錯級別,可設置的值從高到低分別為:‘H‘、‘Q‘、‘M‘、‘L‘,具體的糾錯能力如下:
     *  H = ~30% 
     *  Q = ~25%
     *  M = ~15%
     *  L = ~7%
     */
    public void renderQrCode(String content, int width, int height, char errorCorrectionLevel) {
        render = renderManager.getRenderFactory().getQrCodeRender(content, width, height, errorCorrectionLevel);
    }
    
    public boolean validateCaptcha(String paraName) {
        return com.jfinal.captcha.CaptchaRender.validate(this, getPara(paraName));
    }

這種改進的抽象工廠方法的好處在於:

1.易於交換產品系列。

由於具體工廠類,例如IRenderFactory renderFactory=new TemplateRenderFactory(); 在一個應用中只需要在初始化的時候出現一次,這就使得改變一個應用的具體工廠變得非常容易,它只需要改變具體工廠即可使用不同的產品配置。

2.讓具體的創建實例過程與客戶端分離。

客戶端是通過它們的抽象接口操作實例,產品實現類的具體類名也被具體的工廠實現類分離,不會出現在客戶端代碼中。就像我們上面的例子,客戶端只需要知道RenderManger,至於它是什麽ViewType它就不知道了。

3.簡化了擴展功能代碼。

如果需要擴展Render的功能,只需要通過調用以下代碼即可實現:


    /**
     * Set the renderFactory
     */
    public void setRenderFactory(IRenderFactory renderFactory) {
        if (renderFactory == null) {
            throw new IllegalArgumentException("renderFactory can not be null.");
        }
        RenderManager.me().setRenderFactory(renderFactory);
    }

Jfinal源碼分析-Render系列方法設計模式