最近写了个简单的类似SpringMVC的小容器,在Jetty中运行,在这里分享一下。
主要用到的第三方JAR包如下:
工程代码结构如下:
我设计的思想来源于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:以扫描包的方式加载
浏览器的测试结果如下:
当然,这只是个简单的Demo,和SpringMVC比起来还差很远,不过通过这个Demo,初学者应该对Spring的运行原理有一定的认识了,而且也学会了Jetty容器的使用。