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);
	}
原文地址:https://www.cnblogs.com/Erma/p/10401418.html