Jfinal源碼分析-Render系列方法設計模式
阿新 • • 發佈:2019-02-19
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("<user id=‘5888‘>James</user>", "application/xml");
*/
public void renderText(String text, String contentType) {
render = renderManager.getRenderFactory().getTextRender(text, contentType);
}
/**
* Render with text and ContentType.
* <p>
* Example: renderText("<html>Hello James</html>", 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系列方法設計模式