公司留了作业(还有一个月毕业),让预习Velocity,在家呆着没意思,反正闲着也是闲着,看了VelocityViewServlet源码,感觉还可以,取其精华去其糟粕,自己写了一个基于Velocity的MVC框架,废话不多说了,直接进入正题。
VelocityActionServlet是整个MVC框架的核心类,拦截所有的Action请求,分发给不同的Action进行处理。
init()方法初始化了系统需要的资源和VelocityEngine。
doProcess()是这个类的核心方法,处理用户请求,获取context数据,获取模板,合成html
package com.zzq.velocity.core;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.collections.ExtendedProperties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.io.VelocityWriter;
public class VelocityActionServlet extends HttpServlet {
private static Log log = LogFactory.getLog(VelocityActionServlet.class);
/**
* request放入VelocityContext中的key
*/
public static final String REQUEST = "request";
/**
* response放入VelocityContext中的key
*/
public static final String RESPONSE = "response";
public static final String CONTENT_TYPE = "default.contentType";
public static final String OUTPUT_ENCODING = "output.encoding";
/**
* 默认的输出编码
*/
public static final String DEFAULT_OUTPUT_ENCODING = "UTF-8";
/**
* velocity.properties在web.xml文件默认的key
*/
protected static final String INIT_PROPS_KEY =
"org.apache.velocity.properties";
/**
* velocity.properties在默认的路径
*/
protected static final String DEFAULT_PROPERTIES_PATH =
"/WEB-INF/velocity.properties";
/**
* 默认的ContextType
*/
public static final String DEFAULT_CONTENT_TYPE = "text/html";
/**
* 对输出流维护的池
*/
private ObjectPool<VelocityWriter> pool = new ObjectPool<VelocityWriter>(40);
private VelocityEngine velocity = null;
/**
* 例如user.do?method=add -> 会调用UserAction的add方法 相当于DispatchAction功能
*/
private static final String DEFAULT_PARAMETER_METHOD = "method";
private static final String INIT_PARAMETER_KEY = "parameter";
private String parameterMethod;
/**
* 最终默认的ContentType
*/
private String defaultContentType;
private static final String INIT_TEMPLATEPATH_KEY = "templatePath";
private static final String FILE_RESOURCE_LOADER_PATH = "file.resource.loader.path";
public void init(ServletConfig config) throws ServletException {
super.init(config);
/**
* 初始化Velocity引擎
*/
initVelocity(config);
defaultContentType = (String)getVelocityProperty(CONTENT_TYPE, DEFAULT_CONTENT_TYPE);
String encoding = (String)getVelocityProperty(OUTPUT_ENCODING, DEFAULT_OUTPUT_ENCODING);
parameterMethod = findInitParameter(config, INIT_PARAMETER_KEY, DEFAULT_PARAMETER_METHOD);
int index = defaultContentType.lastIndexOf(";");
if(index < 0) {
defaultContentType += "; charset=" + encoding;
}
}
/**
* 初始化Velocity引擎
* @param config
* @throws ServletException
*/
protected void initVelocity(ServletConfig config) throws ServletException {
try {
velocity = new VelocityEngine();
ExtendedProperties p = loadConfiguration(config);
String templatePath = findInitParameter(config, INIT_TEMPLATEPATH_KEY);
templatePath = config.getServletContext().getRealPath("/") + templatePath;
velocity.setProperty(FILE_RESOURCE_LOADER_PATH, templatePath);
velocity.setExtendedProperties(p);
velocity.init();
} catch (Exception e) {
log.error("初始化velocity引擎时出错!");
throw new ServletException(e);
}
}
protected String getVelocityProperty(String key, String defaultValue) {
String value = (String)velocity.getProperty(key);
if(null == value || "".equals(value.trim())) {
value = defaultValue;
}
return value;
}
/**
* 加载velocity.properties配置文件
*/
protected ExtendedProperties loadConfiguration(ServletConfig config) {
String propsFile = findInitParameter(config, INIT_PROPS_KEY);
if(null == propsFile) {
propsFile = DEFAULT_PROPERTIES_PATH;
}
ExtendedProperties properties = new ExtendedProperties();
InputStream inputStream = getServletContext().getResourceAsStream(propsFile);
try {
inputStream = getServletContext().getResourceAsStream(propsFile);
if(null != inputStream) {
properties.load(inputStream);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if(null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException("关闭" + propsFile + "文件时出现异常!");
}
}
}
return properties;
}
protected String findInitParameter(ServletConfig config, String key) {
String value = config.getInitParameter(key);
if(null == value || "".equals(value.trim())) {
value = config.getServletContext().getInitParameter(key);
}
return value;
}
protected String findInitParameter(ServletConfig config, String key, String defaultValue) {
String value = config.getInitParameter(key);
if(null == value || "".equals(value.trim())) {
value = config.getServletContext().getInitParameter(key);
}
if(null == value || "".equals(value.trim())) {
value = defaultValue;
}
return value;
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doProcess(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doProcess(request, response);
}
/**
* 请求处理的核心方法
* @param request
* @param response
*/
protected void doProcess(HttpServletRequest request, HttpServletResponse response) {
Context context = null;
try {
initServletActionContext(request, response);
initActionContext(request);
setContentType(request, response);
Action action = getAction(request);
String templateName = doExecute(action, request, response);
//在Action中调用response.sendRedirect();
if(null == templateName) {
return ;
}
context = createContext(action, request, response);
Template template = handleRequest(templateName, request, response, context);
mergeTemplate(template, context, response);
} catch(Exception e) {
try {
e.printStackTrace(response.getWriter());
response.getWriter().flush();
} catch (IOException e1) {
throw new RuntimeException(e);
}
} finally {
destoryServletActionContext();
destoryActionContext(request);
}
}
/**
* 给ServletActionContext赋值
* @param request
* @param response
*/
protected void initServletActionContext(HttpServletRequest request,
HttpServletResponse response) {
ServletActionContext.setRequest(request);
ServletActionContext.setResponse(response);
}
/**
* 给ActionContext赋值
* @param request
*/
protected void initActionContext(HttpServletRequest request) {
//处理request
Map<String, Object> values = new HashMap<String, Object>();
Enumeration enumeration = request.getAttributeNames();
while(enumeration.hasMoreElements()) {
String key = (String)enumeration.nextElement();
values.put(key, request.getAttribute(key));
}
ActionContext.setRequest(values);
//处理session
values = new HashMap<String, Object>();
HttpSession session = request.getSession();
if(null != session) {
session = request.getSession(true);
}
enumeration = session.getAttributeNames();
while(enumeration.hasMoreElements()) {
String key = (String)enumeration.nextElement();
values.put(key, session.getAttribute(key));
}
ActionContext.setSession(values);
//处理ServletContext
values = new HashMap<String, Object>();
ServletContext servletContext = session.getServletContext();
enumeration = servletContext.getAttributeNames();
while(enumeration.hasMoreElements()) {
String key = (String)enumeration.nextElement();
values.put(key, session.getAttribute(key));
}
ActionContext.setServletContext(values);
}
/**
* 销毁当前线程的ServletActionContext
*/
protected void destoryServletActionContext() {
ServletActionContext.destory();
}
/**
* 销毁当前线程的ActionContext
* @param request
*/
protected void destoryActionContext(HttpServletRequest request) {
Map<String, Object> values = ActionContext.getContext().getRequest();
for(Iterator<String> iter = values.keySet().iterator(); iter.hasNext(); ) {
String key = iter.next();
request.setAttribute(key, values.get(key));
}
values = ActionContext.getContext().getSession();
HttpSession session = request.getSession();
for(Iterator<String> iter = values.keySet().iterator(); iter.hasNext(); ) {
String key = iter.next();
session.setAttribute(key, values.get(key));
}
values = ActionContext.getContext().getServletContext();
ServletContext servletContext = session.getServletContext();
for(Iterator<String> iter = values.keySet().iterator(); iter.hasNext(); ) {
String key = iter.next();
servletContext.setAttribute(key, values.get(key));
}
ActionContext.destory();
}
protected String doExecute(Action action, HttpServletRequest request,
HttpServletResponse response) throws Exception {
String methodName = request.getParameter(parameterMethod);
if(null == methodName || "".equals(methodName.trim())) {
methodName = "execute";
}
Method method = action.getClass().getMethod(methodName);
String templateName = (String)method.invoke(action);
return templateName;
}
protected Context createContext(Action action, HttpServletRequest request,
HttpServletResponse response){
VelocityContext context = new VelocityContext();
context.put(REQUEST, request);
context.put(RESPONSE, response);
Map<String, Object> params = action.getContext();
for(Iterator<String> iter = params.keySet().iterator(); iter.hasNext(); ) {
String key = iter.next();
Object value = params.get(key);
context.put(key, value);
}
return context;
}
private Action getAction(HttpServletRequest request) {
String requestURI = request.getRequestURI();
String actionName = requestURI.substring(requestURI.indexOf('/', 1), requestURI.lastIndexOf('.'));
Action action = (Action)BeanFactory.getBean(actionName);
if(null == action) {
throw new RuntimeException("没有找到路径为:" + actionName + "对应的Action");
}
return action;
}
protected void setContentType(HttpServletRequest request,
HttpServletResponse response) {
response.setContentType(defaultContentType);
}
protected Template handleRequest(String templateName, HttpServletRequest request,
HttpServletResponse response, Context ctx) {
//do something......
Template template = null;
try {
template = velocity.getTemplate(templateName);
} catch (Exception e) {
throw new RuntimeException(e);
}
return template;
}
protected void mergeTemplate(Template template,
Context context,HttpServletResponse response) throws UnsupportedEncodingException, IOException {
VelocityWriter vw = null;
Writer writer = getResponseWriter(response);
try {
vw = pool.get();
if(null == vw) {
vw = new VelocityWriter(writer, 4*1024, true);
} else {
vw.recycle(writer);
}
template.merge(context, writer);
} finally {
if(null != vw) {
vw.flush();
vw.recycle(null);
pool.put(vw);
}
}
}
protected Writer getResponseWriter(HttpServletResponse response)
throws UnsupportedEncodingException, IOException {
Writer writer = null;
try {
writer = response.getWriter();
} catch (IllegalStateException e) {
String encoding = response.getCharacterEncoding();
if (encoding == null) {
encoding = DEFAULT_OUTPUT_ENCODING;
}
writer = new OutputStreamWriter(response.getOutputStream(), encoding);
}
return writer;
}
}
这是一个简单的Bean工程实现,加载类路径下的beans.properties文件(这里完全可以用xml,这都是次要的问题,不是核心问题,等以后有时间了可以继续改进)
beans.properties文件,
路径=Action类,如:
/user=com.velocity.test.action.UserAction
package com.zzq.velocity.core;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
public class BeanFactory {
private static Map<String, String> map = new HashMap<String, String>();
static {
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("beans.properties");
try {
Properties prop = new Properties();
prop.load(is);
Iterator iter = prop.keySet().iterator();
while(iter.hasNext()) {
String key = (String)iter.next();
String className = prop.getProperty(key);
map.put(key, className);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("初始化BeanFactory失败", e);
} finally {
try {
if(null != is) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
public static Object getBean(String id) {
//prototype
String className = map.get(id);
if(null == className) {
return null;
}
Object action = null;
try {
action = Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("创建Action类:" + className + "失败", e);
}
return action;
}
}
ObjectPool对象池的实现,主要是对Writer进行管理。
package com.zzq.velocity.core;
import java.util.ArrayList;
import java.util.List;
/**
* 对象池
* @author zzq
*
*/
public class ObjectPool<T> {
/**
* 默认池大小
*/
public static final int DEFAULT_POOL_SIZE = 10;
private int max;
private int current=-1;
private List<T> pool = null;
public ObjectPool() {
this(DEFAULT_POOL_SIZE);
}
public ObjectPool(int size) {
max = size;
pool = new ArrayList<T>(size);
}
public T get() {
T obj = null;
synchronized (this) {
if(current > -1) {
obj = pool.get(current);
current --;
}
}
return obj;
}
public void put(T obj) {
synchronized (this) {
current ++;
if(current < max) {
pool.add(obj);
return ;
}
current --;
}
}
public int getMax() {
return max;
}
public List<T> getPool() {
return pool;
}
}
VelocityWriter类对Writer进行修饰,加入了Buffer。
package com.zzq.velocity.core;
import java.io.IOException;
import java.io.Writer;
public class VelocityWriter extends Writer {
private int bufferSize;
private boolean autoFlush;
private Writer writer;
private char cb[];
private int next;
private static final int DEFAULT_CHARBUFFER_SIZE = 8 * 1024;
public VelocityWriter(Writer writer) {
this(writer, DEFAULT_CHARBUFFER_SIZE, true);
}
public VelocityWriter(Writer writer, int size, boolean isFlush) {
if(size < 0) {
throw new IllegalArgumentException("Buffer size < 0");
}
this.bufferSize = size;
this.autoFlush = isFlush;
this.writer = writer;
cb = size > 0 ? new char[size] : null;
next = 0;
}
public int getBufferSize() { return bufferSize; }
public boolean isAutoFlush() { return autoFlush; }
private final void flushBuffer() throws IOException {
if(bufferSize == 0) {
return ;
}
if(next == 0) {
return ;
}
writer.write(cb, 0, next);
this.next = 0;
}
public final void clear() {
next = 0;
}
private final void bufferOverflow() throws IOException {
throw new IOException("Buffer over flow!");
}
public final void write(int c) throws IOException {
if(bufferSize == 0) {
writer.write(c);
return ;
}
if(next >= bufferSize) {
if(autoFlush) {
flushBuffer();
} else {
bufferOverflow();
}
}
cb[next++] = (char)c;
}
public final void write(char buf[]) throws IOException {
write(buf, 0, buf.length);
}
public final void write(String s, int off, int len) throws IOException {
write(s.toCharArray(), off, len);
}
public final void write(String s) throws IOException {
write(s, 0, s.length());
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
if(bufferSize == 0) {
writer.write(cbuf, off, len);
return ;
}
if (len == 0)
{
return;
}
if (len >= bufferSize)
{
if (autoFlush)
flushBuffer();
else
bufferOverflow();
writer.write(cbuf, off, len);
return;
}
if(len + off > cbuf.length) {
throw new IllegalArgumentException("len + off > cbuf.length");
}
while(len > 0) {
int b = len > bufferSize - next ? bufferSize - next : len;
System.arraycopy(cb, next, cbuf, off, b);
next += b;
if(next >= bufferSize) {
if(autoFlush) {
flushBuffer();
} else {
bufferOverflow();
}
}
off += b;
len -= b;
}
}
public final void recycle(Writer writer) {
this.writer = writer;
clear();
}
@Override
public void close() throws IOException {
if(writer != null) {
flush();
writer.close();
}
}
@Override
public void flush() throws IOException {
flushBuffer();
writer.flush();
}
}
ServletActionContext这个类,挺简单的,一看就能明白,和Struts2的ServletActionContext功能一样。
package com.zzq.velocity.core;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class ServletActionContext {
private static ThreadLocal<HttpServletRequest> reqContext = new ThreadLocal<HttpServletRequest>();
private static ThreadLocal<HttpServletResponse> respContext = new ThreadLocal<HttpServletResponse>();
public static void setRequest(HttpServletRequest request) {
reqContext.set(request);
}
public static void setResponse(HttpServletResponse response) {
respContext.set(response);
}
public static HttpServletRequest getRequset() {
return reqContext.get();
}
public static HttpServletResponse getResponse() {
return respContext.get();
}
public static HttpSession getSession() {
return reqContext.get().getSession();
}
public static ServletContext getServletContext() {
return reqContext.get().getSession().getServletContext();
}
public static void destory() {
reqContext.remove();
respContext.remove();
}
}
这个更不用说了,玩过Struts2的人一看就知道。
package com.zzq.velocity.core;
import java.util.Map;
/**
* 将Servlet API 进行解耦
* @author zzq
*
*/
public class ActionContext {
private static ActionContext instance = new ActionContext();
private static ThreadLocal<Map<String, Object>> request = new ThreadLocal<Map<String, Object>>();
private static ThreadLocal<Map<String, Object>> session = new ThreadLocal<Map<String, Object>>();
private static ThreadLocal<Map<String, Object>> servletContext = new ThreadLocal<Map<String, Object>>();
private ActionContext() {}
public static ActionContext getContext() {
return instance;
}
public static void setRequest(Map<String, Object> value) {
request.set(value);
}
public static void setSession(Map<String, Object> value) {
session.set(value);
}
public static void setServletContext(Map<String, Object> value) {
servletContext.set(value);
}
public Map<String, Object> getSession() {
return session.get();
}
public Map<String, Object> getServletContext() {
return servletContext.get();
}
public Map<String, Object> getRequest() {
return request.get();
}
public static void destory() {
request.remove();
session.remove();
servletContext.remove();
}
}
一个接口,所有的Action类都必须要实现这个接口。
package com.zzq.velocity.core;
import java.util.Map;
public interface Action {
public String execute() throws Exception;
/**
* 获取Velocity需要的Context数据
* @return Context
*/
public Map<String, Object> getContext();
}
最后就是这个web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>VelocityActionServlet</servlet-name>
<servlet-class>com.zzq.velocity.core.VelocityActionServlet</servlet-class>
<init-param>
<param-name>properties</param-name>
<param-value>/WEB-INF/velocity.properties</param-value>
</init-param>
<init-param>
<param-name>templatePath</param-name>
<param-value>/vm</param-value>
</init-param>
<load-on-startup>10</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>VelocityActionServlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
核心的东西就是这些,下面玩一下这个框架。
1、定义Action类
package com.velocity.test.action;
import java.util.HashMap;
import java.util.Map;
import com.zzq.velocity.core.Action;
import com.zzq.velocity.core.ServletActionContext;
public class UserAction implements Action {
private Map<String, Object> map = new HashMap<String, Object>();
public String execute() throws Exception {
return "/login.vm";
}
public String login() {
String username = ServletActionContext.getRequset().getParameter("username");
String password = ServletActionContext.getRequset().getParameter("password");
User user = new User();
user.setUsername(username);
user.setPassword(password);
System.out.println(username);
System.out.println(password);
map.put("user", user);
ServletActionContext.getSession().setAttribute("user", user);
return "/login_success.vm";
}
public String register() {
String username = ServletActionContext.getRequset().getParameter("username");
String password = ServletActionContext.getRequset().getParameter("password");
System.out.println(username);
System.out.println(password);
return "/register_success.vm";
}
public String registerInput() {
return "/register.vm";
}
public Map<String, Object> getContext() {
return map;
}
}
2、在beans.properties加入
/user=com.velocity.test.action.UserAction
3、定义模板,放到WebRoot/vm/下(这个路径可以在web.xml中配)
引用
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>用户登录</h2>
<form action="user.html" method="post">
<input type="hidden" name="method" value="login">
用户名:<input name="username"><br>
密码:<input name="password" type="password"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
引用
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
登录成功!$user.username<br>
session:$request.session.getAttribute('user').username
</body>
</html>
4、访问http://localhost:8080/Veloctiy/user.html 一看就知道
总之:这个就是现在闲着没意思编着玩的,缺少很多功能(如上传,参数类型装换,还有对整个框架的扩展如插件化等等),跟成型的MVC Framework没法发,但是如果有人力、时间、金钱,相信我们国人也会编出相当完美优雅的框架。
还有一个月就毕业了,回望过去,还是很向往自己的大学生活,面对现实只能勇敢的去面对。