MVC设计模式(代码的组织办法):
M:Model(数据模型[pojo,vo,po,do]、业务模型【业务逻辑代码】)
V:View视图(jsp、html)
C:Controller(Servlet)
MVC模式又叫做模型视图掌握器模式。SpringMVC属于表现层的一种框架,这个M是View与Controller之间传输的Model。
1.1.2SpringMVC和传统Servlet差异传统的Servlet:在一个项目中,每个能够处理要求的类都是一个Servlet,比如用户和订单就须要两个Servlet来处理。
SpringMVC:全局只有一个Servlet便是DispatcherServlet来管理所有的要求,只不过分发给各个不同的controller进行处理。SpringMVC只是对Servlet做了一层封装,利用起来更加方便。有点类似MyBatis对JDBC的封装。
相同:传统的Servlet和SpringMVC起浸染都是接管要求,返回相应,跳转页面。
MVC处理要求的流程
解释:通过一个Controller来处理要求,将获取到的要求参数委派给业务层,实行业务之后返回一个model然后将model返回到一个视图模板上/或者直接返回model在返回相应。
1.1.3SpringMVC流程要求流程图
处理流程:
前段掌握器DispatchServlet收到要求将要求派发给处理器映射器HandlerMapping获取到实行处理器的实行链将获取到Handler找到处理器适配器,进行处理(Controller)获取到ModelAndView/data如果返回的是ModelAndView找到视图解析器,返回一个View渲染视图返回要求1.2SpringMVC利用1.2.2 url-pattern配置及事理阐发url-pattern浸染:web做事器(tomcat)用拦截匹配规则的url要求。
配置办法:
可以配置.do .action这种, 直接可以拦截url以.do结尾的url配置成/ 可以拦截除了.jsp之外的所有的url, 包括静态资源配置成/ 拦截所有的url为啥 /不会拦截.jsp ,而会拦截静态资源
由于tomcat容器中有一个web.xml(父),自己的项目中也有一个web.xml(子),是一个继续关系。父web.xml中有一个DefaultServlet, url-pattern 是一个 /,此时我们自己的web.xml中也配置了一个/ ,覆写了父web.xml的配置。而拦截jsp的是JspServlet,我们没有重写这个配置因此不会拦截到jsp。
办理静态资源不被拦截的方法
办法一:如果静态资源文件直接放在webapp根目录下边,直接在springmvc.xml配置文件中添加<mvc:default-servlet-handler/> 配置。事理:该配置是在SpringMVC中注册一个DefaultServletHttpRequestHandler
,这个handler专门过滤静态资源的文件,如果是静态资源文件直接访问,不是的话交给框架来正常处理。
办法二:如果静态资源文件不是在webapp根目录下,可能放在resource下的话,须要通过resource来指定静态目录路径来防止拦截。
<mvc:resources location=34;classpath:/" mapping="/resources/"/><mvc:resources location="/WEB-INF/js/" mapping="/js/"/>1.2.3形参的数据接管类型根据利用demo中可以看出,返回BindingAwareModelMap、Model、Map都可以成功的跳转到jsp页面,那么到底是什么缘故原由? 通过不雅观察这些都是接口那么详细的实现类是啥?通过断点可以看到详细的实现类都是BindingAwareModelMap,因此可以知道不管创建的是Model还是Map底层都是通过BindAwareModelMap来传值的。
那么为啥Model和Map都可以传值呢?
看下BindingAwareModelMap的继续图看看。懂了吧。
1.2.4参数绑定什么是参数绑定:SpringMVC如何吸收要求参数
传统的Servlet获取参数的办法:
<packaging>war</packaging> <!--指定编译版本--> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--引入spring webmvc的依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.12.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/</path> </configuration> </plugin> </plugins> </build>
为啥Servlet的参数是一个字符串类型的?由于利用的是Http要求,超文本传输协议,因此传输的都是文本类型的数据。
SpringMVC如何吸收参数:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd "> <!--开启表明扫描--> <context:component-scan base-package="com.springmvc.demo._01SimpleUse"/> <!--配置视图解析器 浸染便是前缀+后缀--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 自动注册最得当的处理器映射器,处理器适配器(调用handler方法) --> <mvc:annotation-driven /> </beans>
SpringMVC底层便是通过对Servlet的封装实现的,只不过通过Handler将参数类型处理成Integer类型。
1.2.5SpringMVC参数吸收<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <!-- 显示名字 --> <display-name>Archetype Created Web Application</display-name> <!--配置启动的Servlet--> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--指定xml配置文件--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:01/springmvc.xml</param-value> </init-param> </servlet> <!--指定映射路径--> <servlet-mapping> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
2、吸收基本类型
Controller处理方法的形参为基本类型或者对应的包装类型并且吸收的参数为1个并且要求的参数和方法的形参同等的 话可以直接处理,但是把稳,boolean类型的只能吸收0/1;true/false
http://localhost:8080/demo/receive02?age=true
@Controller @RequestMapping("/demo") public class DemoController { / 办法一返回一个ModelAndView / @RequestMapping("/getDate") public ModelAndView getDate() { ModelAndView modelAndView = new ModelAndView(); //将数据添加到Object方便获取 modelAndView.addObject("date", LocalDateTime.now()); // 设置视图信息 modelAndView.setViewName("success"); return modelAndView; } / 办法二直接放到Map中jsp就可以获取到数据 / @RequestMapping("/getDate2") public String getDate(Map map) { map.put("date", LocalDateTime.now()); return "success"; } / 办法三 吸收一个Model / @RequestMapping(value = "/getDate3") public String getDate(Model model) { model.addAttribute("date", LocalDateTime.now()); return "success"; } }
如果对付多个参数,或者url的参数和方法形参的名字不一致的时候须要利用@RequesyParam来映射
http://localhost:8080/demo/receive03?AGE=14&NAME=hello
<%@ page language="java" isELIgnored="false" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Insert title here</title> </head> <body> 跳转成功!
做事器韶光:${date} </body> </html>
3、普通的pojo类型吸收
url的名字和形参的名字保持同等
http://localhost:8080/demo/receive04?age=14&name=hello
@RequestMapping("/receive04") public String receive04(User user){ System.out.println(user); return "success"; }
如果Pojo里边嵌套其他的pojo的话可以通过属性名.内嵌属性的办法
http://localhost:8080/demo/receive04?user.age=14&user.name=hello
但是这种办法不常常利用,由于Get要求2048kb 防止超出长度以是一样平常都会利用post要求。
4、Date类型的数据封装
封装在pojo中的韶光格式,通过@Dat儿Tim儿Format的办法来进行解析
http://localhost:8080/demo/receive04?age=14&name=hello&date=2020-12-20
@DateTimeFormat(pattern = "yyyy-MM-dd") private Date date;
单独的作为参数 在方法的形参上贴上这个表明
@RequestMapping("/receive05") public String receive05(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date){ System.out.println(date); return "success"; }
@DateTimeFormat实现事理
在绑定参数的时候判断属性值或形参值是否贴这个表明如果贴了这个表明就确定须要将当前String类型转换成Date类型,如果不该用@DateTimeFormat怎么实现韶光格式的参数封装。
自定义转换器
//自定义转换器 import org.springframework.core.convert.converter.Converter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateConverter implements Converter<String, Date> { @Override public Date convert(String source) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); try { return simpleDateFormat.parse(source); } catch (ParseException e) { e.printStackTrace(); } return null; } }
注册到Spring中
<mvc:annotation-driven conversion-service="myConverter" /> <bean id="myConverter" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.springmvc.demo._01SimpleUse.convernt.DateConverter"></bean> </set> </property> </bean>
利用
@RequestMapping("/receive06") public String receive06(Date date){ System.out.println(date); return "success"; } 测试url http://localhost:8080/demo/receive06?date=2020-12-20
1.2.6SpringMVC的Rest风格要求
利用postman仿照不同的要求办法,如果利用提交可以利用_method隐蔽域的办法来改换方法类型
localhost:8080/restFul/demo/get/ 1212@RequestMapping(path = "/demo/get/{id}", method= RequestMethod.GET) public String getDemo(@PathVariable("id") Integer id){ System.out.println(id); return "success"; } localhost:8080/restFul/demo/post?name=康康&id=1212@RequestMapping(path = "/demo/post", method= RequestMethod.POST) public String postDemo(@RequestParam("id") Integer id, @RequestParam("name") String name){ System.out.println(id); System.out.println(name); return "success"; } localhost:8080/restFul/demo/put/1212/12@RequestMapping(path = "/demo/put/{id}/{age}", method= RequestMethod.PUT) public String getPut(@PathVariable("id") Integer id, @PathVariable("age") Integer age){ System.out.println(age); return "success"; } localhost:8080/restFul/demo/delete/1212@RequestMapping(path = "/demo/delete/{id}", method= RequestMethod.DELETE) public String getDelete(@PathVariable("id") Integer id){ System.out.println(id); return "success"; }
乱码问题办理:通过web.xml的办法来办理
<!--springmvc供应的针对post要求的编码过滤器--> <filter> <filter-name>encoding</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> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/</url-pattern> </filter-mapping>
1.2.7Json办法要求
1、后端接管,后端接管必须利用表明@RequestBody来进行接管,必须利用post办法来要求,由于只有post要求才会有要求体。
2、后端相应json,须要在方法上贴上@ResponseBody , 贴上这个表明之后返回的便是json格式的数据并且不会跳转视图。
SpringMVC默认利用的是jackson因此须要先引入 pom
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.4.0</version> </dependency>
前端编写要求
<script> $(function () { $("#ajaxBtn").bind("click",function () { // 发送ajax要求 $.ajax({ url: '/json/demo', type: 'POST', data: '{"id":"1","name":"李四"}', contentType: 'application/json;charset=utf-8',//指定后端接管类型为json类型 dataType: 'json', //指定前端接管类型为json类型 success: function (data) { alert(data.name); } }) }) }) </script>
后端接管相应要求
@Controller @RequestMapping("/json") public class JsonController { @RequestMapping(value = "/demo", method = RequestMethod.POST) @ResponseBody public JsonPojo jsonDemo(@RequestBody JsonPojo jsonPojo){ System.out.println(jsonPojo); return jsonPojo; } }
1.3SpringMVC高等运用1.3.1监听器、拦截器、过滤器
servlet:处理Request要乞降Response相应。
过滤器(Filter):对Request要求起到过滤的作⽤,作⽤在Servlet之前,如果配置为/可以对所有的资源访问(servlet、js/css静态资源等)进⾏过滤处理。
监听器(Listener):实现了javax.servlet.ServletContextListener 接⼝的做事器端组件,它随Web应⽤的启动⽽启动,只初始化⼀次,然后会⼀直运⾏监视,随Web应⽤的停⽌⽽销毁。
监听器浸染:
可以做一些初始化事情,web应⽤中spring容器启动ContextLoaderListener。监听web中的特定事宜,⽐如HttpSession,ServletRequest的创建和销毁;变量的创建、销毁和修正等。可以在某些动作前后增加处理,实现监控,⽐如统计在线⼈数,利⽤HttpSessionLisener等。拦截器(Interceptor):是SpringMVC、Struts等表现层框架⾃⼰的,不会拦截jsp/html/css/image的静态访问等,只会拦截访问的掌握器⽅法(Handler)。
拦截器(interceptor)浸染机遇:
在Handler业务逻辑执⾏之前拦截⼀次在Handler逻辑执⾏完毕但未跳转⻚⾯之前拦截⼀次在跳转⻚⾯之后拦截⼀次配置位置:从配置的⻆度也能够总结创造:serlvet、filter、listener是配置在web.xml中的,⽽interceptor是配置在表现层框架⾃⼰的配置⽂件中的。
拦截器基本利用自定义拦截器须要实现InterceptorHandler
public class MyInterceptor01 implements HandlerInterceptor { / 在handler方法业务逻辑实行之前, 常用与权限掌握 @return 返回值boolean代表是否放行,true代表放行,false代表中止 / @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor01 preHandle......"); //TODO 权限掌握 return true; } / 会在handler方法业务逻辑实行之后尚未跳转页面时实行 @Param modelAndView 封装了视图和数据,此时尚未跳转页面呢,你可以在这里针对返回的数据和视图信息进行修正 / @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyInterceptor01 postHandle......"); } / 页面已经跳转渲染完毕之后实行 @param ex 可以在这里捕获非常 / @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyInterceptor01 afterCompletion......"); } }
通过xml配置
<!--配置拦截器--> <mvc:interceptors> <!--蓝节所有的handler--> <!--<bean class="com.springmvc.demo._02advince.interceptor.MyInterceptor01"/>--> <mvc:interceptor> <!--匹配到的uri--> <mvc:mapping path="/"/> <!--打消的uri--> <mvc:exclude-mapping path="/demo/"/> <bean class="com.springmvc.demo._02advince.interceptor.MyInterceptor01"/> </mvc:interceptor> </mvc:interceptors>
思考:如果项目中配置了多个拦截器的话,实行顺序是如何的?
拦截器的实行顺序是根据配置在xml中的配置顺序决定的,
<interceptors> <interceptor01> <interceptor02> </ interceptors> 实行顺序是: Interceptor01 preHandle...... Interceptor02 preHandle...... //业务逻辑 Interceptor02 postHandle...... Interceptor01 postHandle...... Interceptor02 afterCompletion...... Interceptor01 afterCompletion......
1.3.2文件上传
理解即可,现在在实战的项目中文件都会上传到文件做事器上的。
客户端哀求:必须post要求,要求办法必须是multipart, type为file
做事器哀求:用MultipartFile来吸收 进行存储
客户端:
<form method="post" enctype="multipart/form-data" action="/demo/uploadFile"> <input type="file" name="multipartFile"/> <input type="submit" value="上传"/> </form>
做事端:
须要引入POM
<!--文件上传所需坐标--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
Handler
@Controller @RequestMapping("/demo") public class FileUploadController { @RequestMapping(value = "/uploadFile" , method = RequestMethod.POST) public String uploadFile(MultipartFile multipartFile, HttpSession session) throws IOException { //存储在项目的webapp的根目录下,须要找到绝对路径, 通过Session String realPath = session.getServletContext().getRealPath("/WEB-INF"); //根据日期存储文件避免单个目录下文件数量过多 String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); //获取到文件判断是否存在不存在须要进行创建 File file = new File(realPath + "/" + datePath); if (!file.exists()) { file.mkdir(); } //获取到文件名 天生一个随机的文件名避免重复 String fileName = UUID.randomUUID().toString().replaceAll("-", ""); String extendName = multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().indexOf(".")); fileName = fileName + extendName; //存储文件 multipartFile.transferTo(new File(file, fileName)); //返回 return "success fileName:" + fileName; } }
配置文件解析器,SpringMVC配置文件中
<!--文件上传解析器 这个id必须是固定的 SpringMVC框架可以自动识别到--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--设置上传文件大小上限,单位是字节,-1代表没有限定也是默认的--> <property name="maxUploadSize" value="5000000"/> </bean>
1.3.3SpringMVC非常处理
自定义非常处理器须要实现HandlerExceptionResolver也可以利用表明的办法进行处理。
全局非常处理,@ControllerAdvince这个表明紧张利用的是AOP的办法来进行处理的。
@ControllerAdvice public class GlobalExceptionResolver { @ExceptionHandler public ModelAndView handleException(RuntimeException runtimeException){ ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("msg",runtimeException); modelAndView.setViewName("error"); return modelAndView; } }
1.3.4重定向携带参数
转发和重定向差异:
1、转发是一个url要求,经由层层往下通报,过程中参数不会丢失
2、重定向是在一个要求如果A完成不了就会重定向到B重定向会导致数据丢失。
重定向之后的handler须要利用@ModelAttribute获取到属性值。
@Controller public class RedirectController { @RequestMapping("/handlerA") public String handlerA(String name, RedirectAttributes redirectAttributes){ redirectAttributes.addFlashAttribute("name", name); System.out.println(name); return "redirect:handlerB"; } @RequestMapping("/handlerB") public String handlerB(@ModelAttribute("name") String name){ System.out.println(name); return "success"; } }
1.5SpringMVC源码剖析1.5.1MVC九大组建
名称浸染HandlerMapping(处理器映射器)[核心]便是处理要求的url找到对应的controller根据(@RequestMapping)。HandlerAdapter(处理器适配器)[核心]对上一步找到的controller进行适配,参数适配,类型适配(controller实现接口/@RequestMapping)。HandlerExceptionResolver(非常解析器)处理非常,当发生非常的时候扑捉到非常进行处理非常 比如跳转500页面。ViewResolver(视图解析器)[核心]在利用过程中常常在Spring配置文件中配置前缀和后缀方便在controller层直接写去世视图的路径。RequestToViewNameTranslator根据视图解析器和返回的路径找到对应视图的名字。LocaleResolver国际化处理,国际化措辞、韶光等处理。ThemeResolver主题处理,现在前后端分离很少利用。MultipartResolver这个组件专门处理上传的文件。FlashMapManager用于要求转发,处理携带的参数。
1.5.2SpringMVC初始化
初始化实行流程
源码剖析 容器启动调用HttpServlet.init方法 HttpServletBean.init方法 调用initServletBean方法 FrameworkServlet.initServletBean方法 FrameworkServlet.initWebApplicationContext方法 FrameworkServlet.createWebApplicationContext创建一个WebApplicationContext方法 FrameworkServlet.configureAndRefreshWebApplicationContext配置并刷新webContext wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));//添加一个容器刷新监听器 wac.refresh()//实行刷新 AbstractApplicationContext.finishRefresh方法中发布刚刚注册的监听器事宜publishEvent getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType) //通过多播器进行发布事宜 SourceFilteringListener. onApplicationEvent方法 DispatcherServlet.onRefresh方法 initStrategies方法<!--初始化九大组件--> initHandlerMappings<!--将url和Method绑定在一起的-->
String ageStr = request.getParameter(“age”); Integer age = Integer.parseInt(ageStr);
initMultipartResolver 初始化文件上传处理器
private void initMultipartResolver(ApplicationContext context) { try { //直接从容器中获取id为multipartResolver的Multipart解析器 因此在注册的时候id一定要注册为multipartResolver this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); } catch (NoSuchBeanDefinitionException ex) { // Default is no multipart resolver. this.multipartResolver = null; } }
initHandlerMappings初始化handlerMapping
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; //是否须要查找所有的处理器映射器 默认为true 可以在web.xml中查找到对应的处理器映射器 if (this.detectAllHandlerMappings) { // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<>(matchingBeans.values()); // We keep HandlerMappings in sorted order. AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { try { HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default HandlerMapping later. } } // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. if (this.handlerMappings == null) { //如果没有找到处理器映射器的话直接获取到一个默认的处理器映射器 没有自动注入的话都会走到这一步 this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerMappings declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } } }
初始化默认的处理器映射器
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { //获取到接口的名字 String key = strategyInterface.getName(); //从默认的配置文件中获取到实现类的名字 String value = defaultStrategies.getProperty(key); if (value != null) { //通过反射进行初始化并返回 String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> strategies = new ArrayList<>(classNames.length); for (String className : classNames) { try { Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); } catch (ClassNotFoundException ex) { throw new BeanInitializationException( "Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex); } catch (LinkageError err) { } } return strategies; } else { return new LinkedList<>(); } }
defaultStrategies 默认的初始化 在DispatcherServlet的static静态代码块中
@RequestMapping("/Demo") public String getDemo(Integer demo){ System.out.println(demo); return "demo"; }
其他的组件初始化办法跟initHandlerMappings同等。
1.5.3SpringMVC运行时源码剖析DispatcherServlet继续图
解释:当要求过来的时候会调用HttpServlet中的doPost和doGet方法,doGet和doPost方法都是FrameworkServlet 中被复写,并且都调用process处理方法。
1、吸收原生的参数SpringMVC只是对Servlet的封装因此也支持原生的参数吸收@RequestMapping("/receive01") public String receive01(HttpServletRequest request, HttpServletResponse response, HttpSession session){ System.out.println(request); System.out.println(response); System.out.println(session); return "success"; }
在process方法中核心处理doService
doDispatcher核心处理方法先通过打断点的不雅观察调用栈的办法来确定实行机遇。
实行Controller方法中的机遇
通过打断点不雅观察到在doDispatcher中ha.handle是实行Handler方法,这里的ha类型是
RequestMappingHndlerAdapter。
跳转视图的机遇
通过打断点可以查看到跳转视图是在processDispatcherResult方法。
剖析doDispatcher紧张流程
doDispatcher紧张核心步骤:
getHandler获取方法实行器链handlerExecutionChaingetHandlerAdapter 获取处理器适配器ha.handle 实行controller方法processDispatchResult 渲染视图getHandler方法protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { //遍历handlerMappings属性通过mapping.getHandler来获取到处理器 for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
mapping.getHandler
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //获取到实行的方法工具包装成一个handlerMethod工具 Object handler = ①getHandlerInternal(request); if (handler == null) { handler = getDefaultHandler(); //如果不存在的话从默认的defaultHandler属性中返回 } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { //如果是string类型的处理器的话就从ioc容器中获取 String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } //根据获取到的handler获取到实行器链 //判断是拦截器还是实行器 分别添加到实行器链中 HandlerExecutionChain executionChain = ②getHandlerExecutionChain(handler, request); //根据header中止定是否跨域 如果跨域就从跨域的配置中获取到实行器链并返回 跨域 header中包含Origin if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }
//获取到实行的方法protected HandlerMethod ①getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); //获取到要求的uri this.mappingRegistry.acquireReadLock(); //上锁ReentrantReadWriteLock 可重入的读写锁由于是单例防止其他线程获取到 try { //通过uri获取到方法处理工具 mappingRegistry这个属性中查找 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { //开释锁 this.mappingRegistry.releaseReadLock(); } } protected HandlerExecutionChain ②getHandlerExecutionChain(Object handler, HttpServletRequest request) { //判断handler是否是实行器链 如果是的 话强转不是的话new一个实行器链 HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); //获取到uri String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); for (HandlerInterceptor interceptor : this.adaptedInterceptors) { //遍历所有的拦截器 //如果拦截器属于 MappedInterceptor并且方法与当前实行的方法匹配就添加到实行器链 if (interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { //否则直接将handler(方法)添加到实行器链中 chain.addInterceptor(interceptor); } } return chain; }
getHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { //从初始化好的handlerAdapters属性中去判断是否是支持 一样平常请的要求判断当前的handler是否HandlerMethod 并且框架是否支持默认为true支持 终极会返回一个RequestMappingHandlerAdapter来进行处理// handlerAdapters这个属性中还包括HttpRequestAdapter 原生的Serrvlet要求,SimpleControllerHandler 通过集成Controller接口的办法处理 for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
▲handler
这一步紧张是实行方法,会实行到RequestMappingHandlerAdapter#handleInternal方法。
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; //校验入参 紧张校验获取到的实行方法存在并且session也存在 checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required. //是否须要同步实行,由于session是共享的可能会有线程安全的问题 默认不须要 if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { //通过session获取到一个随机的key作为锁进行同步实行 Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... //真正实行方法 mav = invokeHandlerMethod(request, response, handlerMethod); } //相应体中不包括Cache-Control的话在判断是否有session存在session属性的话进行缓存 if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { //准备相应工具 prepareResponse(response); } } return mav; }
invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { //封装了一个ServletWebRequest工具 ServletWebRequest webRequest = new ServletWebRequest(request, response); try { //获取一个数据绑定工厂 用来创建WebDataBinder WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); //获取一个模型工厂用来添补model的属性 【创建套路跟上边的差不多先从缓存中获取是否存在,不存在的话在new】 ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); //将当前的handlerMethod封装成一个ServletInvocableHandlerMethod 只是纯挚的封装 方便下边封装更多的属性 ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { //设置一个方法参数处理解析器HandlerMethodArgumentResolvers 这个解析器是在初始化的时候new出来的 invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { //初始化一个方法返回处理器HandlerMethodReturnValueHandlers invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } //设置上边创建的数据绑定器 invocableMethod.setDataBinderFactory(binderFactory); //设置参数名字创造器 invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); //new 了一个视图创造器 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); //往这个视图创造器中添加一些属性 flashMap mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); //往modelFactory中初始化了一个模型 modelFactory.initModel(webRequest, mavContainer, invocableMethod); //设置是否须要忽略默认的模型 默认为false mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); //创建一个异步要求为Servlet3.0准备的 AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); //设置超时时间 //获取到异步管理器 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); //从异步处理器中止定是否已经存在当前的实行结果默认不存在 if (asyncManager.hasConcurrentResult()) { //存在的话直接获取到结果 Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(logger, traceOn -> { String formatted = LogFormatUtils.formatValue(result, !traceOn); return "Resume with async result [" + formatted + "]"; }); //包装一个结果 invocableMethod = invocableMethod.wrapConcurrentResult(result); } //实行处理方法 ① invocableMethod.invokeAndHandle(webRequest, mavContainer); //判断当前异步管理器是否已经开始处理 if (asyncManager.isConcurrentHandlingStarted()) { return null; } //获取到视图并返回 return ②getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }
invocableMethod.invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //实行方法 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); //设置相应状态 if (returnValue == null) { //如果返回值为null进行分外处理 if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); //判断是否修正过,相应状态是否正常 mavContainer.setRequestHandled(true); //设置要求已经处理标记 return; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); //如果相应值有值的话设置已经处理 return; } mavContainer.setRequestHandled(false); try { //处理相应结果 获取到视图解析器 对返回的名字进行解析,添加上前缀和后缀并封装在mavContainer.setViewName(viewName)中//以及是否属于方法的重定向进行判断处理 this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { throw ex; } }
invokeForRequest
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //获取到方法实行的参数 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); //实行方法 return doInvoke(args); //直接通过反射调用方法 }
getMethodArgumentValues获取方法实行参数
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //获取到方法的形参 从parameters中获取到 meters是在初始化的时候就已经对方法进行解析了 MethodParameter[] parameters = getMethodParameters(); //如果参数为空的话直接返回 if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } //创建一个数组 Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; //初始化参数名字创造器 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); //默认绑定 一些实现设置好的值比如Map HttpServletRequest等 args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (!this.resolvers.supportsParameter(parameter)) { //如果不支持的话抛出非常 throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { //根据参数和和要求进行封装参数 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { } } return args; }
resolveArgument 方法
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { // parameter根据parameter从缓存中argumentResolverCache中获取参数 HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null) { throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first."); } //解析参数 return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); }
调用AbstractNamedValueMethodArgumentResolver 类的resolveArgument进行解析
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { //获取到绑定的参数 为null MethodParameter nestedParameter = parameter.nestedIfOptional(); //获取到形参的名字 比如name NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); Object resolvedName = resolveStringValue(namedValueInfo.name); //真正解析出来形参的名字name if (resolvedName == null) { throw new IllegalArgumentException( "Specified name must not resolve to null: [" + namedValueInfo.name + "]"); } //通过名字去解析参数 去名字解析出值 Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest); if (arg == null) { //如果值为null的话解析字符串值 if (namedValueInfo.defaultValue != null) { arg = resolveStringValue(namedValueInfo.defaultValue); } else if (namedValueInfo.required && !nestedParameter.isOptional()) { handleMissingValue(namedValueInfo.name, nestedParameter, webRequest); } //处理null值 arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType()); } else if ("".equals(arg) && namedValueInfo.defaultValue != null) { arg = resolveStringValue(namedValueInfo.defaultValue); } if (binderFactory != null) { //绑定web参数 WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); try { arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter); } catch (ConversionNotSupportedException ex) { } } //处理值 @ModelAttribute表明是否须要分外处理 handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; }
processDispatchResult
视图渲染
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; //判断非常部位null的话跳转到非常页面 if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // Did the handler return a view to render? //视图不为空的话就渲染 并且视图没有被清理 if (mv != null && !mv.wasCleared()) { render(mv, request, response); if (errorView) { //如果是缺点视图就清理属性 WebUtils.clearErrorRequestAttributes(request); } } else { //日志打印 } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } }
Render
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { //本地化处理 Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); View view; //获取视图名字 String viewName = mv.getViewName(); if (viewName != null) { // We need to resolve the view name. 解析视图名字 view = ①resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { // No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } //渲染视图 ②view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { throw ex; } } resolveViewName 解析视图名字protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { //进行循环遍历解析视图名字 if (this.viewResolvers != null) { for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } } return null; }
解析视图名字
public View resolveViewName(String viewName, Locale locale) throws Exception { //缓存 if (!isCache()) { return createView(viewName, locale); } else { Object cacheKey = getCacheKey(viewName, locale); View view = this.viewAccessCache.get(cacheKey); if (view == null) { synchronized (this.viewCreationCache) { view = this.viewCreationCache.get(cacheKey); if (view == null) { // Ask the subclass to create the View object. 创建一个view工具 view = createView(viewName, locale); if (view == null && this.cacheUnresolved) { view = UNRESOLVED_VIEW; } //添加到缓存 if (view != null) { this.viewAccessCache.put(cacheKey, view); this.viewCreationCache.put(cacheKey, view); } } } } else { } return (view != UNRESOLVED_VIEW ? view : null); } }
调用UrlBasedViewResolver中的createView创建视图
protected View createView(String viewName, Locale locale) throws Exception { // If this resolver is not supposed to handle the given view, // return null to pass on to the next resolver in the chain. if (!canHandle(viewName, locale)) { return null; } // Check for special "redirect:" prefix. 如果以redirect开头就创建一个重定向视图 if (viewName.startsWith(REDIRECT_URL_PREFIX)) { String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); String[] hosts = getRedirectHosts(); if (hosts != null) { view.setHosts(hosts); } return applyLifecycleMethods(REDIRECT_URL_PREFIX, view); } // Check for special "forward:" prefix. if (viewName.startsWith(FORWARD_URL_PREFIX)) { //如果以forward开头创建转发视图 String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); InternalResourceView view = new InternalResourceView(forwardUrl); return applyLifecycleMethods(FORWARD_URL_PREFIX, view); } // Else fall back to superclass implementation: calling loadView. return super.createView(viewName, locale); //创建真正的视图}终极调用父类的UrlBasedViewResolver buildView创建视图protected AbstractUrlBasedView buildView(String viewName) throws Exception { //获取到InternalResourceView 视图 Class<?> viewClass = getViewClass(); Assert.state(viewClass != null, "No view class"); //反射实例化工具 AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass); //获取到视图解析器拼装出来物理地址 view.setUrl(getPrefix() + viewName + getSuffix()); //获取到contentType String contentType = getContentType(); if (contentType != null) { view.setContentType(contentType); } //设置要求高下文属性 view.setRequestContextAttribute(getRequestContextAttribute()); //设置属性Map view.setAttributesMap(getAttributesMap()); //是否须要暴露路径变量 默认为null Boolean exposePathVariables = getExposePathVariables(); if (exposePathVariables != null) { view.setExposePathVariables(exposePathVariables); } Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes(); if (exposeContextBeansAsAttributes != null) { view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes); } //暴露高下文beanName String[] exposedContextBeanNames = getExposedContextBeanNames(); if (exposedContextBeanNames != null) { view.setExposedContextBeanNames(exposedContextBeanNames); } return view; }
②render渲染数据
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //将所有须要暴露的字段合并成Map Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); //入参和出到场备好 判断是否须要天生下载的Pragma 和 Cache-Control prepareResponse(request, response); //渲染并合并视图 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }