公司项目一真用的是ssi(spring+springmvc+mybatis/ibatis)感觉还是比较重量级,所以找了个国人开发的JFinal框架学习,方便后续自己开发些东西的话能够快速开发。

但是JFinal默认使用的是freemarket模板引擎,可是一直以后用的都是velocity用顺手了模板引擎就不想变了,虽然支持velocity但不支持velocity的layout布局,也不支持toolbox。所以用JFinal之前先扩展这两个功能做为练手。

一惯的喜欢maven来进行管理代码,JFinal倡导的COC原则还是比较认同的,终于可以告别蛋疼的xml配置。
整完模板,以后开始弄注解方式@action @service和权限控制

	<dependency>
	<groupId>org.apache.velocity</groupId>
		<artifactId>velocity</artifactId>
		<version>1.7</version>
	</dependency>
	<dependency>
		<groupId>org.apache.velocity</groupId>
		<artifactId>velocity-tools</artifactId>
		<version>2.0</version>
	</dependency>

下面是重写的VelocityRender类,为了区分自带的VelocityRender,命名为VelocityLayoutRender

package net.yunbiz.shop.web.reader;


import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.apache.velocity.Template;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.ToolManager;
import org.apache.velocity.tools.view.ViewToolContext;

import com.jfinal.core.JFinal;
import com.jfinal.log.Logger;
import com.jfinal.render.IMainRenderFactory;
import com.jfinal.render.Render;
import com.jfinal.render.RenderException;

/**
 * @author zhiyong.xiongzy
 */
public class VelocityLayoutRender extends Render {

	private static final long	              serialVersionUID	             = 1012573049421601960L;
	private transient static final String	  encoding	                     = getEncoding();
	private transient static final String	  contentType	                 = "text/html;charset=" + encoding;
	/**
	 * The velocity.properties key for specifying the servlet's error template.
	 */
	public static final String	              PROPERTY_ERROR_TEMPLATE	     = "tools.view.servlet.error.template";

	/**
	 * The velocity.properties key for specifying the relative directory holding
	 * layout templates.
	 */
	public static final String	              PROPERTY_LAYOUT_DIR	         = "tools.view.servlet.layout.directory";

	/**
	 * The velocity.properties key for specifying the servlet's default layout
	 * template's filename.
	 */
	public static final String	              PROPERTY_DEFAULT_LAYOUT	     = "tools.view.servlet.layout.default.template";

	/**
	 * The default error template's filename.
	 */
	public static final String	              DEFAULT_ERROR_TEMPLATE	     = "error.html";

	/**
	 * The default layout directory
	 */
	public static final String	              DEFAULT_LAYOUT_DIR	         = "layout/";

	/**
	 * The default filename for the servlet's default layout
	 */
	public static final String	              DEFAULT_DEFAULT_LAYOUT	     = "default.html";

	public static final String	              TOOLBOX_FILE	                 = "WEB-INF/vm-toolbox.xml";

	/**
	 * The context key that will hold the content of the screen.
	 * This key ($screen_content) must be present in the layout template for the
	 * current screen to be rendered.
	 */
	public static final String	              KEY_SCREEN_CONTENT	         = "screen_content";

	/**
	 * The context/parameter key used to specify an alternate layout to be used
	 * for a request instead of the default layout.
	 */
	public static final String	              KEY_LAYOUT	                 = "layout";

	/**
	 * The context key that holds the {@link Throwable} that broke the rendering
	 * of the requested screen.
	 */
	public static final String	              KEY_ERROR_CAUSE	             = "error_cause";

	/**
	 * The context key that holds the stack trace of the error that broke the
	 * rendering of the requested screen.
	 */
	public static final String	              KEY_ERROR_STACKTRACE	         = "stack_trace";

	/**
	 * The context key that holds the {@link MethodInvocationException} that
	 * broke the rendering of the requested screen.
	 * If this value is placed in the context, then $error_cause will hold the
	 * error that this invocation exception is wrapping.
	 */
	public static final String	              KEY_ERROR_INVOCATION_EXCEPTION	= "invocation_exception";

	protected static String	                  errorTemplate;
	protected static String	                  layoutDir;
	protected static String	                  defaultLayout;

	private transient static final Properties	properties	                 = new Properties();

	private transient static boolean	      notInit	                     = true;

	public VelocityLayoutRender(String view) {
		this.view = view;
	}

	public static void setProperties(Properties properties) {
		Set<Entry<Object, Object>> set = properties.entrySet();
		for (Iterator<Entry<Object, Object>> it = set.iterator(); it.hasNext();) {
			Entry<Object, Object> e = it.next();
			VelocityLayoutRender.properties.put(e.getKey(), e.getValue());
		}
	}

