上一节我们讲的是Spring AOP的原理部分,介绍了切面,切点,连接点,通知,织入,以及简单的代码实现.
我们上一节是使用@Aspect来进行切点函数的范围和通知方法的设定的.
但是使用上面的方法有两个问题是无法解决的;
- 无法获取HttpSession
- 切点的execution函数的拦截语法还是有限的,不可以清楚的定位
所以,我们就需要使用新的方法.
SpringAop给我们提供了新的框架------Spring 拦截器
Spring拦截器
Spring 中提供了具体的实现拦截器:HandlerInterceptor,
拦截器的实现分为以下两个步 骤:
- 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的 preHandle(执⾏具体⽅法之前的预处理)⽅ 法。
- 将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法
1. 自定义拦截器
- 创建一个拦截器类
- 实现HandlerInterceptor接口
- 重写preHandle方法
如果通过了拦截器的拦截认证,就会返回true,放行,继续执行之后的代码
但是如果没有通过,就根据业务的需要,执行响应的代码,并返回false
@Component
public class LoginIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session=request.getSession();
if(session!=null&&session.getAttribute("user")!=null)
return true;
response.sendRedirect("/Blog_Main.html");
return false;
}
}
2. 将⾃定义拦截器加⼊到系统配置
- 定义一个配置类
- 使用WebMvcConfigurer类
- 重写addInterceptors方法
- 将拦截器放入到addInterceptor方法中并指定拦截范围和不拦截范围
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Autowired
LoginIntercepter loginIntercepter;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//加入拦截器
registry.addInterceptor(loginIntercepter).
//指定拦截所有的接口
addPathPatterns("/**").
//排除不拦截的接口
excludePathPatterns("/resources/**").
excludePathPatterns("/**/login").
excludePathPatterns("/**/**.png").
excludePathPatterns("/**/**.jpg").
excludePathPatterns("/Blog_Main.html").
excludePathPatterns("/**/**.css").
excludePathPatterns("/**/**.js");
}
}
可以通过多次调用registry.addInterceptor方法来添加多个拦截器
原理分析
那Spring拦截器是怎么样做到的呢?
主要是通过DispatcherServlet来进行实现的.
这个DispathcherServlet里面的doDispath方法里面主要有一个方法:
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
这个applyPreHandle方法会处理所有的拦截器方法,下面是它的实现:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
return true;
}
它会将拦截器列表的所有拦截器都遍历一遍,只要不符合其中任意一个拦截器,就会报错,返回false,拦截不通过.
添加路径前缀
还可以在系统配置中将所有的路径都加上前缀.
- 创建一个配置类实现WebMvcConfigurer接口
- 重写configurePathMatch方法
- 第一个参数是新加入的前缀,后一个参数是描述参数的范围
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Autowired
LoginIntercepter loginIntercepter;
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("hhh",c->true);
}
}
原来:http://127.0.0.1:8080/user/test
现在:http://127.0.0.1:8080/hhh/user/test
加上前缀之后,就必须要带上前缀了
统一异常处理
- 创建一个类
- 在这个类前面加上@RestControllerAdvice或@ControllerAdvice注解,两者都可以
- 在处理异常的方法前面加上@ExceptionHandler,括号里面写异常的类型
- 处理异常的方法的参数是对应的异常类型
@RestControllerAdvice
public class MyExceptionAdvice {
@ExceptionHandler(Exception.class)
public HashMap<String,Object> handleException(Exception e){
HashMap<String,Object> hashMap=new HashMap<>();
hashMap.put("state",-1);
hashMap.put("msg","出现异常");
hashMap.put("log",e.getMessage());
return hashMap;
}
}
统一返回处理
- 创建一个类,在类的前面加上@ControllerAdvice注解
- 实现ResponseBodyAdvice接口,并是实现它的两个方法
- 对于supports方法来说,如果返回true,表示需要进行统一的返回处理.如果返回false,表示不需要统一的返回处理.可以使用这个函数进行过滤
- beforeBodyWrite就是对那些supports方法返回true的接口进行处理.一般都是写成前端可以识别的json格式
@ControllerAdvice
public class MyResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
if("com.example.demo.controller.UserController".equals(returnType.getMethod().getDeclaringClass().getName()))
return true;
else
return false;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
HashMap<String, Object> result = new HashMap<>();
result.put("success", 1);
result.put("message", "");
result.put("data", body);
return result;
}
}
原理分析
我们发现@ControllerAdvice可以处理异常和返回值.但是这个的原理是什么呢?
将所有被@ControllerAdvice注解的类方法一个容器中.就是当发生事件的时候,会去调用对应的类的advice方法