Jetty上的简单MVC容器设计

来源:互联网 发布:统赢编程教程 编辑:程序博客网 时间:2024/06/03 09:25

最近写了个简单的类似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() {// 设置SocketSelectChannelConnector 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);// 设置ServletHandlerserver.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@Documentedpublic @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@Documentedpublic @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;@Overrideprotected 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 datatemplate.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;@Overrideprotected 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 */@Controllerpublic 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容器的使用。

0 0