Jetty上的简单MVC容器设计

简介: 最近写了个简单的类似SpringMVC的小容器,在Jetty中运行,在这里分享一下。 主要用到的第三方JAR包如下: 工程代码结构如下: 我设计的思想来源于SpringMVC,也采用注解和反射的方式加载类,用freemarker作为Model和View的合并工具。

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

目录
相关文章
|
XML 前端开发 Java
Spring MVC 父子容器是什么?这篇文章讲清楚了
Spring MVC 父子容器是初学 Spring MVC 时最先接触到 Spring 知识点之一,还记得我刚工作那会,项目基础架构是其他同事搭建的,其中就用到了 Spring MVC 中的父子容器,还把 Spring MVC 中的不同层拆成了不同的 maven 模块。这里暂不讨论这种模块拆分方式的优劣,Spring 为什么设计出具有层次结构的容器呢?Web 环境中什么场景会用到这种具有层次结构的容器?
1394 0
Spring MVC 父子容器是什么?这篇文章讲清楚了
|
Web App开发 XML JSON
【小家Spring】Spring MVC容器的web九大组件之---HandlerAdapter源码详解---HttpMessageConverter的匹配规则(选择原理)(上)
【小家Spring】Spring MVC容器的web九大组件之---HandlerAdapter源码详解---HttpMessageConverter的匹配规则(选择原理)(上)
【小家Spring】Spring MVC容器的web九大组件之---HandlerAdapter源码详解---HttpMessageConverter的匹配规则(选择原理)(上)
|
前端开发 Java 数据格式
10个知识点让你读懂spring MVC容器
随着 Spring Boot 逐步全面覆盖到我们的项目之中,我们已经基本忘却当年经典的 Servlet + Spring MVC 的组合,那让人熟悉的 web.xml 配置。而本文,我们想先抛开 Spring Boot 到一旁,回到从前,一起来看看 Servlet 是怎么和 Spring MVC 集成,怎么来初始化 Spring 容器的。
251 1
|
Kubernetes Cloud Native 网络协议
理解 Pod 和容器设计 | 学习笔记
快速学习理解 Pod 和容器设计
理解 Pod 和容器设计 | 学习笔记
|
开发框架 运维 前端开发
NET MVC接口服务如何运行在容器中
有些公司内部存在一些NET项目,而公司服务器后期都换成了Linux,若单纯为这一个项目占用一台Windows服务器显得极其浪费,因此需要将NET项目嵌入到Linux服务器中,为了后期方便迁移和运维最好是Docker容器中运行。
250 0
|
前端开发 Java 容器
【Spring专场】「MVC容器」不看源码就带你认识核心流程以及运作原理
【Spring专场】「MVC容器」不看源码就带你认识核心流程以及运作原理
239 0
【Spring专场】「MVC容器」不看源码就带你认识核心流程以及运作原理
|
前端开发 Java 数据库连接
Spring MVC框架:第十五章:多IOC容器整合
Spring MVC框架:第十五章:多IOC容器整合
277 0
Spring MVC框架:第十五章:多IOC容器整合
|
存储 算法 C语言
STL设计之容器适配器,加之经典题目解析
STL设计之容器适配器,加之经典题目解析
STL设计之容器适配器,加之经典题目解析
|
运维 Kubernetes Cloud Native
企业级运维之云原生与Kubernets实战课程 - 第一章第3讲 理解Pod和容器设计
本节课主要介绍Pod概念、Pod解决的问题、Pod启动流程、Pod生命周期管理、Pod中服务探活和K8s常用的命令。
企业级运维之云原生与Kubernets实战课程 - 第一章第3讲 理解Pod和容器设计
|
JSON 前端开发 Java
【小家Spring】Spring MVC容器的web九大组件之---ViewResolver源码详解---视图View详解(下)
【小家Spring】Spring MVC容器的web九大组件之---ViewResolver源码详解---视图View详解(下)
【小家Spring】Spring MVC容器的web九大组件之---ViewResolver源码详解---视图View详解(下)