1. 拦截器
SpringMVC拦截器定义如下:
public interface HandlerInterceptor {
/**
* 在调用自定义的controller之前会调用这个方法,若返回false,
* 将跳过controller方法的调用,否则将进入到controller的方法中。
*/
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
/**
* 调用自定义controller中的方法之后会调用这个方法,此时还没有渲染视图,
* 也就是还没有将结果输出到客户端
*/
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
/**
* 整个请求处理完毕之后,即结果以及输出到客户端之后,调用这个方法,
* 此时可以做一些清理的工作,注意这个方法最后一个参数是Exception类型的,
* 说明这个方法不管整个过程是否有异常,这个方法都会被调用。
*/
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
自定义一个拦截器,实现HandlerInterceptor接口
public class MyInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor.preHandle");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
}
}
配置拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/example/**"/>
<bean class="com.example.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
访问执行顺序如下:
多个拦截器如何执行?
当请求的url匹配到多个拦截器的时候,执行顺序如下图
- preHandle方法是顺序执行的,即和定义的顺序是一致的
- 而拦截器中的其他2个方法postHandle、afterCompletion是逆序执行的,和preHandle的顺序相反
2. Spring定义的Advice,对@Controller, @RequestBody, @ResponseBody的增强
1. 对Controller的增强,@ControllerAdvice, @RestControllerAdvice两个注解,来看一下使用方式
@ControllerAdvice
public class MyControllerAdvice {
/**
* 全局异常处理方法
*/
@ExceptionHandler
public ModelAndView doException(Exception ex){
// 处理异常的逻辑
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("errorMessage", ex.getMessage());
return modelAndView;
}
}
定义的error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false"%>
<html>
<head>
<title>Title</title>
</head>
<body>
<h2 style="color:red">${errorMessage}</h2>
</body>
</html>
Controller
@GetMapping("/exceptionTest")
public String exceptionTest(Model model) {
throw new RuntimeException("测试异常");
}
访问,得到error.jsp页面如下:
2. 对@RequestBody增强,有一个RequestBodyAdvice接口,还有一个默认的实现类RequestBodyAdviceAdapter,我们一般继承RequestBodyAdviceAdapter, 然后对request进行处理,比如得到一串加密的数据,我们需要统一在入口处进行解密。
@ControllerAdvice
public class MyRequestBodyAdvice extends RequestBodyAdviceAdapter {
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
/**
* 在body中的数据读取之前可以做一些处理,我们在这个方法中来做解密的操作
*/
@Override
public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
String body = IOUtils.toString(inputMessage.getBody(), "utf-8");
// 对body解密
String decryptBody = decrypt(body);
final InputStream inputStream = IOUtils.toInputStream(decryptBody, "utf-8");
return new HttpInputMessage() {
public InputStream getBody() throws IOException {
return inputStream;
}
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
};
}
}
3. ResponseBodyAdvice对@ResponseBody进行增强,我们来看一个事例,设计一个通用返回结果
@RestControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Object> {
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.getParameterType() == User.class;
}
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return Result.success(body);
}
}
@Data
@AllArgsConstructor
public class Result<T> {
private String message;
private Integer code;
private T data;
public static <T> Result<T> success(T data){
return new Result<T>("成功", 200, data);
}
}
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/getUser")
public User getUser(){
User user = new User();
user.setName("Mike");
user.setAge("18");
return user;
}
}
访问输出结果如下:
3. 自定义参数解析器Converter
比如我们请求http://localhost:8080/hello/testUser?user=Jack,20把user参数转换成对象该怎么做呢
@GetMapping("/testUser")
public User testUser(@RequestParam("user") User user){
System.out.println(user);
return user;
}
自定义一个Converter实现接口Converter
public class MyConverter implements Converter<String, User> {
public User convert(String source) {
if(!StringUtils.hasText(source)){
return null;
}
String[] split = source.split(",");
return new User(split[0], split[1]);
}
}
xml当中配置converter
<mvc:annotation-driven conversion-service="conversionService" />
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.example.MyConverter" />
</set>
</property>
</bean>
运行输出:
4. CORS跨域问题详解
1. Spring定义了@CrossOrigin注解来实现跨域访问问题,这个注解可以使用在类或方法上,定义如下
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
/** @deprecated */
@Deprecated
String[] DEFAULT_ORIGINS = new String[]{"*"};
/** @deprecated */
@Deprecated
String[] DEFAULT_ALLOWED_HEADERS = new String[]{"*"};
/** @deprecated */
@Deprecated
boolean DEFAULT_ALLOW_CREDENTIALS = false;
/** @deprecated */
@Deprecated
long DEFAULT_MAX_AGE = 1800L;
/**
* 指定允许请求源列表
*/
@AliasFor("origins")
String[] value() default {};
@AliasFor("value")
String[] origins() default {};
String[] originPatterns() default {};
/**
* 指定允许实际请求标头列表
*/
String[] allowedHeaders() default {};
/**
* 指定允许客户端(如浏览器)访问的响应标头
*/
String[] exposedHeaders() default {};
/**
* 指定允许请求的HTTP方法。
*/
RequestMethod[] methods() default {};
/**
* 指定其值,表示客户端(如浏览器)是否应将凭证(如Cookies)和跨域请求一起发送到服务器。
*/
String allowCredentials() default "";
String allowPrivateNetwork() default "";
/**
* 指定预处理响应的最大缓存期限,单位为秒。
*/
long maxAge() default -1L;
}
2. 全局配置CORS
@EnableWebMvc
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
//每次调用registry.addMapping可以添加一个跨域配置,需要多个配置可以多次调用registry.addMapping
registry.addMapping("/**")
.allowedOrigins("*") //放行哪些原始域
.allowedMethods("PUT", "DELETE","POST", "GET") //放行哪些请求方式
.allowedHeaders("header1", "header2", "header3") //放行哪些原始请求头部信息
.exposedHeaders("header1", "header2") //暴露哪些头部信息
.allowCredentials(false) //是否发送 Cookie
.maxAge(3600);
}
}
3. 全局添加CorsFilter
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setMaxAge(3600L);
//corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
}