自己编写一个基于Velocity的MVC框架

公司留了作业(还有一个月毕业),让预习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没法发,但是如果有人力、时间、金钱,相信我们国人也会编出相当完美优雅的框架。
还有一个月就毕业了,回望过去,还是很向往自己的大学生活,面对现实只能勇敢的去面对。

你可能感兴趣的:(apache,框架,mvc,servlet,velocity)