Jetty上的简单MVC容器设计

最近写了个简单的类似SpringMVC的小容器,在Jetty中运行,在这里分享一下。

主要用到的第三方JAR包如下:

Jetty上的简单MVC容器设计

工程代码结构如下:

Jetty上的简单MVC容器设计

我设计的思想来源于SpringMVC,也采用注解和反射的方式加载类,用freemarker作为Model和View的合并工具。

代码如下(内有中文注释):

package org.kitty.web;

import org.kitty.web.container.MvcContainer;
import org.kitty.web.container.ServletDispatch;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Jetty容器
 * @author zhaowen
 */
public class JettyServer {

	private static final Logger LOG = LoggerFactory.getLogger(JettyServer.class);

	public void start() {
		// 设置Socket
		SelectChannelConnector connector = new SelectChannelConnector();
		connector.setPort(9090);

		// 方法1:手动设置映射关系
		// ServletDispatch.addDipatch("/page.action", PageServlet.class);
		// 方法2:直接加载具体的类
		// MvcContainer.loadController("org.kitty.web.IndexController");
		// 方法3:以扫描包的方式加载
		MvcContainer.scanPackage("org.kitty.web");

		// 设置服务器参数
		Server server = new Server();
		server.addConnector(connector);
		// 设置ServletHandler
		server.addHandler(ServletDispatch.getServletHandler());
		try {
			// 启动Jetty容器
			server.start();
		} catch (Exception e) {
			LOG.error("", e);
		}
	}

	public static void main(String[] args) {

		new JettyServer().start();
	}
}
类扫描与注解的处理如下:

package org.kitty.web.container;

import java.io.File;
import java.lang.reflect.Method;

import org.kitty.web.annotation.Controller;
import org.kitty.web.annotation.RequestMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * MVC容器,用于加载Controller类
 * @author zhaowen
 */
public class MvcContainer {
	
	private static final Logger LOG = LoggerFactory.getLogger(MvcContainer.class);
	
	/**
	 * 加载Controller
	 * @param classname
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static void loadController(String classname){
		try {
			// 加载指定的类
			Class clazz = ClassLoader.getSystemClassLoader().loadClass(classname);
			// 判断此类是否有Controller注解
			if(clazz.isAnnotationPresent(Controller.class)){
				// 获取此类的所有方法
				Method[] methods = clazz.getMethods();
				for(Method m : methods){
					// 选择有RequestMapping注解的方法
					if(m.isAnnotationPresent(RequestMapping.class)){
						RequestMapping rm = m.getAnnotation(RequestMapping.class);
						// 获取servlet的请求路径
						String path = rm.value();
						// 将注解的方法加入容器
						ServletDispatch.addMethod(path, m);
						// 将映射关系存入容器
						ServletDispatch.addDipatch(path, ServletTemplate.class);
					}
				}
			}
		} catch (ClassNotFoundException e) {
			LOG.error("",e);
		}
	}
	
	/**
	 * 扫描包并加载
	 */
	public static void scanPackage(String packagefile){
		String root = ClassLoader.getSystemResource("").getPath();
		String path = root + packagefile.replace(".", "\\");
		File file = new File(path);
		String[] files = file.list();
		for(String f : files){
			if(f.endsWith("Controller.class")){
				MvcContainer.loadController(packagefile + "." + f.replace(".class", ""));
			}
		}
	}
	
}
自定义的两个注解如下,类似于Spring:

package org.kitty.web.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Controller {

}
package org.kitty.web.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RequestMapping {

	String value() default "";

}
映射关系容器:

package org.kitty.web.container;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.mortbay.jetty.servlet.ServletHandler;

@SuppressWarnings("rawtypes")
public class ServletDispatch {

	// 映射关系容器
	private static Map<String, Class> container = new ConcurrentHashMap<String, Class>();
	// 调用关系容器
	private static Map<String, Method> invokes = new ConcurrentHashMap<String, Method>();
	// Jetty的Servlet处理器
	private static ServletHandler handler = new ServletHandler();

	public static ServletHandler getServletHandler() {
		return handler;
	}

	public static synchronized void addDipatch(String path, Class servlet) {
		container.put(path, servlet);
		// 加入到ServletHandler中
		handler.addServletWithMapping(servlet, path);
	}

	public static synchronized Class getDipatch(String path) {
		return container.get(path);
	}

	public static synchronized void addMethod(String path, Method method) {
		invokes.put(path, method);
	}

	public static synchronized Method getMethod(String path) {
		return invokes.get(path);
	}

}
由于Jetty是Servlet容器,最终的HTTP请求都转化成Servlet来处理,这里我是这样将Controller转化成Servlet的,写了一个Servlet模板:

package org.kitty.web.container;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.kitty.web.view.HtmlTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * servlet容器模板,供Controller使用,反射调用Controller的注解方法
 * @author zhaowen
 */
