文章目录
一、什么是拦截器
SpringMVC 的处理器拦截器类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理。
常见使用场景
1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间 ;
4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
5、OpenSessionInView:如Hibernate,在进入处理器打开Session,在完成后关闭Session。
preHandle:
预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器
返回值
true表示继续流程(如调用下一个拦截器或处理器);
false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
postHandle:
后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
afterCompletion:
整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中preHandle返回true的拦截器的afterCompletion。
二、入门案例
2.1注册组件
@Component //注册Interceptor组件
public class CustomInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("hello preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
modelAndView.setViewName("WEB-INF/jsp/post.jsp");
System.out.println("hello postHhandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
2.2配置interceptor
<mvc:interceptors>
<!--使用的是ref或bean标签,全局作用范围-->
<!--<ref bean="customInterceptor"/>-->
<!--<bean class="com.qyt.interceptor.CustomInterceptor"/>-->
<mvc:interceptor>
<!--interceptor标签内的是局部作用范围-->
<!--path中填的是url-->
<!--/hello 只针对hello请求-->
<!--/hello* 针对helloXXX请求-->
<!--/hello/ * 针对hello/XXX一级任意目录的请求-->
<!--/hello/ ** 针对hello/多级任意目录的请求-->
<mvc:mapping path="/hello/**"/>
<ref bean="customInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
2.3prehandle返回值为false
执行到prehandle则停止向下执行,执行不到handler和postHandle和afterCompletion
2.4postHandle可以做后处理
可以处理视图,也可以处理json
如果handler方法上有@RequestBody注解,说明是以json进行相应,那么ModelAndView的值为null。
二、interceptor的作用范围
默认如果使用的是<bean或<ref子标签,作用范围是DispacherServlet的全局
如果是写在interceptor标签里面的,那么作用范围只针对部分请求。
三、多个interceptor执行顺序
3.1均为true
执行顺序:先进后出,后进先出
套娃模型、登山模型
123…n
n…321
n…321
3.2出现false
如果为true,向下执行,并且可以执行到对应的afterCompletion
如果为false,则中断流程。
如果preHandle1为true,preHandle2为false,依次执行preHandle1,preHandle2,afterCompletion1。
如果preHandle1为false,只执行preHandle1。
如果preHandle1,preHandle2为true,preHandle3为false,依次执行preHandle1,preHandle2,preHandle3,afterCompletio2,afterCompletio3。
执行顺序练习:
5个interceptor:
1245 为true 3为false:123 21
1为false 2345为true :1
1235为true 4为false:1234 321
四、Lcale
地区,语言信息
在SpringMVC中管理Locale,Locale可以作为handler方法中的形参传入,Locale一般放到cookie或者session中管理。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qyt</groupId>
<artifactId>demo1-locale</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.1</version>
</dependency>
</dependencies>
</project>
application.xml配置文件
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<!--通过cookie中的language字段管理Locale-->
<property name="cookieName" value="language"/>
<!--默认Locale为en_US,语言使用美式英文-->
<property name="defaultLocale" value="en_US"/>
</bean>
HelloController.java
package com.qyt.controller;
import com.qyt.bean.BaseRespVo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Locale;
@RestController
public class HelloController {
@RequestMapping("hello")
public BaseRespVo hello(Locale locale){
return BaseRespVo.ok(locale);
}
}
BaseRespVo.java
package com.qyt.bean;
import lombok.Data;
/**
* Vo viewObject
* @param <T>
* 这是前后端分离,json数据一个常用的格式
*/
@Data
public class BaseRespVo<T> {
T data;
String msg;//告诉前端请求的消息
long errno;//自己的前后端应用 : 自定义的状态码 → 通常前端根据该状态码做不同的处理
public static BaseRespVo ok(){
BaseRespVo<Object> baseRespVo = new BaseRespVo<>();
baseRespVo.setErrno(0);
baseRespVo.setMsg("成功");
return baseRespVo;
}
public static BaseRespVo ok(Object data){
BaseRespVo<Object> baseRespVo = new BaseRespVo<>();
baseRespVo.setData(data);
baseRespVo.setErrno(0);
baseRespVo.setMsg("成功");
return baseRespVo;
}
public static BaseRespVo ok(Object data, String msg){
BaseRespVo<Object> baseRespVo = new BaseRespVo<>();
baseRespVo.setData(data);
baseRespVo.setErrno(0);
baseRespVo.setMsg(msg);
return baseRespVo;
}
public static BaseRespVo fail(){
BaseRespVo<Object> baseRespVo = new BaseRespVo<>();
baseRespVo.setErrno(500);
baseRespVo.setMsg("失败");
return baseRespVo;
}
public static BaseRespVo fail(String msg){
BaseRespVo<Object> baseRespVo = new BaseRespVo<>();
baseRespVo.setErrno(500);
baseRespVo.setMsg(msg);
return baseRespVo;
}
}
默认是en_US
在cookie中将language字段的值修改为zh_CN。
五、MessageSource
5.1注册组件
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<!--加载的配置文件的名字,注意加上classpath-->
<property name="basename" value="classpath:message"/>
<!--properties配置文件的编码-->
<property name="defaultEncoding" value="utf-8"/>
</bean>
5.2使用messageSource
@Autowired
@Qualifier("messageSource")
MessageSource messageSource;
@RequestMapping("hello/{key}")
public BaseRespVo hello(@PathVariable("key") String key){
//第一个参数 → String → 就是配置文件中的key
String message = messageSource.getMessage(key, null, Locale.getDefault());
return BaseRespVo.ok(message);
}
配置文件
输入key,返回对应的值。
5.3locale和messageSource结合
同一个key在不同的locale下,value值不同
分别新建四个配置文件,表示不同语言(Locale)的厉害的意思:
en_US(美式英文):awesome → message_en_US.properties
zh_CN(简体中文):牛皮 → message_zh_CN.properties
th_Th(泰文):เก่ง → message_th_TH.properties
ja_JP(日文):すごい → message_ja_JP.properties
→ 这上面4个配置文件的basename就为message
5.3.1组件注册
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<!--加载的配置文件的名字,注意加上classpath-->
<property name="basename" value="classpath:message"/>
<!--properties配置文件的编码-->
<property name="defaultEncoding" value="utf-8"/>
</bean>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="defaultLocale" value="zh_CN"/>
<property name="cookieName" value="language"/>
</bean>
5.3.2使用
配置文件信息
tip:复制进来的properties的文件原来是什么编码就还是什么编码。
HelloController中的代码
@RequestMapping("i18n/{key}")
public BaseRespVo i18n(@PathVariable("key") String key,Locale locale){
String message = messageSource.getMessage(key, null, locale);
return BaseRespVo.ok(message);
}
5.4第二个参数object[ ]
为从配置文件中取出的值做占位符的赋值
Locale使用默认的zh_CN,运行结果为:
六、validator
请求参数校验:将校验和模型绑定起来 → 请求参数和javabean中的成员变量绑定起来 → 校验逻辑和javabean中的成员变量绑定起来。
6.1引入依赖
在之前的基础上引入validator的依赖
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Final</version>
<!--注意这里validator的版本要比springmvc的版本高一个版本,
这里的springmvc的版本是5.2.8,所以validator的版本选择6.1.0-->
</dependency>
6.2application.xml配置
<mvc:annotation-driven validator="localValidatorFactoryBean"/>
<!--规范-->
<bean id="localValidatorFactoryBean" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!--具体的实现方式-->
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
</bean>
注册完validator组件要让springmvc知道。
6.3代码
使用validator的注解对javabean的成员变量进行验证。
@Data
public class User {
@NotNull//限定用户名不为空
@Size(min = 6)//限定用户名最小长度是6
String username;
@Size(min = 6,max = 10)//限定密码长度为6-10之间
String password;
}
@RestController
public class UserController {
@RequestMapping("login")
public BaseRespVo login(@Valid User user){
return BaseRespVo.ok();
}
}
以输入长度不合法的密码为例,产生如下报错信息:
tomcat的报错:
Field error in object ‘user’ on field ‘password’: rejected value [1234]; codes [Size.user.password,Size.password,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.password,password]; arguments []; default message [password],10,6]; default message [个数必须在6和10之间]]
说明密码长度必须在6和10之间,这是对字段的验证。
6.4常见注解
常见的注解 (Bean Validation 中内置的 constraint)
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期 Date
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator 附加的 constraint
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
6.5message
6.5.1自定义错误消息
@Data
public class User {
@NotNull
@Size(min = 6, message = "username length min 6")//自定义的错误信息
String username;
@Size(min = 6,max = 10, message = "password length between 6 and 10")//自定义的错误信息
String password;
}
/**
*
* @param user
* @param bindingResult 👉参数校验的结果 → 哪一个参数发生错误了,发生的错误是什么
* @return
*/
@RequestMapping("login")
public BaseRespVo login(@Valid User user, BindingResult bindingResult){
if (bindingResult.hasFieldErrors()){
FieldError fieldError = bindingResult.getFieldError();
String field = fieldError.getField();
Object rejectedValue = fieldError.getRejectedValue();
//获取错误信息,如果未自定义输出系统默认的错误信息,如果有自定义,输出自定义错误信息
String defaultMessage = fieldError.getDefaultMessage();
String message = "参数" + field + "携带了" + rejectedValue + "没有完成校验:" + defaultMessage;
return BaseRespVo.fail(message);
}
return BaseRespVo.ok();
}
携带的参数密码长度不合法,收到返回的错误信息。
6.5.2和messageSource、Locale结合实现国际化的错误消息返回
- 配置文件注册messageSource、Locale相关组件
<mvc:annotation-driven validator="localValidatorFactoryBean"/>
<!--规范-->
<bean id="localValidatorFactoryBean" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!--具体的实现方式-->
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<property name="validationMessageSource" ref="messageSource"/>//注意这里不要忘了配置messageSource
</bean>
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:message"/>
<property name="defaultEncoding" value="utf-8"/>
</bean>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="cookieName" value="language"/>
<property name="defaultLocale" value="zh_CN"/>
</bean>
- 配置文件(这里以两种语言为例)
@Data
public class User {
@NotNull
//@Size(min = 6,message = "username length min 6")
@Size(min = 6,message = "{error.username}")
String username;
//@Size(min = 6,max = 10,message = "password length between 6 and 10")
@Size(min = 6,max = 10,message = "{error.password}")
String password;
}
/**
*
* @param bindingResult 👉参数校验的结果 → 哪一个参数发生错误了,发生的错误是什么
*/
@RequestMapping("login")
public BaseRespVo login(@Valid User user, BindingResult bindingResult){
if (bindingResult.hasFieldErrors()){
FieldError fieldError = bindingResult.getFieldError();
String field = fieldError.getField();
Object rejectedValue = fieldError.getRejectedValue();
String defaultMessage = fieldError.getDefaultMessage();
String message = "参数" + field + "携带了" + rejectedValue + "没有完成校验:" + defaultMessage;
return BaseRespVo.fail(message);
}
return BaseRespVo.ok();
}
默认的Locale为zh_CN
修改浏览器cookie中的language字段为en_US,报错信息以英文显示。
七、Spring和SpringMVC的整合
7.1Spring容器和SpringMVC容器分家
7.1.1ContextLoaderListener
7.1.2DispatcherServlet加载的是SpringMVC容器
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--ContextLoaderListener-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.xml</param-value>
</context-param>
<!--DispatcherServlet-->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
7.1.3context:component-scan
application-mvc.xml
<context:component-scan base-package="com.qyt.controller"/>
<mvc:annotation-driven/>
application.xml
<context:component-scan base-package="com.qyt">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
7.2SpringMVC JavaConfig
在配置类中注册组件,使用JavaConfig替代原来的xml文件:
Spring配置类 → application.xml
SpringMVC配置类 → application-mvc.xml
web.xml → 替代它需要满足加载spring和springmvc的配置类
7.2.1AACDSI
7.2.2Spring配置类
7.2.3MVC配置类
7.2.4其他组件注册
- CharactrerEncodingFilter
web.xml → AACDSI
- 静态资源映射
<mvc:resources mapping location/>
- interceptors
<mvc:interceptors>
<bean或ref标签
<mvc:interceptor>
<mvc:mapping path/>
<ref或bean标签
- multipartResolver
commons-fileupload(io)
- localeResolver
- viewResolver
- validator
- converter
conversionService
1、取出
2、增加上converter
3、放回容器
- handlerExceptionResolver
八、spring阶段的组件
8.1jdbcTemplate
8.2TransactionManager
8.3aspectj
导包