最近写了个简单的类似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容器的使用。