1.SpringMVC
1.1SpringMVC简介
1.1.1MVC框架介绍
在项目中按照代码架构划分:表现层(view视图+Controller)、业务层(Service)、Dao层
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处理请求的流程
SprringMVC进阶学习说明:通过一个Controller来处理请求,将获取到的请求参数委派给业务层,执行业务之后返回一个model然后将model返回到一个视图模板上/或者直接返回model在返回响应。
1.1.3SpringMVC流程
请求流程图
SprringMVC进阶学习处理流程:
- 前段控制器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="classpath:/" mapping="/resources/"/>**
- <mvc:resources location="/WEB-INF/js/" mapping="/js/"/>**
1.2.3形参的数据接受类型
SprringMVC进阶学习根据使用demo中可以看出,返回BindingAwareModelMap、Model、Map都可以成功的跳转到jsp页面,那么到底是什么原因? 通过观察这些都是接口那么具体的实现类是啥?通过断点可以看到具体的实现类都是BindingAwareModelMap,因此可以知道不管创建的是Model还是Map底层都是通过BindAwareModelMap来传值的。
那么为啥Model和Map都可以传值呢?
看下BindingAwareModelMap的继承图看看。懂了吧。
SprringMVC进阶学习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是配置在表现层框架⾃⼰的配置⽂件中的。
SprringMVC进阶学习拦截器基本使用
自定义拦截器需要实现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
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继承图
SprringMVC进阶学习说明:当请求过来的时候会调用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方法中的时机
SprringMVC进阶学习通过打断点观察到在doDispatcher中ha.handle是执行Handler方法,这里的ha类型是
RequestMappingHndlerAdapter。
跳转视图的时机
SprringMVC进阶学习通过打断点可以查看到跳转视图是在processDispatcherResult方法。
分析doDispatcher主要流程
doDispatcher主要核心步骤:
- getHandler获取方法执行器链handlerExecutionChain
- getHandlerAdapter 获取处理器适配器
- 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);
}
网友评论