拦截机制中Aspect、ControllerAdvice、Interceptor、Fliter之间的区别详解
在项目的开发中,在某些情况下,我们需要对客户端发出的请求进行拦截,常用的API拦截方式有Fliter,Interceptor,ControllerAdvice以及Aspect。
上图是spring中的拦截机制,如果出现异常的话,异常的顺序是从里面到外面一步一步的进行处理,如果到了最外层都没有进行处理的话,就会由tomcat容器抛出异常。下面我将详细的解释这四个拦截方式的不同。因为只是演示就不是所有的使用了日志记录。这里相关的依赖自己可以去https://mvnrepository.com/下载,我就不一一添加了
先做一个总的总结吧
1.过滤器:Filter
:可以获得Http原始的请求和响应信息,但是拿不到响应方法的信息.在Security当中用于Token认证拦截
2.拦截器:Interceptor
:可以获得Http原始的请求和响应信息,也拿得到响应方法的信息,但是拿不到方法响应中参数的值.在后端开发当中可用于自定义权限拦截.在Controller上加上自定义的权限注解,通过拦截器来判断权限信息.
3.ControllerAdvice(Controller增强,自spring3.2的时候推出):
用于处理当数据库事务业务和预期不同的时候抛出封装后的异常,进行数据库事务回滚,并将异常的显示给用户
4.切片:Aspect
可以拿得到方法响应中参数的值,但是拿不到原始的Http请求和相对应响应的方法
一般来说在项目开发当中,使用切面日志来记录Controller层的方法调用以及参数.
代码实例
Filter(过滤器):
可以获得Http原始的请求和响应信息,但是拿不到响应方法的信息
filter是属于Servlet规范的,不属于Spring
springboot中的配置方法:
自定义一个Filter
public class TimeFilter implements Filter {
/**Filter接口中的部分方法添加了default关键字,这样的方法就不是一定要重写*/
@Override
public void init(FilterConfig filterConfig)throws ServletException {
System.out.println("Time Filter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {
System.out.println("time filter start");
/**这里说明一下,在JDK8以及之后的JDK版本中都不再建议使用new Date().getTime()这种方式来获得时间*/
long start = System.currentTimeMillis();
chain.doFilter(request,response);
System.out.println("time filter:"+(System.currentTimeMillis()-start));
System.out.println("time filter finish");
}
@Override
public void destroy() {
System.out.println("time filter destroy");
}
}
方式一:通过Bean注入的方式
注册Filter,springboot当中提供了FilterRegistrationBean类来注册Filter
@Configuration
public class WebConfigimplements WebMvcConfigurer {
/**WebMvcConfigurerAdapter在JDK8中这个类已经过时,我们直接继承这个类所继承的接口*/
@Bean
public FilterRegistrationBeantimeFilter(){
FilterRegistrationBean registrationBean =new FilterRegistrationBean();
TimeFilter timeFilter =new TimeFilter();
registrationBean.setFilter(timeFilter);
/**添加拦截的地址*/
List urls =new ArrayList<>();
urls.add("/*");
return registrationBean;
}
}
方式二:通过@WebFilter注解实现
@Component
@WebFilter(filterName ="TimeFilter",urlPatterns ="/*")
public class TimeFilterimplements Filter {
@Override
public void init(FilterConfig filterConfig)throws ServletException {
System.out.println("Time Filter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {
System.out.println("time filter start");
/**这里说明一下,在JDK8以及之后的JDK版本中都不再建议使用new Date().getTime()这种方式来获得时间*/
long start = System.currentTimeMillis();
chain.doFilter(request,response);
System.out.println("time filter:"+(System.currentTimeMillis()-start));
System.out.println("time filter finish");
}
@Override
public void destroy() {
System.out.println("time filter destroy");
}
}
@WebFilter 的常用属性
属性名 类型 描述
filterName String 指定过滤器的 name 属性,等价于
value String[] 该属性等价于 urlPatterns 属性。但是两者不应该同时使用。
urlPatterns String[] 指定一组过滤器的 URL 匹配模式。等价于 标签。
servletNames String[] 指定过滤器将应用于哪些 Servlet。取值是 @WebServlet 中的 name 属性的取值,或者是
web.xml 中 的取值。
dispatcherTypes DispatcherType 指定过滤器的转发模式。具体取值包括:
ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。
initParams WebInitParam[] 指定一组过滤器初始化参数,等价于 标签。
asyncSupported boolean 声明过滤器是否支持异步操作模式,等价于 标签。
description String 该过滤器的描述信息,等价于 标签。
displayName String 该过滤器的显示名,通常配合工具使用,等价于 标签。
相较而言,方式一会更加的灵活,而方式二更加的方便,但是两者在实质上是一样的
Interceptor (拦截器) :
可以获得Http原始的请求和响应信息,也拿得到响应方法的信息,但是拿不到方法响应中参数的值
在web开发中,拦截器是经常用到的功能。它可以帮我们验证是否登陆、预先设置数据以及统计方法的执行效率等。在spring中拦截器有两种,第一种是HandlerInterceptor,第二种是MethodInterceptor。HandlerInterceptor是SpringMVC中的拦截器,它拦截的是Http请求的信息,优先于MethodInterceptor。而MethodInterceptor是springAOP的。前者拦截的是请求的地址,而后者是拦截controller中的方法,因为下面要将Aspect,就不详细讲述MethodInterceptor
在springboot中HandlerInterceptor的配置
1.首先定义自己的Interceptor
/**
* 拦截器会拦截所有的控制器,不管是spring的还是自定义的
*/
@Component
public class TimeInterceptorimplements HandlerInterceptor {
/**
*控制器方法调用之前会进行
*和上面的Filter一样,继承的某些接口方法中也加了default关键字,可以不用重写,这里为了演示就都写了
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
System.out.println("proHandle");
System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
System.out.println(((HandlerMethod)handler).getMethod().getName());
request.setAttribute("startTime",System.currentTimeMillis());
return true;
/**true的话 就是选择可以调用后面的方法 也就是controller中的getInfo方法*/
}
/**控制后方法执行之后会进行,抛出异常则不会被执行*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {
System.out.println("postHandle");
Long start = (Long)request.getAttribute("startTime");
System.out.println("time interceptor 耗时:"+(System.currentTimeMillis()-start));
}
/**方法被调用或者抛出异常都会被执行*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {
System.out.println("afterCompletion");
Long start = (Long)request.getAttribute("startTime");
System.out.println("time interceptor 耗时:"+(System.currentTimeMillis()-start));
}zai
}
2.在配置类中配置自定义的Interceptor
@Configuration
public class WebConfigimplements WebMvcConfigurer {
/**WebMvcConfigurerAdapter在JDK8中这个类已经过时,我们直接继承这个类所继承的接口*/
@Autowired
private TimeInterceptortimeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timeInterceptor);
}
}
ControllerAdvice(Controller增强,自spring3.2的时候推出):
用于处理当数据库事务业务和预期不同的时候抛出封装后的异常,进行数据库事务回滚,并将异常的显示给用户
1.定义自己的异常类
@Data
public class UserNotExistExceptionextends RuntimeException{
private static final long serialVersionUID =4820951478405122770L;
private Stringid;
public UserNotExistException(String id) {
super("user not exist.......");
this.id = id;
}
}
2.编写全局异常处理类
@ControllerAdvice
public class ControllerExceptionHandler {
/**指定抛出的异常类*/
@ExceptionHandler(UserNotExistException.class)
/**如果全部异常处理返回json格式,那么可以使用 @RestControllerAdvice 代替 @ControllerAdvice ,这样在方法上就可以不需要添加 @ResponseBody.@ResponseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据。*/
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
/**当出现该Http状态码的时候抛出异常*/
public MaphandlerUserNotExistException(UserNotExistException exception){
Map result =new HashMap<>();
result.put("id",exception.getId());
result.put("message",exception.getMessage());
return result;
}
}
3、controller中抛出异常进行测试
@RestController
public class UserController {
@GetMapping(value ="/user/{id}")
public UsergetInfo(@PathVariable String id)throws Exception{
throw new UserNotExistException(id);
}
}
Aspect(切面):
可以拿得到方法响应中参数的值,但是拿不到原始的Http请求和相对应响应的方法,基于Controller层。对于统一异常处理和日志记录非常方便,有效地减少了代码的重复率。
可以参照https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html,spring的官方文档
1.创建Controller
2.创建一个Aspect切面
@Aspect
@Component
@Slf4j
public class TimeAspect {
@Around("execution(* com.imooc.controller.UserController.*(..))")
public ObjecthandleControllerMethod(ProceedingJoinPoint
proceedingJoinPoint)throws Throwable {
log.info("Time aspect start");
Long start = System.currentTimeMillis();
Object[] args = proceedingJoinPoint.getArgs();
for(Object object:args)
{
log.info("arg is:"+String.valueOf(object));
}
Object object =proceedingJoinPoint.proceed();
System.out.println("time filter:"+(System.currentTimeMillis()-start));
log.info("Time aspect finish");
return object;
}
}
切面的方法说明:
@Aspect
作用是把当前类标识为一个切面供容器读取
@Before
标识一个前置增强方法,相当于BeforeAdvice的功能
@AfterReturning
后置增强,相当于AfterReturningAdvice,方法退出时执行
@AfterThrowing
异常抛出增强,相当于ThrowsAdvice
@After
final增强,不管是抛出异常或者正常退出都会执行
@Around
环绕增强,相当于MethodInterceptor
除了@Around外,每个方法里都可以加或者不加参数JoinPoint,如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。@Around参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。@AfterReturning方法里,可以加returning = “XXX”,XXX即为在controller里方法的返回值,本例中的返回值是“first controller”。@AfterThrowing方法里,可以加throwing = “XXX”
关于切面PointCut的切入点
execution切点函数
execution函数用于匹配方法执行的连接点,语法为:
execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选))
例如:execution(* com.imooc.controller.UserController.*(…))
第一个*代表的是所有的返回值类型,com.imooc.controller.UserController.*代表的是com.imooc.controller包下UserController类的所有方法,(…)代表的是所有的参数
参数部分允许使用通配符:
- 匹配任意字符,但只能匹配一个元素
… 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用
- 必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类
除了execution(),Spring中还支持其他多个函数,这里列出名称和简单介绍,以方便根据需要进行更详细的查询
@annotation()
表示标注了指定注解的目标类方法
例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示标注了@Transactional的方法
args()
通过目标类方法的参数类型指定切点
例如 args(String) 表示有且仅有一个String型参数的方法
@args()
通过目标类参数的对象类型是否标注了指定注解指定切点
如 @args(org.springframework.stereotype.Service) 表示有且仅有一个标注了@Service的类参数的方法
within()
通过类名指定切点
如 with(examples.chap03.Horseman) 表示Horseman的所有方法
target()
通过类名指定,同时包含所有子类
如 target(examples.chap03.Horseman) 且Elephantman extends Horseman,则两个类的所有方法都匹配
@within()
匹配标注了指定注解的类及其所有子类
如 @within(org.springframework.stereotype.Service) 给Horseman加上@Service标注,则Horseman和Elephantman 的所有方法都匹配
@target()
所有标注了指定注解的类
如 @target(org.springframework.stereotype.Service) 表示所有标注了@Service的类的所有方法
this()
大部分时候和target()相同,区别是this是在运行时生成代理类后,才判断代理类与指定的对象类型是否匹配
参考
- Spring Security (三) 核心配置解读
- Spring Cloud配置中心获取不到最新配置信息的问题
- 总是听别人说响应式布局,原来这么简单
- Spring Cloud Zuul重试机制探秘
- Eureka中RetryableClientQuarantineRefreshPercentage参数探秘
- Edgware.RC1中ZuulFallbackProvider的改进
- JPA的多表复杂查询:详细篇
- 尝试使用Memcached遇到的狗血问题
- Enumerable#Zip 实现一下
- 更新自己,不要影响其他人
- 【译】Spring官方教程:Spring Boot整合消息中间件RabbitMQ
- [实录]解决Migrator.Net 小bug
- Jenkins Pipeline插件十大最佳实践!
- Spring Cloud Hystrix的请求合并
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 使用 Horoscope 测试 TiDB 优化器
- 聊聊claudb的importRDB
- 【17期】什么情况用ArrayList or LinkedList呢?
- YOLO V3网络结构解析
- 文献笔记二十一:PhenoGram可视化染色体上的信息
- 【动手学深度学习笔记】之对模型参数的访问、初始化和共享
- 【动手学深度学习笔记】之构造MLP模型的几种方法
- 【动手学深度学习笔记】之通过丢弃法缓解过拟合问题
- 【动手学深度学习笔记】之通过权重衰减法解决过拟合问题
- 【动手学深度学习笔记】之多层感知机实现
- Linux程序员效率工具:比man更好用的命令提示工具
- 我对torch中的gather函数的一点理解
- 冒泡排序的实现思路和优化方案
- Java Map转对象
- 59.Vue 使用webpack构建vue项目