一,spring原生态的代码分析
1.1,首先,我们先来认识一下SpringMVC的主要组件
前端控制器(DisatcherServlet):接收请求,响应结果,返回可以是json,String等数据类型,也可以是页面(Model)。
处理器映射器(HandlerMapping):根据URL去查找处理器,一般通过xml配置或者注解进行查找。
处理器(Handler):就是我们常说的controller控制器啦,由程序员编写。
处理器适配器(HandlerAdapter):可以将处理器包装成适配器,这样就可以支持多种类型的处理器。
视图解析器(ViewResovler):进行视图解析,返回view对象(常见的有JSP,FreeMark等)。
1.2,工作原理
图解:
1、用户发送请求到前端控制器(DispatcherServlet)。
2、前端控制器请求处理器映射器(HandlerMapping)去查找处理器(Handler)。
3、找到以后处理器映射器(HandlerMappering)向前端控制器返回执行链(HandlerExecutionChain)。
4、前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)。
5、处理器适配器去执行Handler。
6、处理器执行完给处理器适配器返回ModelAndView。
7、处理器适配器向前端控制器返回ModelAndView。
8、前端控制器请求视图解析器(ViewResolver)去进行视图解析。
9、视图解析器向前端控制器返回View。
10、前端控制器对视图进行渲染。
11、前端控制器向用户响应结果。
看到这些步骤我相信大家很感觉非常的乱,这是正常的,但是这里主要是要大家理解springMVC中的几个组件:
前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。
处理器映射器(HandlerMapping):根据URL去查找处理器
处理器(Handler):(需要程序员去写代码处理逻辑的)
处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用)
视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面
换成自己的理解:
通过上面的代码我们可以看出它的顺序
1,浏览器request请求过来,到前端控制器。(DispatcherServlet)。。。。ps:在项目启动的时候已经通过扫包把所有@Controller注解的类已经加到一个map中去了,其中key就是类名小写,value就是实例
2,在前端处理器中(DispatcherServlet)通过处理器映射器(HandlerMapping)来确定调用的类和方法
3,处理器处理代码逻辑(Handler),程序员自己写的代码。
4,让后通过处理器的适配器(HandlerAdapter)去返回视图解析器(ModelAndView)。。。。ps:也就是我们常看到的<property name="prefix" value="/" /> <property name="suffix" value=".jsp" /> 这段代码
5,在前端控制器中再把视图解析器渲染成视图(view)返回
1.3,mvc模式回忆杀(springmvc中的mvc设计模式)
1.用户发起request请求至控制器(Controller)
控制接收用户请求的数据,委托给模型进行处理
2.控制器通过模型(Model)处理数据并得到处理结果
模型通常是指业务逻辑
3.模型处理结果返回给控制器
4.控制器将模型数据在视图(View)中展示
web中模型无法将数据直接在视图上显示,需要通过控制器完成。如果在C/S应用中模型是可以将数据在视图中展示的。
5.控制器将视图response响应给用户
通过视图展示给用户要的数据或处理结果。
自己理解:
在这里我们需要理解的是mvc模式
request请求到控制器,控制器,C
控制器请求请求模型(就是自己写的代码)把值赋给bean M
然后进行处理,这个时候控制器会把返回的M的数据统一渲染之后返回一个V中去。
1.4,上代码
web.xml
<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> Spring MVC 核心控制器 DispatcherServlet 配置 <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:mvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> 拦截所有/* 的请求,交给DispatcherServlet处理,性能最好 <url-pattern>/</url-pattern> </servlet-mapping>
mvc-config.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描要自动管理的包,使SpringMVC认为包下用了@Controller 注解的类是控制器 --> <context:component-scan base-package="com.itmayiedu.controller" /> <!-- 添加注解驱动,扩充了注解驱动,可以将请求参数绑定到控制器参数 --> <mvc:annotation-driven enable-matrix-variables="true" /> <!-- 静态资源处理,静态资源文件路径设置 --> <mvc:resources location="/styles/" mapping="/styles/.**" /> <mvc:resources location="/images/" mapping="/images/.**" /> <mvc:resources location="/plugins/" mapping="/plugins/.**" /> <mvc:resources location="/scripts/" mapping="/scripts/.**" /> <mvc:resources location="/html/" mapping="/html/.**" /> <mvc:resources location="/index.html" mapping="/index.html" /> <!--避免IE执行AJAX时,返回JSON出现下载文件 --> <bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> </list> </property> </bean> <!-- 启动SpringMVC的注解功能,完成请求和注解POJO的映射 --> <context:component-scan base-package="com.qingruihappy2"></context:component-scan> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="mappingJacksonHttpMessageConverter" /> <!-- JSON转换器 --> </list> </property> </bean> <!-- 配置文件上传,如果没有使用文件上传可以不用配置,当然如果不配,那么配置文件中也不必引入上传组件包 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 默认编码 --> <property name="defaultEncoding" value="utf-8" /> <!-- 文件大小最大值 --> <property name="maxUploadSize" value="10485760000" /> <!-- 内存中的最大值 --> <property name="maxInMemorySize" value="40960" /> <!-- 启用是为了推迟文件解析,以便捕获文件大小异常 --> <property name="resolveLazily" value="true" /> </bean> <!-- 定义跳转的文件的前后缀 ,视图模式配置 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 这里的配置我的理解是自动给后面action的方法return的字符串加上前缀和后缀,变成一个 可用的url地址 --> <property name="prefix" value="/" /> <property name="suffix" value=".jsp" /> </bean> </beans>
@Controller
package com.qingruihappy2.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * * @author qingruihappy * @data 2018年11月23日 上午1:05:45 * @说明: 通过上面的代码我们可以看出它的顺序 * 1,浏览器request请求过来,到前端控制器。(DispatcherServlet)。。。。ps:在项目启动的时候已经通过扫包把所有@Controller注解的类已经加到一个map中去了,其中key就是类名小写,value就是实例 * 2,在前端处理器中(DispatcherServlet)通过处理器映射器(HandlerMapping)来确定调用的类和方法 * 3,处理器处理代码逻辑(Handler),程序员自己写的代码。 * 4,让后通过处理器的适配器(HandlerAdapter)去返回视图解析器(ModelAndView)。。。。ps:也就是我们常看到的<property * name="prefix" value="/" /> <property name="suffix" value=".jsp" /> 这段代码 * 5,在前端控制器中再把视图解析器渲染成视图(view)返回 */ @Controller @RequestMapping("/ext") public class ExtIndexController { // ext/test/?name=122&age=6440654 @RequestMapping("/test") public String test() { System.out.println("手写springmvc框架..."); return "index"; } }
二,手写springMVC框架
web.xml
<web-app> <!-- Spring MVC 核心控制器 DispatcherServlet 配置 --> <!-- 自定义的下面 --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>com.qingruihappy1.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>
注解类:
package com.qingruihappy1.extspringmvc.extannotation; 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.qingruihappy1.extspringmvc.extannotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface ExtRequestMapping { String value() default ""; }
获取包下面所有类的工具类,不是重点
package com.qingruihappy1.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(); } }
重点类似于前端控制器的核心类
package com.qingruihappy1.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 javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import com.qingruihappy1.extspringmvc.extannotation.ExtController; import com.qingruihappy1.extspringmvc.extannotation.ExtRequestMapping; import com.qingruihappy1.utils.ClassUtil; /** * 自定义前端控制器<br> * 手写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 判断类上是否有注解,使用java反射机制循环遍历方法 ,判断方法上是否存在注解,进行封装url和方法对应存入集合中<br> * ####3.处理请求 重写Get或者是Post方法 <br> * ##########3.1 * 获取请求url,从urlBeans集合获取实例对象,获取成功实例对象后,调用urlMethods集合获取方法名称,使用反射机制执行 2. 作者: */ public class ExtDispatcherServlet extends HttpServlet { // springmvc 容器对象 key:类名id ,value 对象 private ConcurrentHashMap<String, Object> springmvcBeans = new ConcurrentHashMap<String, Object>(); // springmvc 容器对象 keya:请求地址 ,vlue类 private ConcurrentHashMap<String, Object> urlBeans = new ConcurrentHashMap<String, Object>(); // springmvc 容器对象 key:请求地址 ,value 方法名称 private ConcurrentHashMap<String, String> urlMethods = new ConcurrentHashMap<String, String>(); @Override public void init() throws ServletException { // 1.获取当前包下的所有的类 List<Class<?>> classes = ClassUtil.getClasses("com.qingruihappy1.controller"); // 2.将扫包范围所有的类,注入到springmvc容器里面,存放在Map集合中 key为默认类名小写,value 对象 try { findClassMVCAnnotation(classes); } catch (Exception e) { // TODO: handle exception } // 3.将url映射和方法进行关联 handlerMapping(); } @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 { // #################处理请求#################### // 1.获取请求url地址 String requestURI = req.getRequestURI(); if (StringUtils.isEmpty(requestURI)) { return; } // 2.从Map集合中获取控制对象 Object object = urlBeans.get(requestURI); if (object == null) { resp.getWriter().println(" not found 404 url"); return; } // 3.使用url地址获取方法 String methodName = urlMethods.get(requestURI); if (StringUtils.isEmpty(methodName)) { resp.getWriter().println(" not found method"); } // 4.使用java的反射机制调用方法 String resultPage = (String) methodInvoke(object, methodName); // 5.调用视图转换器渲染给页面展示 extResourceViewResolver(resultPage, req, resp); } private void extResourceViewResolver(String pageName, HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // 根路径 String prefix = "/"; String suffix = ".jsp"; req.getRequestDispatcher(prefix + pageName + suffix).forward(req, res); } private Object methodInvoke(Object object, String methodName) { try { Class<? extends Object> classInfo = object.getClass(); Method method = classInfo.getMethod(methodName); Object result = method.invoke(object); return result; } catch (Exception e) { return null; } } // 2.将扫包范围所有的类,注入到springmvc容器里面,存放在Map集合中 key为默认类名小写,value 对象 public void findClassMVCAnnotation(List<Class<?>> classes) throws ClassNotFoundException, InstantiationException, IllegalAccessException { for (Class<?> classInfo : classes) { // 判断类上是否有加上注解 ExtController extController = classInfo.getAnnotation(ExtController.class); if (extController != null) { // 默认类名是小写 String beanId = ClassUtil.toLowerCaseFirstOne(classInfo.getSimpleName()); // 实例化对象 Object object = ClassUtil.newInstance(classInfo); springmvcBeans.put(beanId, object); } } } // 3.将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 declaredAnnotation = classInfo.getAnnotation(ExtRequestMapping.class); String baseUrl = ""; if (declaredAnnotation != null) { // 获取类上的url映射地址 baseUrl = declaredAnnotation.value(); } // 4.判断方法上是否有加url映射地址 Method[] declaredMethods = classInfo.getDeclaredMethods(); for (Method method : declaredMethods) { // 判断方法上是否有加url映射注解 ExtRequestMapping methodExtRequestMapping = method.getAnnotation(ExtRequestMapping.class); if (methodExtRequestMapping != null) { String methodUrl = baseUrl + methodExtRequestMapping.value(); // springmvc 容器对象 keya:请求地址 ,vlue类 urlBeans.put(methodUrl, object); // springmvc 容器对象 key:请求地址 ,value 方法名称 urlMethods.put(methodUrl, method.getName()); } } } } }
controller类
package com.qingruihappy1.controller; import com.qingruihappy1.extspringmvc.extannotation.ExtController; import com.qingruihappy1.extspringmvc.extannotation.ExtRequestMapping; @ExtController @ExtRequestMapping("/ext") public class ExtIndexController { //ext/test/?name=122&age=6440654 @ExtRequestMapping("/test") public String test() { System.out.println("手写springmvc框架..."); return "index"; } }
index.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <html> <body> <h2>我的第一个手写springMVC的代码</h2> </body> </html>