手写Springmvc
- 2019 年 11 月 7 日
- 笔记
一、手写springmvc所用技术
1、java反射
2、自定义注解
二、手写思路:
init:
通过解析xml获取扫包范围,通过扫包范围工具类,找打类上是否有controller注解,并将其装入容器中。再看类上是否有requestMapping ,创建类与url的关系,再检查该类方法中是否有requestMapping是否存在注解,并且创建方法与url的关系。
Dispatservlet:
解析请求的路径,通过路径去寻找对应的方法,通过反射执行方法。并且转发到相对应的视图。
现在开始我们的手写springmvc之旅 !!!
首先我们创建一个web项目并且创建一下目录结构:

第一步:引入依赖
<dependencies> <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies>
第二部:自定义工具类
ClassUtil
package com.siyuan.extspringmvc.utils; 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.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class ClassUtil { /** * 取得某个接口下所有实现这个接口的类 */ public static List<Class> getAllClassByInterface(Class c) { List<Class> returnClassList = null; if (c.isInterface()) { // 获取当前的包名 String packageName = c.getPackage().getName(); // 获取当前包下以及子包下所以的类 List<Class<?>> allClass = getClasses(packageName); if (allClass != null) { returnClassList = new ArrayList<Class>(); for (Class classes : allClass) { // 判断是否是同一个接口 if (c.isAssignableFrom(classes)) { // 本身不加入进去 if (!c.equals(classes)) { returnClassList.add(classes); } } } } } return returnClassList; } /* * 取得某一类所在包的所有类名 不含迭代 */ public static String[] getPackageAllClassName(String classLocation, String packageName) { // 将packageName分解 String[] packagePathSplit = packageName.split("[.]"); String realClassLocation = classLocation; int packageLength = packagePathSplit.length; for (int i = 0; i < packageLength; i++) { realClassLocation = realClassLocation + File.separator + packagePathSplit[i]; } File packeageDir = new File(realClassLocation); if (packeageDir.isDirectory()) { String[] allClassName = packeageDir.list(); return allClassName; } return null; } /** * 从包package中获取所有的Class * * @param pack * @return */ public static List<Class<?>> getClasses(String packageName) { // 第一个class类的集合 List<Class<?>> classes = new ArrayList<Class<?>>(); // 是否循环迭代 boolean recursive = true; // 获取包的名字 并进行替换 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)) { // 获取包的物理路径 String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); // 以文件的方式扫描整个包下的文件 并添加到集合中 findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes); } else if ("jar".equals(protocol)) { // 如果是jar包文件 // 定义一个JarFile 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, List<Class<?>> classes) { // 获取此包的目录 建立一个File File dir = new File(packagePath); // 如果不存在或者 也不是目录就直接返回 if (!dir.exists() || !dir.isDirectory()) { 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)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } // 首字母转小写 public static String toLowerCaseFirstOne(String s) { if (Character.isLowerCase(s.charAt(0))) return s; else return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString(); } // 初始化对象 public static Object newInstance(Class<?> classInfo) throws ClassNotFoundException, InstantiationException, IllegalAccessException { return classInfo.newInstance(); } }
第三步:自定义注解类 ExtController 与 ExtyRequstMapping
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //自定义控制器注解 @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface ExtController { }
ExtyRequstMapping:
//自定义RequestMapping @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface ExtyRequstMapping { String value() default ""; }
第四步:创建dispatrue类
package com.siyuan.extspringmvc.dispatur; import java.io.IOException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.siyuan.extspringmvc.annotation.ExtController; import com.siyuan.extspringmvc.annotation.ExtyRequstMapping; import com.siyuan.extspringmvc.utils.ClassUtil; public class DispactrueServlet extends HttpServlet{ /** * 一、设计容器: * 1、定义controller容器 * 2、定义controllerRequestMapping容器 * 3、定义methodRequstMapping容器 * 二、设计dispatcherServlet * 1、重写servlet init方法 * 1) 通过解析xml获取package路径 * 2) 扫描包下所有类,将带有ExtController注解的类全部添加到mvcBeans容器中 ==== init * 3) 通过遍历mvcBeans容器 通过java反射初始化mvcControllerUrl,mvcMethodUrl ==== handelMaping * 2、重写 get,post方法 * 1)通过反射得到路径去寻找对应的方法。 * 2)通过转发不改变访问路径 找到对应的页面 ===视图 */ private Map<String, Object> mvcBeans = new HashMap<String, Object>(); private Map<String, Object> mvcControllerUrl = new HashMap<String, Object>(); private Map<String, String> mvcMethodUrl = new HashMap<String, String>(); @Override public void init() throws ServletException { try { //1、扫包 List<Class<?>> classes = ClassUtil.getClasses("com.siyuan.extcontroller"); //2、遍历包下所有类,找到含有ExtController类并装配到 mvcBeans容器中 initBeans(classes); //3、处理路径与类的关系,和路径与方法的关系。处理映射关系 handlerMapping(); } catch (Exception e) { System.out.println("springmbvc初始化异常:"+e); } } /** * 处理映射关系 */ public void handlerMapping() { for (Map.Entry<String, Object> entry : mvcBeans.entrySet()) { // 1、获取对象 以及反射对象 Object object = entry.getValue(); Class<? extends Object> classInfo = object.getClass(); // 2、判断类上有没有requestMapping String obectUrl = null; ExtyRequstMapping extController = classInfo.getDeclaredAnnotation(ExtyRequstMapping.class); if(extController != null ) { obectUrl = extController.value(); } //3、遍历该类所有方法并且判断方法上面是否有requestMapping 并且设置对应关系。 Method [] declareMthods = classInfo.getDeclaredMethods(); for (Method method : declareMthods) { //1、判断该方法上是否有这个 注解 ExtyRequstMapping methdoRequestMapping = method.getDeclaredAnnotation(ExtyRequstMapping.class); //2、判断 if(methdoRequestMapping != null ) { //获取 value值 String methdUrl = methdoRequestMapping.value(); //存储类 mvcControllerUrl.put(obectUrl+methdUrl, object); //存储方法名称对应关系 mvcMethodUrl.put(obectUrl+methdUrl, method.getName()); } } } } /** * 遍历包下所有类,找到含有ExtController类并装配到 mvcBeans容器中 * @param classes * @throws IllegalAccessException * @throws InstantiationException * @throws ClassNotFoundException */ public void initBeans(List<Class<?>> classes) throws ClassNotFoundException, InstantiationException, IllegalAccessException { for (Class<?> classinfo : classes) { //1、找到含有ExtController ExtController extController = classinfo.getDeclaredAnnotation(ExtController.class); //2、判断 if(extController != null) { //首字母小写 String beanId = ClassUtil.toLowerCaseFirstOne(classinfo.getSimpleName()); //通过反射创建对象 mvcBeans.put(beanId, ClassUtil.newInstance(classinfo)); } } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { dipact(req,resp); } catch (Exception e) { e.printStackTrace(); } } /** * 根据路径找到对应的方法并且执行 返回对应的视图 * * @param req * @param resp * @throws IOException * @throws ServletException */ public void dipact(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { //1、得到 请求路径 String reqUrl = req.getRequestURI(); //2、去mvcControllerUrl找对应的类 Object object = mvcControllerUrl.get(reqUrl); if(object == null){ resp.getWriter().println("not fund controller "); System.out.println("没有找到对应的 controller"); return ; } //3、找方法 String method = mvcMethodUrl.get(reqUrl); if(method.isEmpty()) { resp.getWriter().println("not fund requstMapping "); System.out.println("没有找到对应的 requstMapping"); return ; } // 4、执行方法 String retPage = methodInvok(object,method); //5、返回视图处理 if(!retPage.isEmpty()) { viewdisplay(retPage,req,resp); } } /** * 执行方法 * @param object * @param method * @return */ public String methodInvok(Object object, String method) { try { //4、执行方法 Class<? extends Object> classInfo = object.getClass(); //5、得到方法对象 Method actMenthod = classInfo.getMethod(method); //6、执行方法 return (String) actMenthod.invoke(object); } catch (Exception e) { System.out.println("执行方法报错:"+e); } return null; } /** * 视图展示 * @param pageName * @param req * @param res * @throws ServletException * @throws IOException */ public void viewdisplay(String pageName, HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // 获取后缀信息 String suffix = ".jsp"; // 页面目录地址 String prefix = "/"; req.getRequestDispatcher(prefix + pageName + suffix).forward(req, res); } }
第五步:web.xml中配置dispatruservlet
<!-- Spring MVC 核心控制器 DispatcherServlet 配置 --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>com.siyuan.extspringmvc.dispatur.DispactrueServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <!-- 拦截所有/* 的请求,交给DispatcherServlet处理,性能最好 --> <url-pattern>/</url-pattern> </servlet-mapping>
第六步:创建controller类
package com.siyuan.extcontroller; import com.siyuan.extspringmvc.annotation.ExtyRequstMapping; @ExtController @ExtyRequstMapping("/extIndex") // 1、/extIndex/test public class ExtController { @ExtyRequstMapping("/test") public String test() { System.out.println("手写springmvc"); return "test"; } }
第七步:写好index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>页面展示</title> </head> <body> <h1>我是手写SpringMVC框架....</h1> </body> </html>
执行效果:

这里有一个bug就是需要配置一下项目路径为 / 因为我没做处理只是简单的写一下。