用过springmvc的可能都知道,要集成springmvc需要在web.xml中加入一个跟随web容器启动的DispatcherServlet,然后由该servlet初始化一些东西,然后所有的web请求都被这个servlet接管。所以自己写mvc的关键就是弄懂这个servlet干了啥。先分析一下springmvc的功能,首先我们写一个接口,就是写一个Controller,然后里面写一个方法,在类或者方法里面使用@RequestMapping直接修饰,当该注解对应的path被请求时,会按照指定格式传入参数并调用该方法,然后按照指定格式将调用的结果写出到向浏览器的输出流中(@ResponseBody),或者转发到jsp,再去由jsp转换生成的servlet去将结果写出到输出流(默认的请求转发),或者重定向到指定的jsp(return "redirect:/test.jsp"等。上面仅仅使用jsp举例子,不代表springmvc只支持使用jsp渲染。但是我们自己写的mvc只是为了演示整个流程和若干细节,并不能全面重写springmvc我也没那个能力重写,所以页面层只用jsp。
详细分析下写一个mvc的流程:第一:我们也可以使用一个servlet将前端所有请求都接管到一个servlet中去(这里其实filter,servlet都可以实现,比如strus2采用filter接管,springmvc采用servlet,原理大同小异),第二:这个servlet是随着容器自启动,所以需要配置load-on-startup,然后我们在这个servlet的init方法里面可以扫描指定的包(扫描哪些包,可以通过servlet在web.xml中的init-param配置),加载一些注解并将注解配置的属性和类,方法对象的关系保存在一些map中,第三:当页面访问任意后端接口时,最终会经过doGet或者doPost(上层是service方法,为了方便不用service方法),我们可以在这两个方法中根据请求的url路径去找到对应的Controller类和Method对象,然后从请求中拿出参数传入方法需要的参数,得到方法执行的结果,最后根据方法里面指定的返回格式(@ResponseBody这种),或者请求转发,或者重定向做处理。废话不多说,直接上代码。
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
- <display-name>mymvc</display-name>
- <servlet>
- <servlet-name>dispacher</servlet-name>
- <servlet-class>com.rd.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>package</param-name>
- <param-value>com.rd.controller</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>dispacher</servlet-name>
- <url-pattern>/</url-pattern>
- </servlet-mapping>
- </web-app>
- package com.rd.servlet;
- import java.io.IOException;
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.regex.Pattern;
- import javax.servlet.ServletConfig;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.apache.commons.lang3.StringUtils;
- import com.rd.annotation.Path;
- import com.rd.annotation.RespJson;
- import com.rd.util.JsonUtil;
- import com.rd.util.MethodUtil;
- import com.rd.util.ScanClassUtil;
- /**
- * 类似springmvc的将请求全部纳入控制范围的servlet
- * @author rongdi
- * @date 2017年9月20日 下午1:58:26
- */
- public class DispatcherServlet extends HttpServlet {
- private static final long serialVersionUID = 1L;
- //path和Class的映射
- private final static Map < String,
- Class < ?>>classMap = new HashMap < String,
- Class < ?>>();
- //path和Method的映射
- private final static Map < String,
- Method > methodMap = new HashMap < String,
- Method > ();
- //存放被@RespJson修饰的类
- private final static Set < Class < ?>>classRespJsons = new HashSet < Class < ?>>();
- //存放被@RespJson修饰的方法
- private final static Set < Method > methodRespJsons = new HashSet < Method > ();
- @Override public void init(ServletConfig config) throws ServletException {
- System.out.println("---DispatcherServlet初始化开始---");
- //获取web.xml中配置的要扫描的包
- String basePackage = config.getInitParameter("package");
- //配置了多个包
- if (basePackage.indexOf(",") > 0) {
- //按逗号进行分隔
- String[] packageNameArr = basePackage.split(",");
- for (String packageName: packageNameArr) {
- add2ClassMap(packageName);
- }
- } else {
- add2ClassMap(basePackage);
- }
- System.out.println("----DispatcherServlet初始化结束---");
- }
- /**
- * 将被注解修饰的类
- * @param packageName
- */
- private void add2ClassMap(String packageName) {
- Set < Class < ?>>setClasses = ScanClassUtil.getClasses(packageName);
- for (Class < ?>clazz: setClasses) {
- String pathAttrValue = null;
- //判断类被注解修饰
- if (clazz.isAnnotationPresent(Path.class)) {
- //获取path的Annotation的实例
- Path pathInstance = clazz.getAnnotation(Path.class);
- //获取Annotation的实例的value属性的值
- pathAttrValue = pathInstance.value();
- if (StringUtils.isNotEmpty(pathAttrValue)) {
- pathAttrValue = handPathStr(pathAttrValue);
- classMap.put(pathAttrValue, clazz);
- }
- }
- if (clazz.isAnnotationPresent(RespJson.class)) {
- classRespJsons.add(clazz);
- }
- //判断方法被注解修饰
- Method[] methods = clazz.getMethods();
- for (Method m: methods) {
- //判断方法被注解修饰
- if (m.isAnnotationPresent(Path.class)) {
- //获取path的Annotation的实例
- Path pathInstance = m.getAnnotation(Path.class);
- //获取Annotation的实例的value属性的值
- String methodPathValue = pathInstance.value();
- if (StringUtils.isNotEmpty(methodPathValue)) {
- methodPathValue = handPathStr(methodPathValue);
- pathAttrValue = handPathStr(pathAttrValue);
- methodMap.put(pathAttrValue + methodPathValue, m);
- }
- }
- if (m.isAnnotationPresent(RespJson.class)) {
- methodRespJsons.add(m);
- }
- }
- }
- }
- /**
- * 处理一下路径,前面后面的斜杠
- * @param pathStr
- * @return
- */
- private String handPathStr(String pathStr) {
- if (pathStr.endsWith("/")) {
- pathStr = pathStr.substring(0, pathStr.length() - 1);
- }
- if (!pathStr.startsWith("/")) {
- pathStr = "/" + pathStr;
- }
- return pathStr;
- }
- @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
- IOException {
- Map < String,
- String[] > params = request.getParameterMap();
- String url = request.getRequestURI();
- //当请求根路径是,请求转发到首页
- if ("/".equals(url)) {
- request.getRequestDispatcher("/index.jsp").forward(request, response);
- return;
- }
- if ("/favicon.ico".equals(url)) {
- return;
- }
- Method m = null;
- if (methodMap.containsKey(url)) {
- m = methodMap.get(url);
- } else {
- //这个过程,其实可以优化,如果存在通配符匹配,不用每次都循环匹配,可以缓存起来,第二次直接用,这里暂时忽略优化问题
- Set < String > urls = methodMap.keySet();
- for (String murl: urls) {
- String reg = "^" + murl.replace("*", ".*?") + "$";
- if (Pattern.matches(reg, url)) {
- m = methodMap.get(murl);
- break;
- }
- }
- }
- if (m == null) {
- throw new ServletException("没有找到与路径:" + url + "对应的处理方法");
- }
- try {
- /**
- * 这里需要获取参数名,jdk1.8之后可以直接直接反射获取,条件比较恶心,需要开启开关
- * 如下直接使用javassist字节码工具类实现,也可以用asm等其他工具
- */
- String[] paramNames = MethodUtil.getAllParamaterName(m);
- List < String > paramValues = new ArrayList < String > ();
- for (String paramName: paramNames) {
- if (params.get(paramName) == null) {
- paramValues.add(null);
- } else {
- paramValues.add(params.get(paramName)[0]);
- }
- }
- /**
- * 调用方法所在类的默认构造方法,生成执行方法的对象(springmvc里的这个对象是单例的,这里为了省事,每次都new一个出来),
- * 然后执行方法,返回结果
- */
- Class < ?>cla = m.getDeclaringClass();
- Object result = m.invoke(cla.newInstance(), paramValues.toArray());
- /**
- * 如果方法返回类型为 void,则该调用结果返回 null,如果返回值为void,则直接跳转到同路径的jsp页面上,
- * 为了简单起见这里后缀写死.jsp,实际上springmvc是支持配置ViewResolver,
- * 可以指定请求转发或者重定向所在的界面层的前缀和后缀。
- */
- if (result == null) {
- request.getRequestDispatcher(url + ".jsp").forward(request, response);
- }
- /**
- * 这里springmvc默认是请求转发到jsp
- * 为了方便这里直接根据修饰返回类型的注解,确定用哪种方式序列化,
- */
- if (classRespJsons.contains(cla) || methodRespJsons.contains(m)) {
- result = JsonUtil.serialize(result);
- //输出json到浏览器
- response.getWriter().print(result);
- } else if (result.toString().startsWith("redirect:")) { //重定向
- //去掉前缀就是重定向到的路径,实际上这里不严谨,应该加上一个项目的上下文路径
- response.sendRedirect(result.toString().substring(9) + ".jsp");
- } else { //请求转发
- request.getRequestDispatcher(result.toString() + ".jsp").forward(request, response);
- }
- } catch(Exception e) {
- e.printStackTrace();
- }
- }
- @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
- IOException {
- doGet(request, response);
- }
- public static void main(String[] args) {
- String url = "/test/a";
- String reg = "^/test/.*?$";
- System.out.println(Pattern.matches(reg, url));
- System.out.println(Long.class);
- }
- }
- package com.rd.controller;
- import java.util.HashMap;
- import java.util.Map;
- import com.rd.annotation.Path;
- import com.rd.annotation.RespJson;
- @Path(value = "/test") public class TestController {
- @Path(value = "/text") public void text() {}
- @Path(value = "/redirect") public String redirect(String a) {
- return "redirect:/test/redirect";
- }
- @Path(value = "/json")@RespJson public Map json(String a) {
- return new HashMap() {
- {
- put("a", a);
- }
- };
- }
- }
- package com.rd.annotation;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- @Retention(RetentionPolicy.RUNTIME)@Target({
- ElementType.TYPE,
- ElementType.METHOD
- }) public@interface Path {
- //访问的匹配路径
- String value();
- }
- package com.rd.annotation;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- @Retention(RetentionPolicy.RUNTIME)@Target({
- ElementType.TYPE,
- ElementType.METHOD
- }) public@interface RespJson {
- }
- package com.rd.util;
- import java.util.HashMap;
- import com.fasterxml.jackson.databind.DeserializationFeature;
- import com.fasterxml.jackson.databind.ObjectMapper;
- public class JsonUtil {
- private static ObjectMapper mapper;
- static {
- mapper = new ObjectMapper();
- mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
- }
- public static String serialize(Object obj) throws Exception {
- if (obj == null) {
- throw new IllegalArgumentException("obj should not be null");
- }
- return mapper.writeValueAsString(obj);
- }
- public static void main(String[] args) throws Exception {
- System.out.println(serialize(new HashMap() {
- {
- put("name", "zhangsan");
- }
- }));
- }
- }
- package com.rd.util;
- import java.lang.reflect.Method;
- import javassist.ClassClassPath;
- import javassist.ClassPool;
- import javassist.CtClass;
- import javassist.CtMethod;
- import javassist.Modifier;
- import javassist.NotFoundException;
- import javassist.bytecode.CodeAttribute;
- import javassist.bytecode.LocalVariableAttribute;
- import javassist.bytecode.MethodInfo;
- /**
- * 使用javassist的方法工具
- * @author rongdi
- * @date 2017年9月20日 上午11:21:29
- */
- public class MethodUtil {
- public static String[] getAllParamaterName(Method method) throws NotFoundException {
- Class < ?>clazz = method.getDeclaringClass();
- ClassPool pool = ClassPool.getDefault();
- ClassClassPath classPath = new ClassClassPath(MethodUtil.class);
- pool.insertClassPath(classPath);
- CtClass clz = pool.get(clazz.getName());
- CtClass[] params = new CtClass[method.getParameterTypes().length];
- for (int i = 0; i < method.getParameterTypes().length; i++) {
- params[i] = pool.getCtClass(method.getParameterTypes()[i].getName());
- }
- CtMethod cm = clz.getDeclaredMethod(method.getName(), params);
- MethodInfo methodInfo = cm.getMethodInfo();
- CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
- LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
- int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
- String[] paramNames = new String[cm.getParameterTypes().length];
- for (int i = 0; i < paramNames.length; i++) {
- paramNames[i] = attr.variableName(i + pos);
- }
- return paramNames;
- }
- }
- package com.rd.util;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.Properties;
- /**
- * @author rongdi
- * @date 2017年9月16日 下午3:36:08
- */
- public class PropertyUtil {
- private static Properties props;
- synchronized static private void loadProps() {
- props = new Properties();
- InputStream in =null;
- try {
- try {
- String path = getJarDir() + "/application.properties"; in =new FileInputStream(path);
- } catch(Exception e) { in =PropertyUtil.class.getClassLoader().getResourceAsStream("application.properties");
- }
- props.load( in );
- } catch(FileNotFoundException e) {
- //logger.error("application.properties文件未找到");
- } catch(IOException e) {
- //logger.error("出现IOException");
- } finally {
- try {
- if (null != in) { in .close();
- }
- } catch(IOException e) {
- //logger.error("application.properties文件流关闭出现异常");
- }
- }
- }
- public static String getProperty(String key) {
- if (null == props) {
- loadProps();
- }
- return props.getProperty(key);
- }
- public static String getProperty(String key, String defaultValue) {
- if (null == props) {
- loadProps();
- }
- return props.getProperty(key, defaultValue);
- }
- /**
- * 获取jar绝对路径
- *
- * @return
- */
- public static String getJarPath() {
- File file = getFile();
- if (file == null) return null;
- return file.getAbsolutePath();
- }
- /**
- * 获取jar目录
- *
- * @return
- */
- public static String getJarDir() {
- File file = getFile();
- if (file == null) return null;
- return getFile().getParent();
- }
- /**
- * 获取jar包名
- *
- * @return
- */
- public static String getJarName() {
- File file = getFile();
- if (file == null) return null;
- return getFile().getName();
- }
- /**
- * 获取当前Jar文件
- *
- * @return
- */
- private static File getFile() {
- // 关键是这行...
- String path = PropertyUtil.class.getProtectionDomain().getCodeSource().getLocation().getFile();
- try {
- path = java.net.URLDecoder.decode(path, "UTF-8"); // 转换处理中文及空格
- } catch(java.io.UnsupportedEncodingException e) {
- return null;
- }
- return new File(path);
- }
- }
- package com.rd.util;
- import java.io.File;
- import java.io.FileFilter;
- import java.io.IOException;
- import java.net.JarURLConnection;
- import java.net.URL;
- import java.net.URLDecoder;
- import java.util.Enumeration;
- import java.util.LinkedHashSet;
- import java.util.Set;
- import java.util.jar.JarEntry;
- import java.util.jar.JarFile;
- /**
- * 扫描字节码工具类
- * @author rongdi
- * @date 2017年9月19日 下午4:13:19
- */
- public class ScanClassUtil {
- /**
- * 从包package中获取所有的Class
- * @param pack
- * @return
- */
- public static Set < Class < ?>>getClasses(String pack) {
- // 第一个class类的集合
- Set < Class < ?>>classes = new LinkedHashSet < Class < ?>>();
- // 是否循环迭代
- boolean recursive = true;
- // 获取包的名字 并进行替换
- String packageName = pack;
- String packageDirName = packageName.replace('.', '/');
- // 定义一个枚举的集合 并进行循环来处理这个目录下的things
- Enumeration < URL > dirs;
- try {
- dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
- // 循环迭代下去
- while (dirs.hasMoreElements()) {
- // 获取下一个元素
- URL url = dirs.nextElement();
- // 得到协议的名称
- String protocol = url.getProtocol();
- // 如果是以文件的形式保存在服务器上
- if ("file".equals(protocol)) {
- System.err.println("file类型的扫描");
- // 获取包的物理路径
- String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
- // 以文件的方式扫描整个包下的文件 并添加到集合中
- findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
- } else if ("jar".equals(protocol)) {
- // 如果是jar包文件
- // 定义一个JarFile
- System.err.println("jar类型的扫描");
- JarFile jar;
- try {
- // 获取jar
- jar = ((JarURLConnection) url.openConnection()).getJarFile();
- // 从此jar包 得到一个枚举类
- Enumeration < JarEntry > entries = jar.entries();
- // 同样的进行循环迭代
- while (entries.hasMoreElements()) {
- // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
- JarEntry entry = entries.nextElement();
- String name = entry.getName();
- // 如果是以/开头的
- if (name.charAt(0) == '/') {
- // 获取后面的字符串
- name = name.substring(1);
- }
- // 如果前半部分和定义的包名相同
- if (name.startsWith(packageDirName)) {
- int idx = name.lastIndexOf('/');
- // 如果以"/"结尾 是一个包
- if (idx != -1) {
- // 获取包名 把"/"替换成"."
- packageName = name.substring(0, idx).replace('/', '.');
- }
- // 如果可以迭代下去 并且是一个包
- if ((idx != -1) || recursive) {
- // 如果是一个.class文件 而且不是目录
- if (name.endsWith(".class") && !entry.isDirectory()) {
- // 去掉后面的".class" 获取真正的类名
- String className = name.substring(packageName.length() + 1, name.length() - 6);
- try {
- // 添加到classes
- classes.add(Class.forName(packageName + '.' + className));
- } catch(ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- } catch(IOException e) {
- e.printStackTrace();
- }
- }
- }
- } catch(IOException e) {
- e.printStackTrace();
- }
- return classes;
- }
- /**
- * 以文件的形式来获取包下的所有Class
- *
- * @param packageName
- * @param packagePath
- * @param recursive
- * @param classes
- */
- public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set < Class < ?>>classes) {
- // 获取此包的目录 建立一个File
- File dir = new File(packagePath);
- // 如果不存在或者 也不是目录就直接返回
- if (!dir.exists() || !dir.isDirectory()) {
- // log.warn("用户定义包名 " + packageName + " 下没有任何文件");
- return;
- }
- // 如果存在 就获取包下的所有文件 包括目录
- File[] dirfiles = dir.listFiles(new FileFilter() {
- // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
- public boolean accept(File file) {
- return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
- }
- });
- // 循环所有文件
- for (File file: dirfiles) {
- // 如果是目录 则继续扫描
- if (file.isDirectory()) {
- findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
- } else {
- // 如果是java类文件 去掉后面的.class 只留下类名
- String className = file.getName().substring(0, file.getName().length() - 6);
- try {
- // 添加到集合中去
- //classes.add(Class.forName(packageName + '.' + className));
- classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
- } catch(ClassNotFoundException e) {
- // log.error("添加用户自定义视图类错误 找不到此类的.class文件");
- e.printStackTrace();
- }
- }
- }
- }
- }
- <properties>
- <project.deploy>deploy</project.deploy>
- <jackson.version>2.5.4</jackson.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
- <version>3.1.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.4</version>
- </dependency>
- <dependency>
- <groupId>org.javassist</groupId>
- <artifactId>javassist</artifactId>
- <version>3.21.0-GA</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-core</artifactId>
- <version>${jackson.version}</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- <version>${jackson.version}</version>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-annotations</artifactId>
- <version>${jackson.version}</version>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>3.8.1</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
完整代码百度云地址(不要吐槽要放git啥的,我屌丝一个,不用那么高大上的东西):https://pan.baidu.com/s/1mi24Lbq
来源: http://www.cnblogs.com/rongdi/p/8017041.html