public class ServletTemplate extends HttpServlet {

	private static final Logger LOG = LoggerFactory
			.getLogger(ServletTemplate.class);
	/**
	 * 
	 */
	private static final long serialVersionUID = 5237184324182149493L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		// 根据当前的请求路径从容器中获取要调用的方法
		Method method = ServletDispatch.getMethod(req.getServletPath());
		if (method != null) {
			try {
				// 利用反射调用方法
				Object obj = method.getDeclaringClass().newInstance();
				method.invoke(obj, req, resp);
			} catch (IllegalAccessException e) {
				LOG.error("", e);
			} catch (IllegalArgumentException e) {
				LOG.error("", e);
			} catch (InvocationTargetException e) {
				LOG.error("", e);
			} catch (InstantiationException e) {
				LOG.error("", e);
			}
		} else {
			PrintWriter pw = resp.getWriter();
			Map<String, Object> model = new HashMap<String, Object>();
			model.put("content", "Template");
			String data = HtmlTool.getHtml(model, "index.ftl");
			pw.write(data);
			pw.flush();
			pw.close();
		}
		super.doGet(req, resp);
	}

}
我测试用的展示层模板是用ftl文件,语法参见freemarker:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="pragma" content="no-cache" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
</head> 
<body>
<h1>hello ${content}</h1>
</body> 
</html> 
Model与View的合并如下:

package org.kitty.web.view;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

@SuppressWarnings("deprecation")
public class HtmlTool {

	private static final Logger LOG = LoggerFactory.getLogger(HtmlTool.class);

	private static Configuration configuration;

	static {
		configuration = new Configuration();
		configuration.setClassForTemplateLoading(HtmlTool.class, "");
	}

	/**
	 * 生成HTML文件
	 * 
	 * @param map
	 *            Data Model
	 * @param ftl
	 *            template file
	 * @return HTML
	 */
	public static String getHtml(Map<String, Object> model, String ftl) {
		Template template = null;
		Writer out = new StringWriter();
		try {
			template = configuration.getTemplate(ftl);
			template.setEncoding("UTF-8");
			try {
				// merge template and data
				template.process(model, out);
			} catch (TemplateException e) {
				LOG.error("", e);
			}
		} catch (IOException e) {
			LOG.error("", e);
		}
		return out.toString();
	}
}

最后是具体的Controller类的编写了,在没有MVC时用的就是Servlet,如下:

package org.kitty.web;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.kitty.web.view.HtmlTool;

/**
 * servlet方式处理请求
 * @author zhaowen
 */
public class PageServlet extends HttpServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = 7200471112326637238L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		PrintWriter pw = resp.getWriter();
		Map<String, Object> model = new HashMap<String, Object>();
		model.put("content", "PageServlet");
		String data = HtmlTool.getHtml(model, "index.ftl");
		pw.write(data);
		pw.flush();
		pw.close();
		super.doGet(req, resp);
	}

}

有了MVC后的写法就变了:

package org.kitty.web;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.kitty.web.annotation.Controller;
import org.kitty.web.annotation.RequestMapping;
import org.kitty.web.view.HtmlTool;

/**
 * 类似于SpringMVC的例子
 * @author zhaowen
 */
@Controller
public class IndexController {

	@RequestMapping("/index.htm")
	public void index(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		PrintWriter pw = resp.getWriter();
		// 数据层Model处理
		Map<String, Object> model = new HashMap<String, Object>();
		model.put("content", "Kitty");
		// 用数据和模板生成HTML文件
		String viewTemplate = "index.ftl";
		String data = HtmlTool.getHtml(model, viewTemplate);
		pw.write(data);
		pw.flush();
		pw.close();
	}

	@RequestMapping("/index/hello.do")
	public void hello(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		PrintWriter pw = resp.getWriter();
		Map<String, Object> model = new HashMap<String, Object>();
		model.put("content", "MVC");
		String data = HtmlTool.getHtml(model, "index.ftl");
		pw.write(data);
		pw.flush();
		pw.close();
	}
}
是不是和SpringMVC的很像?

区别就在于:

		// 方法1:手动设置映射关系
		// ServletDispatch.addDipatch("/page.action", PageServlet.class);
		// 方法2:直接加载具体的类
		// MvcContainer.loadController("org.kitty.web.IndexController");
		// 方法3:以扫描包的方式加载

浏览器的测试结果如下:

Jetty上的简单MVC容器设计     Jetty上的简单MVC容器设计   Jetty上的简单MVC容器设计

当然,这只是个简单的Demo,和SpringMVC比起来还差很远,不过通过这个Demo,初学者应该对Spring的运行原理有一定的认识了,而且也学会了Jetty容器的使用。

上一篇:解决针对ubuntu11.04安装中文包后不能正常查看或使用pdf和Archiver的问题


下一篇:Springboot+@Async异步+多线程