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