	public void render() {
		init();
		PrintWriter writer = null;
		try {

			VelocityEngine velocityEngine = new VelocityEngine();
			ViewToolContext context = new ViewToolContext(velocityEngine, request, response, JFinal.me().getServletContext());

			ToolManager tm = new ToolManager();
			tm.setVelocityEngine(velocityEngine);
			tm.configure(JFinal.me().getServletContext().getRealPath(TOOLBOX_FILE));
			if (tm.getToolboxFactory().hasTools(Scope.REQUEST)) {
				context.addToolbox(tm.getToolboxFactory().createToolbox(Scope.REQUEST));
			}
			if (tm.getToolboxFactory().hasTools(Scope.APPLICATION)) {
				context.addToolbox(tm.getToolboxFactory().createToolbox(Scope.APPLICATION));
			}
			if (tm.getToolboxFactory().hasTools(Scope.SESSION)) {
				context.addToolbox(tm.getToolboxFactory().createToolbox(Scope.SESSION));
			}

			for (@SuppressWarnings("unchecked")
			Enumeration<String> attrs = request.getAttributeNames(); attrs.hasMoreElements();) {
				String attrName = attrs.nextElement();
				context.put(attrName, request.getAttribute(attrName));
			}

			Template template = Velocity.getTemplate(view);
			StringWriter sw = new StringWriter();
			template.merge(context, sw);
			context.put(KEY_SCREEN_CONTENT, sw.toString());

			response.setContentType(contentType);
			writer = response.getWriter(); // BufferedWriter writer = new
			                               // BufferedWriter(new
			                               // OutputStreamWriter(System.out));
			Object obj = context.get(KEY_LAYOUT);
			String layout = (obj == null) ? null : obj.toString();
			if (layout == null) {
				// no alternate, use default
				layout = defaultLayout;
			} else {
				// make it a full(er) path
				layout = layoutDir + layout;
			}

			try {
				// load the layout template
				template = Velocity.getTemplate(layout);
			} catch (ResourceNotFoundException e) {
				Logger.getLogger(VelocityLayoutRender.class).error("Can't load layout \"" + layout + "\"", e);

				// if it was an alternate layout we couldn't get...
				if (!layout.equals(defaultLayout)) {
					// try to get the default layout
					// if this also fails, let the exception go
					try {
						template = Velocity.getTemplate(defaultLayout);
					} catch (ResourceNotFoundException e2) {
						Logger.getLogger(VelocityLayoutRender.class).error("Can't load layout \"" + defaultLayout + "\"", e2);
					}
				}
			}

			template.merge(context, writer);
			writer.flush(); // flush and cleanup
		} catch (ResourceNotFoundException e) {
			throw new RenderException("Error : cannot find template " + view, e);
		} catch (ParseErrorException e) {
			throw new RenderException("Syntax error in template " + view + ":" + e, e);
		} catch (Exception e) {
			throw new RenderException(e);
		} finally {
			if (writer != null)
				writer.close();
		}
	}

	private void init() {
		if (notInit) {
			String webPath = JFinal.me().getServletContext().getRealPath("/");
			properties.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, webPath);
			properties.setProperty(Velocity.ENCODING_DEFAULT, encoding);
			properties.setProperty(Velocity.INPUT_ENCODING, encoding);
			properties.setProperty(Velocity.OUTPUT_ENCODING, encoding);
			Velocity.init(properties);
			// setup layout
			errorTemplate = VelocityLayoutRender.properties.getProperty(PROPERTY_ERROR_TEMPLATE, DEFAULT_ERROR_TEMPLATE);
			layoutDir = VelocityLayoutRender.properties.getProperty(PROPERTY_LAYOUT_DIR, DEFAULT_LAYOUT_DIR);
			defaultLayout = VelocityLayoutRender.properties.getProperty(PROPERTY_DEFAULT_LAYOUT, DEFAULT_DEFAULT_LAYOUT);

			// preventive error checking! directory must end in /
			if (!layoutDir.endsWith("/")) {
				layoutDir += '/';
			}
			// for efficiency's sake, make defaultLayout a full path now
			defaultLayout = layoutDir + defaultLayout;

			// log the current settings
			Logger.getLogger(VelocityLayoutRender.class).info("VelocityRender: Error screen is '" + errorTemplate + "'");
			Logger.getLogger(VelocityLayoutRender.class).info("VelocityRender: Layout directory is '" + layoutDir + "'");
			Logger.getLogger(VelocityLayoutRender.class).info("VelocityRender: Default layout template is '" + defaultLayout + "'");
			notInit = false;
		}
	}

	public static final class VelocityLayoutRenderFactory implements IMainRenderFactory {

		public Render getRender(String view) {
			return new VelocityLayoutRender(view);
		}

		public String getViewExtension() {
			return ".html";
		}
	}
}

然后把在webapp下面建个目录layout用于放置layout文件,默认的layout文件路径是 layout/default.html

<!doctype html>
<html>
<title>Jfinal-velocity-layout-toolbox</title>
</head>
我是layout页面内容
<div class="wrap">
	$!{screen_content}
</div>
</body>
</html>

toolbox配置文件不可少,vm-toolbox.xml,路径参考VelocityLayoutRender.TOOLBOX_FILE
暂时放几个常用的,后续自己可以扩展功能

<?xml version="1.0"?>

<toolbox>
	<xhtml>true</xhtml>

	<tool>
		<key>stringUtils</key>
		<scope>application</scope>
		<class>org.apache.commons.lang.StringUtils</class>
	</tool>
	<tool>
		<key>esc</key>
		<scope>application</scope>
		<class>org.apache.velocity.tools.generic.EscapeTool</class>
	</tool>
	<tool>
		<key>number</key>
		<scope>application</scope>
		<class>org.apache.velocity.tools.generic.NumberTool</class>
		<parameter name="format" value="#0.00" />
	</tool>
	<tool>
		<key>gson</key>
		<scope>application</scope>
		<class>com.google.gson.Gson
		</class>
	</tool>
</toolbox>

最后一步就简单了,在Config中指定视图处理器

public class ShopConfig extends JFinalConfig {

	/**
	 * 配置常量
	 */
	public void configConstant(Constants me) {
		// 加载少量必要配置,随后可用getProperty(...)获取值
		loadPropertyFile("a_little_config.txt");
		me.setDevMode(getPropertyToBoolean("devMode", false));
		RenderFactory.setMainRenderFactory(new VelocityLayoutRenderFactory());
	}
	
	//后面的代码省略...... 
}