SpringMVC简介
什么是MVC
MVC是一种软件架构的思想,将软件按照模型、识图、控制器来划分
M为Model,模型层,指工程中的Javabean,作用是处理数据
Javabean分为两类:
实体类bean:专门存储业务数据的
业务处理bean:指service或dao对象,专门用于处理业务逻辑和数据访问
V为View,视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据
C为Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器
MVC的工作流程
用户通过视图层发送请求到服务器,在服务器中请求被控制层接收,控制层调用响应的模型层处理请求,处理完毕将结果返回到控制层,控制层再根据请求处理的结果找到相应的视图,渲染数据后最终响应给浏览器
注:三层架构分为表述层(表示层)、业务逻辑层、数据访问层,表述层表示前端页面和后端servlet
什么是SpringMVC
SpringMVC是Spring的一个后续产品,是它的一个子项目
SpringMVC是Spring为表述层开发提供一套完备的解决方案
特点
Spring家族原生产品,与IOC容器等基础设施无缝对接
基于原生的servlet,通过功能强大的DispatcherServlet,对请求和响应进行统一处理
表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
代码清晰简洁,大幅度提高开发效率
内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
性能卓著,尤其适合现代大型、超大型互联网项目要求
环境配置
web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <servlet > <servlet-name > SpringMVC</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <init-param > <param-name > contextConfigLocation</param-name > <param-value > classpath*:spring/*.xml</param-value > </init-param > <load-on-startup > 1</load-on-startup > </servlet > <servlet-mapping > <servlet-name > SpringMVC</servlet-name > <url-pattern > /</url-pattern > </servlet-mapping >
SpringMVC配置文件
1 2 3 4 5 6 7 <context:component-scan base-package ="com.zhuweitung.springmvc" /> <bean id ="internalResourceViewResolver" class ="org.springframework.web.servlet.view.InternalResourceViewResolver" > <property name ="prefix" value ="/WEB-INF/" /> <property name ="suffix" value =".jsp" /> </bean >
基础功能
@RequestMapping注解
功能
将请求和处理请求的控制器方法关联起来,建立映射关系
位置
标识一个类:设置映射请求的请求路径的初始信息
标识一个方法:设置映射请求的请求路径的具体信息
属性
name:映射名称标识
value/path:请求路径,用数组可以表示多个请求路径,请求路径不匹配时不会报404错误
method:请求方式,GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE,请求方式不匹配时会报405错误
@GetMapping:get请求方式的RequestMapping的派生注解
@PostMapping:post请求方式的RequestMapping的派生注解
@PutMapping:put请求方式的RequestMapping的派生注解
@DeleteMapping:delete请求方式的RequestMapping的派生注解
params:通过请求的请求参数来匹配请求映射,可以通过四种表达式设置请求参数和请求映射的匹配关系,请求参数不匹配时会报400错误
“param”:要求请求映射所匹配的请求必须携带param请求参数
“!param”:要求请求映射所匹配的请求必须不能携带param请求参数
“param=value”:要求请求映射所匹配的请求必须携带param请求参数,且参数值为value
“param!=value”:要求请求映射所匹配的请求必须携带param请求参数,且参数值不为value
headers:通过请求的请求头信息匹配请求映射,可以通过四种表达式设置请求头信息和请求映射的匹配关系,请求头信息不匹配时不会报404错误
“header”:要求请求映射所匹配的请求必须携带header请求头信息
“!header”:要求请求映射所匹配的请求必须不能携带header请求头信息
“header=value”:要求请求映射所匹配的请求必须携带header请求头信息,且信息为value
“header!=value”:要求请求映射所匹配的请求必须携带header请求头信息,且信息不为value
Ant风格路径
?:表示任意单个字符(除?和/),如@GetMapping(“he?lo”)可以匹配 /hello、/healo等路径
*:表示0个若多个字符
**:表示任意的一层或多层目录,只能使用/**/xxx的方式
路径占位符
原始方式:/deleteUser?id=1
rest方式:/deleteUser/1
@RequestMapping注解的value属性中通过占位符{xxx}
表示传输的数据,再通过@PathVariable
注解,将占位符所表示的数据复制给控制其方法的形参,当请求携带占位符数量不一致时会报404错误
1 2 3 4 @GetMapping("demo/{id}/{name}") public String demo (HttpServletRequest request, @PathVariable String id, @PathVariable String name) { return "demo" ; }
SpringMVC获取请求参数
通过ServletAPI获取
1 2 3 4 5 6 7 @GetMapping("getParam") @ResponseBody public String getParam (HttpServletRequest request) { System.out.println(request.getParameter("name" )); System.out.println(Arrays.toString(request.getParameterValues("hobby" ))); return "success" ; }
通过控制器形参获取
请求参数名必须和形参名称保持一致
1 2 3 4 5 6 7 8 @GetMapping("getParam2") @ResponseBody public String getParam2 (String id, String name, String[] hobby) { System.out.println(id); System.out.println(name); System.out.println(Arrays.toString(hobby)); return "success" ; }
@RequestParam注解
@RequestParam是将请求参数和控制器方法的形参创建映射关系
@RequestParam注解一共有三个属性:
value:指定为形参赋值的请求参数的参数名
required:设置是否必须传输此请求参数,默认为true,默认情况下当value所指定的请求参数没有传输且未设置默认值时会报400错误
defaultValue:不管required属性值为true或false,当value所指定的请求参数没有传输时,则使用默认值为形参赋值
@RequestHeader是将请求头信息和控制器方法的形参创建映射关系
@RequestHeader注解的属性和用法与@RequestParam注解一致
@CookieValue注解
@CookieValue是将cookie数据和控制器方法的形参创建映射关系
@CookieValue注解的属性和用法与@RequestParam注解一致
通过POJO获取请求参数
可以在控制器方法的形参位置设置一个实体类型的形参,若浏览器传输的请求参数和实体类中的属性名一致,那么请求参数就会为此属性赋值
解决获取请求参数的乱码问题
get请求乱码需要修改tomcat的配置,conf/server.xml
中给对应的Connector标签增加URIEncoding="UTF-8"
属性;
post请求乱码需要在在DispatcherServlet调度请求之前处理请求的编码问题,所以需要在Filter中做处理
在web.xml中配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 <filter > <filter-name > CharacterEncodingFilter</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 > CharacterEncodingFilter</filter-name > <url-pattern > /</url-pattern > </filter-mapping >
域对象共享数据
使用ServletAPI向request域对象共享数据
1 2 3 4 5 6 @GetMapping("demo/{id}/{name}") public String demo (HttpServletRequest request, @PathVariable String id, @PathVariable String name) { request.setAttribute("id" , id); request.setAttribute("name" , name); return "demo" ; }
使用ModelAndView向request域对象共享数据
1 2 3 4 5 6 7 8 @GetMapping("modelAndView") public ModelAndView modelAndView () { ModelAndView view = new ModelAndView (); view.addObject("id" , "model" ); view.addObject("name" , "view" ); view.setViewName("demo" ); return view; }
使用Model向request域对象共享数据
1 2 3 4 5 6 @GetMapping("model") public String model (Model model) { model.addAttribute("id" , "2" ); model.addAttribute("name" , "model" ); return "demo" ; }
使用Map向request域对象共享数据
1 2 3 4 5 6 @GetMapping("map") public String map (Map<String, Object> map) { map.put("id" , "3" ); map.put("name" , "map" ); return "demo" ; }
使用ModelMap向request域对象共享数据
1 2 3 4 5 6 @GetMapping("modelMap") public String modelMap (ModelMap modelMap) { modelMap.put("id" , "4" ); modelMap.put("name" , "modelMap" ); return "demo" ; }
Model、ModelMap、Map的关系
运行类型都是BindingAwareModelMap
以上几种向request域共享对象的方法最终都会在DispatcherServlet的doDispatch方法中被处理为ModelAndView
向session域共享数据
1 2 3 4 5 6 @GetMapping("session") public String session (HttpSession session) { session.setAttribute("id" , "5" ); session.setAttribute("name" , "session" ); return "demo" ; }
向application域共享数据
1 2 3 4 5 6 7 @GetMapping("application") public String application (HttpServletRequest request) { ServletContext application = request.getServletContext(); application.setAttribute("id" , "6" ); application.setAttribute("name" , "application" ); return "demo" ; }
SpringMVC的视图
SpingMVC中的视图是View接口,视图的作用是渲染数据,将模型Model中的数据展示给用户
SpringMVC视图的种类很多,默认有转发视图和重定向视图
当工程引入jstl的依赖,转发视图会自动转换为JstlView
若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析之后所得到的是ThymeleafView
转发视图
SpringMVC中默认的转发视图是InternalResourceView
(当配置的视图解析器不为InternalResourceViewResolver时可以debug看到差别,不然都是InternalResourceView)
SpringMVC中创建转发视图的情况:
当控制器方法中所设置的视图名称以"forward:"为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部分作为最终路径通过转发的方式实现跳转
1 2 3 4 @GetMapping("forward") public String forward () { return "forward:/view/default" ; }
重定向视图
SpringMVC中默认的重定向视图是RedirectView
当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩余部分作为最终路径通过重定向的方式实现跳转
1 2 3 4 @GetMapping("redirect") public String redirect () { return "redirect:/view/default" ; }
视图控制器
当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示
1 2 3 4 <mvc:view-controller path ="/testView" view-name ="success" > </mvc:view-controller > <mvc:annotation-driven />
RESTful
简介
REST:Re presentational S tate T ransfer,表现层资源状态转移
RESTful的实现
HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE
它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源
REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性
操作
传统方式
REST风格
查询操作
getUserById?id=1
user/1–>get请求方式
保存操作
saveUser
user–>post请求方式
删除操作
deleteUser?id=1
user/1–>delete请求方式
更新操作
updateUser
user–>put请求方式
通过过滤器处理delete和put请求
由于浏览器只支持发送get和post方式的请求,发送put和delete请求需要使用SpringMVC 提供的HiddenHttpMethodFilter
将 POST 请求转换为 DELETE 或 PUT 请求
web.xml中配置过滤器(需要配置在字符集过滤器后面,否则字符集过滤器就会失效)
1 2 3 4 5 6 7 8 9 <filter > <filter-name > HiddenHttpMethodFilter</filter-name > <filter-class > org.springframework.web.filter.HiddenHttpMethodFilter</filter-class > </filter > <filter-mapping > <filter-name > HiddenHttpMethodFilter</filter-name > <url-pattern > /*</url-pattern > </filter-mapping >
静态资源访问
在spring配置文件中配置:
1 2 3 4 <mvc:default-servlet-handler /> <mvc:annotation-driven />
第一个配置表示开启默认的Servlet请求处理器,而对静态资源的访问就是通过容器默认servlet处理(DefaultServletHttpRequestHandler)的,资源的请求过程为:先到DispatcherServlet处理,发现无法处理,就到默认Servlet处理
当开启第一个配置之后@RequestMapping注解方式配置的处理器就会失效,这时就需要加上第二个配置,让MVC注解生效
扩展功能
HttpMessageConverter
HttpMessageConverter,报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文
HttpMessageConverter提供了两个注解和两个类型:@RequestBody,@ResponseBody,RequestEntity,ResponseEntity
@RequestBody
@RequestBody可以获取请求体
,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值
@ResponseBody
@ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体
响应到浏览器
处理Json的步骤
引入json依赖,如jackson、fastjson
在spring配置文件中开启mvc注解驱动(<mvc:annotation-driven/>)
HandlerAdaptor中会自动装配一个消息转换器,如引入的是jackson,则为MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为json格式的字符串
1 2 3 4 5 @GetMapping("user/{name}") @ResponseBody public List<User> updateName (@PathVariable String name) { return userService.findAllByName(name); }
RequestEntity
RequestEntity封装请求报文
的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息
ResponseEntity
ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文
@RestController
@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解
文件上传和下载
文件下载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @GetMapping("download") public void download (HttpServletRequest request, HttpServletResponse response) throws IOException { ServletContext ctx = request.getServletContext(); String filename = "logo.png" ; String filePath = "/static/img/" + filename; InputStream is = ctx.getResourceAsStream(filePath); response.setContentType(ctx.getMimeType(filePath)); String headerFilename = filename; String ua = request.getHeader("User-Agent" ); if (ua.toLowerCase().contains("firefox" )) { headerFilename = "=?utf-8?B?" + new BASE64Encoder ().encode(filename.getBytes(StandardCharsets.UTF_8)) + "?=" ; } else { headerFilename = URLEncoder.encode(filename, "utf-8" ); } response.setHeader("Content-Disposition" , "attachment;filename=" + headerFilename); IOUtils.copy(is, response.getOutputStream()); }
文件上传
在spring配置文件中配置文件上传解析器:
1 2 <bean id ="multipartResolver" class ="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @PostMapping("upload") @ResponseBody public String upload (HttpServletRequest request, MultipartFile file) throws IOException { String originalFilename = file.getOriginalFilename(); String extName = FileUtil.extName(originalFilename); if (StrUtil.isNotBlank(extName)) { extName = "." + extName; } String fileName = UUID.fastUUID().toString(true ) + extName; ServletContext ctx = request.getServletContext(); String uploadFilePath = ctx.getRealPath("/upload" ) + File.separator + DateUtil.today() + File.separator + fileName; FileUtil.mkParentDirs(uploadFilePath); file.transferTo(new File (uploadFilePath)); return "success" ; }
拦截器
过滤器作用于浏览器请求到DispatcherServlet之间
SpringMVC中的拦截器用于拦截控制器方法的执行
SpringMVC中的拦截器需要实现HandlerInterceptor
SpringMVC的拦截器必须在Spring的配置文件中进行配置
拦截器的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 <mvc:interceptors > <ref bean ="globalHandlerInterceptor" /> <mvc:interceptor > <mvc:mapping path ="/admin/**" /> <mvc:exclude-mapping path ="/admin/hello" /> <ref bean ="adminHandlerInterceptor" /> </mvc:interceptor > </mvc:interceptors >
抽象方法
拦截器有三个抽象方法:
preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
postHandle:控制器方法执行之后执行postHandle()
afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation()
执行顺序
若每个拦截器执行preHandle都放行:
多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关
preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行
1 2 3 4 5 6 com.zhuweitung.springmvc.interceptor.GlobalHandlerInterceptor preHandle com.zhuweitung.springmvc.interceptor.AdminHandlerInterceptor preHandle com.zhuweitung.springmvc.interceptor.AdminHandlerInterceptor postHandle com.zhuweitung.springmvc.interceptor.GlobalHandlerInterceptor postHandle com.zhuweitung.springmvc.interceptor.AdminHandlerInterceptor afterCompletion com.zhuweitung.springmvc.interceptor.GlobalHandlerInterceptor afterCompletion
若某个拦截器执行preHandle没有放行:
preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行
1 2 3 com.zhuweitung.springmvc.interceptor.GlobalHandlerInterceptor preHandle com.zhuweitung.springmvc.interceptor.AdminHandlerInterceptor preHandle com.zhuweitung.springmvc.interceptor.GlobalHandlerInterceptor afterCompletion
HandlerExecutionChain相关源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 boolean applyPreHandle (HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = 0 ; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(request, response, this .handler)) { triggerAfterCompletion(request, response, null ); return false ; } this .interceptorIndex = i; } } return true ; }void applyPostHandle (HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = interceptors.length - 1 ; i >= 0 ; i--) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(request, response, this .handler, mv); } } }void triggerAfterCompletion (HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = this .interceptorIndex; i >= 0 ; i--) { HandlerInterceptor interceptor = interceptors[i]; try { interceptor.afterCompletion(request, response, this .handler, ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception" , ex2); } } } }
异常处理
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver
HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver
SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver
基于配置的异常处理
1 2 3 4 5 6 7 8 <bean class ="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" > <property name ="exceptionMappings" > <props > <prop key ="com.zhuweitung.springmvc.exception.SimpleException" > error</prop > </props > </property > </bean >
基于注解的异常处理
@ControllerAdvice:将当前类标识为异常处理的组件
@ExceptionHandler:用于设置所标识方法处理的异常
1 2 3 4 5 6 7 8 9 10 @ControllerAdvice public class SimpleExceptionAdvice { @ExceptionHandler(value = {SimpleException.class}) public String handleSimpleException (Exception exception, Model model) { model.addAttribute("exception" , exception); return "error" ; } }
注解配置SpringMVC
使用配置类和注解代替web.xml和SpringMVC配置文件的功能
初始化类代替web.xml
在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器。 Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class []{SpringConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { return new Class []{WebConfig.class}; } @Override protected String[] getServletMappings() { return new String []{"/" }; } @Override protected Filter[] getServletFilters() { CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter ("UTF-8" ); HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter (); return new Filter []{characterEncodingFilter, hiddenHttpMethodFilter}; } }
SpringConfig配置类代替spring配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Configuration @EnableTransactionManagement @PropertySource("classpath:hikari.properties") public class SpringConfig { @Value("${dataSourceClassName}") private String dataSourceClassName; @Value("${dataSource.driverClassName}") private String driverClassName; @Value("${dataSource.jdbcUrl}") private String jdbcUrl; @Value("${dataSource.username}") private String username; @Value("${dataSource.password}") private String password; @Value("${minimumIdle}") private Integer minimumIdle; @Value("${maximumPoolSize}") private Integer maximumPoolSize; @Bean public DataSource hikariDataSource () { Properties properties = new Properties (); properties.put("dataSourceClassName" , dataSourceClassName); properties.put("dataSource.driverClassName" , driverClassName); properties.put("dataSource.jdbcUrl" , jdbcUrl); properties.put("dataSource.username" , username); properties.put("dataSource.password" , password); properties.put("minimumIdle" , minimumIdle); properties.put("maximumPoolSize" , maximumPoolSize); return new HikariDataSource (new HikariConfig (properties)); } @Bean public JdbcTemplate jdbcTemplate (DataSource hikariDataSource) { return new JdbcTemplate (hikariDataSource); } @Bean public DataSourceTransactionManager dataSourceTransactionManager (DataSource hikariDataSource) { return new DataSourceTransactionManager (hikariDataSource); } }
WebConfig配置类代替SpringMVC配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Configuration @EnableWebMvc @ComponentScan(basePackages = "com.zhuweitung.springmvc") public class WebConfig implements WebMvcConfigurer { @Bean public InternalResourceViewResolver internalResourceViewResolver () { return new InternalResourceViewResolver ("/WEB-INF/" , ".jsp" ); } @Bean public MultipartResolver multipartResolver () { return new CommonsMultipartResolver (); } @Override public void configureDefaultServletHandling (DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new GlobalHandlerInterceptor ()); registry.addInterceptor(new AdminHandlerInterceptor ()).addPathPatterns("/admin/**" ).exclude } }
SpringMVC执行流程
SpringMVC常用组件
DispatcherServlet:前端控制器 ,不需要工程师开发,由框架提供
作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
HandlerMapping:处理器映射器 ,不需要工程师开发,由框架提供
作用:根据请求的url、method等信息查找Handler,即控制器方法
Handler:处理器 (Controller),需要工程师开发
作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
HandlerAdapter:处理器适配器 ,不需要工程师开发,由框架提供
作用:通过HandlerAdapter对处理器(控制器方法)进行执行
ViewResolver:视图解析器 ,不需要工程师开发,由框架提供
作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView
View:视图
DispatcherServlet初始化过程
DispatcherServlet 本质上是一个 Servlet,所以天然的遵循 Servlet 的生命周期,DispatcherServlet的初始化入口也是init(ServletConfig config)方法
首先DispatcherServlet中没有重写init(ServletConfig config)
方法,向上找它的父类,发现由GenericServlet
实现了,里面调用了init()
方法
1 2 3 4 5 6 7 public void init (ServletConfig config) throws ServletException { this .config = config; this .init(); }public void init () throws ServletException { }
HttpServletBean
重写了init()
方法,然后调用了initServletBean()
方法,进行封装初始化参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Override public final void init () throws ServletException { PropertyValues pvs = new ServletConfigPropertyValues (getServletConfig(), this .requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this ); ResourceLoader resourceLoader = new ServletContextResourceLoader (getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor (resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true ); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'" , ex); } throw ex; } } initServletBean(); }protected void initServletBean () throws ServletException { }
FrameworkServlet
重写了initServletBean()
方法,里面调用了initWebApplicationContext()
方法,进行对WebApplicationContext的初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 protected WebApplicationContext initWebApplicationContext () { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null ; if (wac == null ) { wac = findWebApplicationContext(); } if (wac == null ) { wac = createWebApplicationContext(rootContext); } if (!this .refreshEventReceived) { synchronized (this .onRefreshMonitor) { onRefresh(wac); } } if (this .publishContext) { String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }protected WebApplicationContext createWebApplicationContext (@Nullable ApplicationContext parent) { Class<?> contextClass = getContextClass(); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException ( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext" ); } ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); String configLocation = getContextConfigLocation(); if (configLocation != null ) { wac.setConfigLocation(configLocation); } configureAndRefreshWebApplicationContext(wac); return wac; }
DispatcherServlet
重写了onRefresh
方法,方法里面会初始化策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Override protected void onRefresh (ApplicationContext context) { initStrategies(context); }protected void initStrategies (ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
DispatcherServlet调用组件处理请求
首先DispatcherServlet中没有实现service(ServletRequest req, ServletResponse res)方法,从它的父类找,发现HttpServlet
实现了,里面又调用了service(HttpServletRequest req, HttpServletResponse resp)
这个方法被FrameworkServlet
重写,里面又调用processRequest
方法,processRequest方法中调用了doService
方法,该方法为空方法,被DispatcherServlet
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Override protected void doService (HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); Map<String, Object> attributesSnapshot = null ; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap <>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this .cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this .localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this .themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); if (this .flashMapManager != null ) { FlashMap inputFlashMap = this .flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null ) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap ()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this .flashMapManager); } try { doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { if (attributesSnapshot != null ) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
doService方法中调用了doDispatch
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null ; boolean multipartRequestParsed = false ; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null ; Exception dispatchException = null ; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); mappedHandler = getHandler(processedRequest); if (mappedHandler == null ) { noHandlerFound(processedRequest, response); return ; } HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET" .equals(method); if (isGet || "HEAD" .equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest (request, response).checkNotModified(lastModified) && isGet) { return ; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return ; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return ; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException ("Handler dispatch failed" , err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } }
doDispatch方法中调用了processDispatchResult
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 private void processDispatchResult (HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false ; 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 ); } } if (mv != null && !mv.wasCleared()) { render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isTraceEnabled()) { logger.trace("No view rendering, null ModelAndView returned." ); } } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { return ; } if (mappedHandler != null ) { mappedHandler.triggerAfterCompletion(request, response, null ); } }
SpringMVC执行流程
用户向服务器发送请求,请求被前端控制器DispatcherServlet捕获
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射
若映射不存在
判断是否配置了mvc:default-servlet-handler
若没配置,客户端显示404错误
若有配置,则访问目标资源(一般为静态资源),找不目标资源时客户端显示404错误
若映射存在
根据该URI,调用HandlerMapping获取该Handler配置的所有相关对象(Handler对象以及对于的拦截器),最后以HandlerExecutionChain执行链对象的形式返回
DispatcherServlet得到Handler,获取HandlerAdapter
获得HandlerAdapter后,执行拦截器的preHandler方法
提取请求中的模型数据,填充到Handler入参,执行Handler(Controller)方法,处理请求
填充数据模型到入参时,spring还做了下面工作
HttpMessageConveter:将请求消息转换为一个对象
数据类型转换:如将String转换为Integer
数据格式化:如将字符串转换成格式化数字或格式化日期等
数据验证:验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象
开始执行拦截器的postHandle方法
根据返回的ModelAndView选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图
此时会判断是否存在异常,如果存在异常,则执行HandlerExceptionResolver进行异常处理
渲染视图完毕执行拦截器的afterCompletion方法
将渲染结果返回给客户端