Jfinal源码分析-Render系列方法设计模式

在学习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);
    }

猜你喜欢

转载自www.cnblogs.com/Erma/p/10401418.html