源码分析-手写springMVC框架@RequestMapping和@Controller注解
Posted 漫漫求索修身
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析-手写springMVC框架@RequestMapping和@Controller注解相关的知识,希望对你有一定的参考价值。
源码分析-手写springMVC框架@RequestMapping和@Controller注解
一、springMVC运行流程
1)、用户发送请求至前端控制器DipatcherServlet
2)、DipatcherServlet收到请求调用HandlerMapping处理器映射器
3)、处理器映射器根据请求url找到对应的处理器,生成处理器对象以及处理器拦截器(如果有则生成),一并返回给DipatcherServlet
4)、DipatcherServlet通过HandlerAdapter处理器适配器调用处理器
5)、执行处理器(Controller,也叫后端控制器)
6)、Controller执行完成后,返回ModelAndView
7)、处理器适配器将ModelAndView返回给DipatcherServlet
8)、DipatcherServlet将ModelAndView返回给视图解析器
9)、ViewReslover解析后返回给具体的view
10)、DipatcherServlet对view进行视图渲染,然后返回给用户
二、Servlet相关知识回顾
servlet是单例的,线程不安全的。
servlet的生命周期:加载---实例化----服务-----销毁
init():在servlet的生命周期中仅执行一次,在服务器装入servlet时执行,负责初始化servlet对象。
service():servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,都要调用该service方法
destroy():仅执行一次,在服务器停止且卸载Servlet时执行该方法。
三、手写springMVC思路
1、web.xml加载
为了读取web.xml中的配置,我们用到ServletConfig这个类,通过web.xml中加载我们自己写的ExtDispatcherServlet和读取配置信息
2、初始阶段
在前面我们提到DispatcherServlet的initStrategies方法会初始化9大组件,但是这里将实现一些SpringMVC的最基本的组件而不是全部,按顺序包括:
加载配置文件
扫描用户配置包下面所有的类
拿到扫描到的类,通过反射机制,实例化。并且放到ioc容器中(Map的键值对 beanName-bean) beanName默认是首字母小写
初始化HandlerMapping,这里其实就是把url和method对应起来放在一个k-v的Map中,在运行阶段取出
3、运行阶段
每一次请求将会调用doGet或doPost方法,所以统一运行阶段都放在doDispatch方法里处理,它会根据url请求去HandlerMapping中匹配到对应的Method,然后利用反射机制调用Controller中的url对应的方法,并得到结果返回。按顺序包括以下功能:
异常的拦截
获取请求传入的参数并处理参数
通过初始化好的handlerMapping中拿出url对应的方法名,反射调用
4、手写springMVC基本实现
自定义注解 package com.cy.ext.springmvc.annotation; 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 { } package com.cy.ext.springmvc.annotation; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.TYPE; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(value={METHOD, TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ExtRequestMapping { String value() default ""; } controller类 package com.cy.ext.springmvc.controller; import com.cy.ext.springmvc.annotation.ExtController; import com.cy.ext.springmvc.annotation.ExtRequestMapping; @ExtController @ExtRequestMapping("/index") public class IndexController { @ExtRequestMapping("/test") public String test(){ System.out.println("The Program is to here====="); return "index"; } } 自定义前端控制器类 package com.cy.ext.springmvc.servlet; import java.io.IOException; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.cy.ext.springmvc.annotation.ExtController; import com.cy.ext.springmvc.annotation.ExtRequestMapping; import com.cy.ext.utils.ClassUtils; /** * 手写springMVC 原理分析</br> * 1、创建一个前端控制器ExtDispatcherServlet 拦截所有请求,springMVC基于servlet实现<br> * 2、初始化操作,重写servlet的init方法</br> * ####2.1、扫包 将包下所有的类,注入到springmvc容器里面,存放在map集合中,key默认为类名小写 value为对象<br> * ####2.2、将url映射和方法进行关联</br> * ########2.2.1、判断类上是否有注解,使用反射机制循环遍历方法,判断方法上是否存在注解,进行封装 * 3、处理请求,重写get或者post方法(获取请求url,从urlBeans集合中获取实例对象,获取成功实例对象后, * 从urlMethodBeans集合中获取方法名称,然后使用反射机制执行) * @author ymk * */ public class ExtDispatcherServlet extends HttpServlet{ /** * com.cy.ext.springmvc.servlet.ExtDispatcherServlet */ private static final long serialVersionUID = 1L; //springMVC 容器对象key:类名id value:对象 private ConcurrentMap<String, Object> springmvcBeans = new ConcurrentHashMap<String, Object>();
private ConcurrentMap<String, Object> urlBeans = new ConcurrentHashMap<String, Object>();
private ConcurrentMap<String, Object> urlMethods = new ConcurrentHashMap<String, Object>(); @Override public void init() throws ServletException { // 1、获取当前包下所有类 List<Class<?>> classes = ClassUtils.getClasses("com.cy.ext"); try { // 2、包下所有的类,注入到springmvc容器里面,存放在map集合中,key默认为类名小写 value为对象 findClassMvcAnnotation(classes); } catch (Exception e) {
} // 3、将url和方法进行映射 handlerMapping(); }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // TODO Auto-generated method stub doPost(req, resp); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求的url String requestURI = req.getRequestURI(); System.out.println("requestURI:"+requestURI); if (requestURI == null || requestURI == "") { return; } // 2.从Map集合中获取控制对象 Object object = urlBeans.get(requestURI); if (object == null) { resp.getWriter().print("404----------------not found url"); return; } Object methodNameObj = urlMethods.get(requestURI); if (methodNameObj == null) { resp.getWriter().println("not found method"); } // 4.使用java的反射机制调用方法 String methodName = String.valueOf(methodNameObj); String resultPage = (String) methodInvoke(object, methodName); // 5.调用视图转换器渲染给页面展示 extResourceViewResolver(resultPage, req, resp);
}
// 调用视图转换器渲染给页面展示 private void extResourceViewResolver(String pageName, HttpServletRequest request ,HttpServletResponse response) throws IOException,ServletException{ String preffix = "/"; String suffix = ".jsp"; request.getRequestDispatcher(preffix+pageName+suffix).forward(request, response); }
//使用java的反射机制调用方法 private Object methodInvoke(Object object,String methodName){ Class<? extends Object> classInfo = object.getClass(); try { Method method = classInfo.getMethod(methodName); Object result = method.invoke(object); return result; } catch (Exception e) { return null; } }
//包下所有的类,注入到springmvc容器里面,存放在map集合中,key默认为类名小写 value为对象 private void findClassMvcAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException { for (Class<?> classInfo : classes) { if (classInfo != null) { ExtController extController = classInfo.getAnnotation(ExtController.class); if (extController != null) { //将类首字母转为小写 String beanId = ClassUtils.toLowerCaseFirstOne(classInfo.getSimpleName()); //初始化对象 Object object = ClassUtils.newInstance(classInfo); springmvcBeans.put(beanId, object); } } } }
//将url和方法进行映射 public void handlerMapping(){ //// 1.遍历springmvc bean容器 判断类上属否有url映射注解 for (Map.Entry<String, Object> mvcBean : springmvcBeans.entrySet()) { // 2.遍历所有的方法上是否有url映射注解 // 获取bean的对象 Object object = mvcBean.getValue(); // 3.判断类上是否有加url映射注解 Class<? extends Object> classInfo = object.getClass(); ExtRequestMapping extRequestMapping = classInfo.getAnnotation(ExtRequestMapping.class); // ExtRequestMapping extRequestMapping = classInfo.getDeclaredAnnotation(ExtRequestMapping.class); String baseUrl = ""; if (extRequestMapping != null) { baseUrl = extRequestMapping.value(); } Method[] methods = classInfo.getDeclaredMethods(); for (Method method : methods) { // 判断方法上是否有加url映射注解 ExtRequestMapping extMethodAnnotation = method.getAnnotation(ExtRequestMapping.class); if (extMethodAnnotation != null) { String methodUrl = baseUrl + extMethodAnnotation.value(); urlBeans.put(methodUrl, object); urlMethods.put(methodUrl, method.getName()); } } } } } class扫包工具类 package com.cy.ext.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; import org.apache.commons.lang.StringUtils; /** * 扫包工具 * @author ymk * */ public class ClassUtils { /** * 取得某个接口下所有实现这个接口的类 */ 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 getFirstLetterToLower(String str){ if (StringUtils.isEmpty(str)) { return str; } return str.substring(0, 1).toLowerCase() + str.substring(1); }
// 首字母转小写 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 InstantiationException, IllegalAccessException{ Object newInstance = classInfo.newInstance(); return newInstance; } } pom文件: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.cy</groupId> <artifactId>springframework_day20</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging>
<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> <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang --> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> </dependencies> <!-- <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> --> </project> web.xml文件 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <!-- Spring MVC 核心控制器 DispatcherServlet 配置 --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>com.cy.ext.springmvc.servlet.ExtDispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <!-- 拦截所有/* 的请求,交给DispatcherServlet处理,性能最好 --> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> |
以上是关于源码分析-手写springMVC框架@RequestMapping和@Controller注解的主要内容,如果未能解决你的问题,请参考以下文章
带你手写一个SpringMVC框架(有助于理解springMVC)