赞
踩
最近接触到了过滤器和拦截器,网上查了查资料,这里记录一下,这篇文章就来仔细剖析下过滤器和拦截器的区别与联系。
从上面对拦截器与过滤器的描述来看,它俩是非常相似的,都能对客户端发来的请求进行处理,它们的区别如下:
拦截机制有三种:
1. 过滤器(Filter)能拿到http请求,但是拿不到处理请求方法的信息。
2. 拦截器(Interceptor)既能拿到http请求信息,也能拿到处理请求方法的信息,但是拿不到方法的参数信息。
3. 切片(Aspect)能拿到方法的参数信息,但是拿不到http请求信息。
他们三个各有优缺点,需要根据自己的业务需求来选择最适合的拦截机制。
preHandle
方法内返回 false 进行中断Spring的Interceptor(拦截器)与Servlet的Filter有相似之处,都能实现权限检查、日志记录等。不同的是:
Filter | Interceptor | 摘要 |
---|---|---|
Filter 接口定义在 javax.servlet 包中 | 接口 HandlerInterceptor 定义在org.springframework.web.servlet 包中 | |
Filter 定义在 web.xml 中 | ||
Filter在只在 Servlet 前后起作用。Filters 通常将 请求和响应(request/response) 当做黑盒子,Filter 通常不考虑servlet 的实现。 | 拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。允许用户介入(hook into)请求的生命周期,在请求过程中获取信息,Interceptor 通常和请求更加耦合。 | 在Spring构架的程序中,要优先使用拦截器。几乎所有 Filter 能够做的事情, interceptor 都能够轻松的实现1 |
Filter 是 Servlet 规范规定的。 | 而拦截器既可以用于Web程序,也可以用于Application、Swing程序中。 | 使用范围不同 |
Filter 是在 Servlet 规范中定义的,是 Servlet 容器支持的。 | 而拦截器是在 Spring容器内的,是Spring框架支持的。 | 规范不同 |
Filter 不能够使用 Spring 容器资源 | 拦截器是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如 Service对象、数据源、事务管理等,通过IoC注入到拦截器即可 | Spring 中使用 interceptor 更容易 |
Filter 是被 Server(like Tomcat) 调用 | Interceptor 是被 Spring 调用 | 因此 Filter 总是优先于 Interceptor 执行 |
1、过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
如下图:
2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。
过滤器拦截器运行先后步骤:
其中第2步,SpringMVC的机制是由DispaterServlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的.
3、过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射,代理分静态代理和动态代理,动态代理是拦截器的简单实现。
过滤器Filter,是在Servlet规范中定义的,是Servlet容器支持的,该接口定义在 javax.servlet
包下,主要是在客户端请求(HttpServletRequest)进行预处理,以及对服务器响应(HttpServletResponse)进行后处理。接口代码如下:
- package javax.servlet;
-
- import java.io.IOException;
-
- public interface Filter {
- void init(FilterConfig var1) throws ServletException;
-
- void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
-
- void destroy();
- }
对上面三个接口方法进行分析:
FilterChain.doFilter
可以将请求继续传递下去,如果想拦截这个请求,可以不调用FilterChain.doFilter,那么这个请求就直接返回了,所以Filter是一种责任链设计模式,在spring security
就大量使用了过滤器,有一条过滤器链在springboot自定义Filter类如下:
例子1:
- @Component
- public class MyFilter implements Filter {
- private Logger logger = LoggerFactory.getLogger(MyFilter.class);
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- logger.info("filter init");
- }
-
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- logger.info("doFilter");
- //对request,response进行预处理
- //TODO 进行业务逻辑
- filterChain.doFilter(servletRequest, servletResponse);
- }
-
- @Override
- public void destroy() {
- logger.info("filter destroy");
- }
- }
例子2:
- package com.zhongcy.filter
-
-
- @WebFilter(urlPatterns = "/*")
- @Order(value = 1)
- public class TestFilter implements Filter {
-
- private static final Set<String> ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(
- Arrays.asList("/main/excludefilter", "/login", "/logout", "/register")));
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- System.out.println("init-----------filter");
- }
-
- @Override
- public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest) req;
- HttpServletResponse response = (HttpServletResponse) res;
- String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/]+$", "");
- boolean allowedPath = ALLOWED_PATHS.contains(path);
-
- if (allowedPath) {
- System.out.println("这里是不需要处理的url进入的方法");
- chain.doFilter(req, res);
- }
- else {
- System.out.println("这里是需要处理的url进入的方法");
- }
- }
-
- @Override
- public void destroy() {
- System.out.println("destroy----------filter");
- }
- }
关于为什么SpringBoot中使用了@WebFilter但是过滤器却没有生效:一定要加上@Configuration注解,@Service其实也可以,其他类似。
- @SpringBootApplication
- @EnableTransactionManagement
- @MapperScan("com.zhongcy.repository")
- @ServletComponentScan("com.zhongcy.filter")
- public class Application {
-
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
-
- }
在springboot中提供了FilterRegistrationBean
方式,此类提供setOrder方法,可以为多个filter设置排序值。代码如下:
- @Configuration
- public class FilterConfig {
- /**
- * 配置一个Filter注册器
- * @return
- */
- @Bean
- public FilterRegistrationBean filterRegistrationBean1() {
- FilterRegistrationBean registrationBean = new FilterRegistrationBean();
- registrationBean.setFilter(filter1());
- registrationBean.setName("filter1");
- //设置顺序
- registrationBean.setOrder(10);
- return registrationBean;
- }
- @Bean
- public FilterRegistrationBean filterRegistrationBean2() {
- FilterRegistrationBean registrationBean = new FilterRegistrationBean();
- registrationBean.setFilter(filter2());
- registrationBean.setName("filter2");
- //设置顺序
- registrationBean.setOrder(3);
- return registrationBean;
- }
- @Bean
- public Filter filter1() {
- return new MyFilter();
- }
-
- @Bean
- public Filter filter2() {
- return new MyFilter2();
- }
- }
拦截器是Spring提出的概念,它的作用于过滤器类似,可以拦截用户请求并进行相应的处理,它可以进行更加精细的控制。
在SpringMVC中,DispatcherServlet捕获每个请求,在到达对应的Controller之前,请求可以被拦截器处理,在拦截器中进行前置处理后,请求最终才到达Controller。
拦截器的接口是 org.springframework.web.servlet.HandlerInterceptor
接口,接口代码如下:
- public interface HandlerInterceptor {
- default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- return true;
- }
-
- default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
- }
-
- default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
- }
- }
接口方法解读:
preHandle
方法返回 true。具体来说,postHandler
方法会在DispatcherServlet进行视图返回渲染前被调用,也就是说我们可以在这个方法中对 Controller 处理之后的ModelAndView
对象进行操作preHandle
方法的返回值为 true才行。该方法一般用于资源清理工作- public class MyInterceptor implements HandlerInterceptor {
- private Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- logger.info("preHandle....");
- return true;
- }
-
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
- logger.info("postHandle...");
- }
-
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
- logger.info("afterCompletion...");
- }
- }
- @Configuration
- public class WebMvcConfig implements WebMvcConfigurer {
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(handlerInterceptor())
- //配置拦截规则
- .addPathPatterns("/**");
- }
- @Bean
- public HandlerInterceptor handlerInterceptor() {
- return new MyInterceptor();
- }
- }
在springMVC中我们可以实现多个拦截器,并依次将他们注册进去,如下:
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(handlerInterceptor())
- .addPathPatterns("/**");
- registry.addInterceptor(handlerInterceptor2())
- .addPathPatterns("/**");
- }
拦截器是在DispatcherServlet
这个servlet中执行的,因此所有的请求最先进入Filter,最后离开Filter。其顺序如下。
Filter->Interceptor.preHandle->Handler->Interceptor.postHandle->Interceptor.afterCompletion->Filter。
拦截器的顺序也跟他们注册时的顺序有关,至少 preHandle
方法是这样,下图表示了两个拦截器协同工作时的执行顺序:
上图出自慕课网
后台打印日志也输出了相同的执行顺序:
- io-9999-exec-2] c.p.filter.interceptor.MyInterceptor : preHandle....
- 2018-09-13 12:13:31.292 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : preHandle2....
- 2018-09-13 12:13:31.388 INFO 9736 --- [nio-9999-exec-2] c.p.filter.controller.HelloController : username:pjmike,password:123456
- 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : postHandle2...
- 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor : postHandle...
- 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : afterCompletion2...
- 2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor : afterCompletion...
两种方式:
两种方式的本质都是一样的,都是去FilterRegistrationBean注册自定义Filter
- package com.hwm.filter;
-
- import javax.servlet.*;
- import java.io.IOException;
-
- public class MyFilter implements Filter {
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- }
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- // do something 处理request 或response
- System.out.println("filter1");
- // 调用filter链中的下一个filter
- filterChain.doFilter(servletRequest,servletResponse);
- }
- @Override
- public void destroy() {
- }
- }
- @Configuration
- public class FilterConfig {
- @Bean
- public FilterRegistrationBean registrationBean() {
- FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter());
- filterRegistrationBean.addUrlPatterns("/*");
- return filterRegistrationBean;
- }
- }
- // 注入spring容器
- @Component
- // 定义filterName 和过滤的url
- @WebFilter(filterName = "my2Filter" ,urlPatterns = "/*")
- public class My2Filter implements Filter {
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- }
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- System.out.println("filter2");
- }
- @Override
- public void destroy() {
- }
- }
- public class MyInterceptor implements HandlerInterceptor {
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- System.out.println("preHandle");
- return true;
- }
-
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
- System.out.println("postHandle");
- }
-
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
- System.out.println("afterCompletion");
- }
- }
- @Configuration
- public class InterceptorConfig implements WebMvcConfigurer {
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(new MyInterceptor());
- }
- }
- @RestController
- public class UController {
- @GetMapping("/home")
- public String home(){
- System.out.println("home");
- return "myhome";
- }
- }
interceptor 的执行顺序大致为:
Spring 中主要通过 HandlerInterceptor 接口来实现请求的拦截,实现 HandlerInterceptor 接口需要实现下面三个方法:
统计请求耗时
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
- import org.apache.log4j.Logger;
- import org.springframework.web.servlet.ModelAndView;
- import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
-
- public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter{
-
- private static final Logger logger = Logger.getLogger(ExecuteTimeInterceptor.class);
-
- //before the actual handler will be executed
- public boolean preHandle(HttpServletRequest request,
- HttpServletResponse response, Object handler)
- throws Exception {
-
- long startTime = System.currentTimeMillis();
- request.setAttribute("startTime", startTime);
-
- return true;
- }
-
- //after the handler is executed
- public void postHandle(
- HttpServletRequest request, HttpServletResponse response,
- Object handler, ModelAndView modelAndView)
- throws Exception {
-
- long startTime = (Long)request.getAttribute("startTime");
-
- long endTime = System.currentTimeMillis();
-
- long executeTime = endTime - startTime;
-
- //modified the exisitng modelAndView
- modelAndView.addObject("executeTime",executeTime);
-
- //log it
- if(logger.isDebugEnabled()){
- logger.debug("[" + handler + "] executeTime : " + executeTime + "ms");
- }
- }
- }
例子来源 mkyong
使用mvc:interceptors标签来声明需要加入到SpringMVC拦截器链中的拦截器
- <mvc:interceptors>
- <!-- 使用bean定义一个Interceptor,直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求 -->
- <bean class="com.company.app.web.interceptor.AllInterceptor"/>
- <mvc:interceptor>
- <mvc:mapping path="/**"/>
- <mvc:exclude-mapping path="/parent/**"/>
- <bean class="com.company.authorization.interceptor.SecurityInterceptor" />
- </mvc:interceptor>
- <mvc:interceptor>
- <mvc:mapping path="/parent/**"/>
- <bean class="com.company.authorization.interceptor.SecuritySystemInterceptor" />
- </mvc:interceptor>
- </mvc:interceptors>
可以利用mvc:interceptors标签声明一系列的拦截器,然后它们就可以形成一个拦截器链,拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。
在mvc:interceptors标签下声明interceptor主要有两种方式:
经过上述两步之后,定义的拦截器就会发生作用对特定的请求进行拦截了。
实现拦截器可以自定义实现HandlerInterceptor
接口,也可以通过继承HandlerInterceptorAdapter
类,后者是前者的实现类。下面是拦截器的一个实现的例子,目的是判断用户是否登录。如果preHandle
方法return true
,则继续后续处理。
- public class LoginInterceptor extends HandlerInterceptorAdapter {
- /**
- *预处理回调方法,实现处理器的预处理(如登录检查)。
- *第三个参数为响应的处理器,即controller。
- *返回true,表示继续流程,调用下一个拦截器或者处理器。
- *返回false,表示流程中断,通过response产生响应。
- */
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
- Object handler) throws Exception {
- System.out.println("-------------------preHandle");
- // 验证用户是否登陆
- Object obj = request.getSession().getAttribute("username");
- if (obj == null || !(obj instanceof String)) {
- response.sendRedirect(request.getContextPath() + "/index.html");
- return false;
- }
- return true;
- }
-
- /**
- *当前请求进行处理之后,也就是Controller 方法调用之后执行,
- *但是它会在DispatcherServlet 进行视图返回渲染之前被调用。
- *此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理。
- */
- @Override
- public void postHandle(HttpServletRequest request, HttpServletResponse response,
- Object handler, ModelAndView modelAndView) throws Exception {
- System.out.println("-------------------postHandle");
- }
-
- /**
- *方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。
- *这个方法的主要作用是用于进行资源清理工作的。
- */
- @Override
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
- Object handler, Exception ex) throws Exception {
- System.out.println("-------------------afterCompletion");
- }
-
- }
为了使自定义的拦截器生效,需要注册拦截器到spring容器中,具体的做法是继承WebMvcConfigurerAdapter类,覆盖其addInterceptors(InterceptorRegistry registry)方法。最后别忘了把Bean注册到Spring容器中,可以选择@Component 或者 @Configuration。
- @Component
- public class InterceptorConfiguration extends WebMvcConfigurerAdapter{
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- // 注册拦截器
- InterceptorRegistration ir = registry.addInterceptor(new LoginInterceptor());
- // 配置拦截的路径
- ir.addPathPatterns("/**");
- // 配置不拦截的路径
- ir.excludePathPatterns("/**.html");
-
- // 还可以在这里注册其它的拦截器
- //registry.addInterceptor(new OtherInterceptor()).addPathPatterns("/**");
- }
- }
拦截器本质上是面向切面编程(AOP),符合横切关注点的功能都可以放在拦截器中来实现,主要的应用场景包括:
首先说一下Filter的使用地方,我们在配置web.xml时,总会配置下面一段设置字符编码,不然会导致乱码问题:
- <filter>
- <filter-name>encoding</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>
- <init-param>
- <param-name>forceEncoding</param-name>
- <param-value>true</param-value>
- </init-param>
- </filter>
-
- <filter-mapping>
- <filter-name>encoding</filter-name>
- <servlet-name>/*</servlet-name>
- </filter-mapping>
拦截器的配置一般在SpringMVC的配置文件中,使用Interceptors标签,具体配置如下:
- <mvc:interceptors>
- <mvc:interceptor>
- <mvc:mapping path="/**" />
- <bean class="com.scorpios.atcrowdfunding.web.LoginInterceptor"></bean>
- </mvc:interceptor>
- <mvc:interceptor>
- <mvc:mapping path="/**" />
- <bean class="com.scorpios.atcrowdfunding.web.AuthInterceptor"></bean>
- </mvc:interceptor>
- </mvc:interceptors>
参考:拦截器(Interceptor)和过滤器(Filter)的执行顺序和区别_scorpio的博客-CSDN博客_过滤器和拦截器的区别
以下示例是基于Spring Boot实战系列(2)数据存储之Jpa操作MySQL chapter2-1可在Github获取源码
项目根目录 pom.xml
添加依赖 spring-boot-starter-aop
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
答案是否。只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy。
@Aspect
: 切面,由通知和切入点共同组成,这个注解标注在类上表示为一个切面。@Joinpoint
: 连接点,被AOP拦截的类或者方法,在前置通知中有介绍使用@Joinpoint
获取类名、方法、请求参数。Advice
: 通知的几种类型 @Before
: 前置通知,在某切入点@Pointcut
之前的通知 @After
: 后置通知,在某切入点@Pointcut
之后的通知无论成功或者异常。 @AfterReturning
: 返回后通知,方法执行return之后,可以对返回的数据做加工处理。 @Around
: 环绕通知,在方法的调用前、后执行。 @AfterThrowing
: 抛出异常通知,程序出错跑出异常会执行该通知方法。@Pointcut
: 切入点,从哪里开始。例如从某个包开始或者某个包下的某个类等。详情可见,《Spring官方文档》
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-ataspectj-advice-ordering
Spring AOP通过指定aspect的优先级,来控制不同aspect,advice的执行顺序,有两种方式:Aspect 类添加注解:org.springframework.core.annotation.Order,使用注解value属性指定优先级。
Aspect 类实现接口:org.springframework.core.Ordered,实现 Ordered 接口的 getOrder() 方法。
其中,数值越低,表明优先级越高,@Order 默认为最低优先级,即最大数值:
- /**
- * Useful constant for the lowest precedence value.
- * @see java.lang.Integer#MAX_VALUE
- */
- int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
同一aspect,相同advice的执行顺序并不能直接确定,而且 @Order 在advice方法上也无效,但是有如下两种变通方式:
将两个 advice 合并为一个 advice,那么执行顺序就可以通过代码控制了
将两个 advice 分别抽离到各自的 aspect 内,然后为 aspect 指定执行顺序
Spring事务管理(Transaction Management),也是基于Spring AOP。
在Spring AOP的使用中,有时我们必须明确自定义aspect的优先级低于或高于事务切面(Transaction Aspect),所以我们需要知道:
事务切面优先级:默认为最低优先级
LOWEST_PRECEDENCE = Integer.MAX_VALUE
如何修改事务切面的优先级:
在开启事务时,通过设置 @EnableTransactionManagement 和 <tx:annotation-driven/> 中的, order 属性来修改事务切面的优先级。
详情可见,《Spring官方文档》https://docs.spring.io/spring/docs/4.3.18.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#tx-annotation-driven-settings
配置:
- <!-- 註解式事務的支持 -->
- <tx:annotation-driven transaction-manager="txManager" order="0"/>
- <!-- 服務事務註冊切面 -->
- <aop:config >
- <aop:pointcut expression="execution(* oddtech.service.impl.*.*(..))"
- id="txPoint" />
- <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint" order="1"/>
- </aop:config>
-
- 参考:https://blog.csdn.net/glory1234work2115/article/details/51817893
-
- 或者代码:
-
- //@Transactional 的优先级默认为LOWEST_PRECEDENCE
-
- @Aspect
- @Order(100) //优先级设置高点 这里要求优先级大于Transactional
-
目录 aspect
下 新建 httpAspect.java
类
,在收到请求之后先记录请求的相关参数日志信息,请求成功完成之后打印响应信息,请求处理报错打印报错日志信息。
httpAspect.java
- package com.angelo.aspect;
-
- import com.google.gson.Gson;
- import com.google.gson.GsonBuilder;
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.stereotype.Component;
- import org.springframework.web.context.request.RequestContextHolder;
- import org.springframework.web.context.request.ServletRequestAttributes;
-
- import javax.servlet.http.HttpServletRequest;
- import java.util.HashMap;
- import java.util.Map;
-
- @Aspect
- @Component
- public class HttpAspect {
- // 打印日志模块
- private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class);
-
- // 下面会一一介绍...
定义切入的入口在哪里,封装一个公共的方法实现复用
httpAspect.java
- /**
- * 定义一个公共的方法,实现服用
- * 拦截UserController下面的所有方法
- * 拦截UserController下面的userList方法里的任何参数(..表示拦截任何参数)写法:@Before("execution(public * com.angelo.controller.UserController.userList(..))")
- */
- @Pointcut("execution(public * com.angelo.controller.UserController.*(..))")
- public void log() {
- }
拦截方法之前的一段业务逻辑,获取请求的一些信息,其中用到了Gson
处理对象转json输出
httpAspect.java
- @Before("log()")
- public void doBefore(JoinPoint joinPoint) {
- ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
- HttpServletRequest request = attributes.getRequest();
-
- Map params = new HashMap();
- params.put("url", request.getRequestURL()); // 获取请求的url
- params.put("method", request.getMethod()); // 获取请求的方式
- params.put("ip", request.getRemoteAddr()); // 获取请求的ip地址
- params.put("className", joinPoint.getSignature().getDeclaringTypeName()); // 获取类名
- params.put("classMethod", joinPoint.getSignature().getName()); // 获取类方法
- params.put("args", joinPoint.getArgs()); // 请求参数
-
- // 输出格式化后的json字符串
- Gson gson = new GsonBuilder().setPrettyPrinting().create();
-
- logger.info("REQUEST: {}", gson.toJson(params));
- }
拦截方法之后的一段业务逻辑
httpAspect.java
- @After("log()")
- public void doAfter() {
- logger.info("doAfter");
- }
环绕通知是在方法的前后的一段逻辑操作,可以修改目标方法的返回值,第一个参数是org.aspectj.lang.ProceedingJoinPoint
类型,注意这里要调用执行目标方法proceed()
获取值返回,不然会造成空指针异常。在环绕通知里面也可以捕获错误返回。
httpAspect.java
- @Around("log()")
- public Object doAround(ProceedingJoinPoint point) {
- try {
- Object o = point.proceed();
- System.out.println("方法环绕proceed,结果是 :" + o);
- logger.info("doAround1");
-
- return o;
- } catch (Throwable e) {
- // e.printStackTrace();
- logger.info("doAround2");
-
- return null;
- }
- }
在切入点完成之后的返回通知,此时就不会抛出异常通知,除非返回后通知的业务逻辑报错。
httpAspect.java
- /**
- * 获取响应返回值
- * @param object
- */
- @AfterReturning(returning = "object", pointcut = "log()")
- public void doAfterReturning(Object object) {
- // logger.info("RESPONSE: {}", object); 会打印出一个对象,想打印出具体内容需要在定义模型处加上toString()
-
- logger.info("RESPONSE: {}", object.toString());
- }
抛出异常后的通知,此时返回后通知@AfterReturning
就不会执行。
httpAspect.java
- @AfterThrowing(pointcut = "log()")
- public void doAfterThrowing() {
- logger.error("doAfterThrowing: {}", " 异常情况!");
- }
一段段伪代码读懂执行顺序
- try {
- // @Before 执行前通知
-
- // 执行目标方法
-
- // @Around 执行环绕通知 成功走finall,失败走catch
- } finally {
- // @After 执行后置通知
-
- // @AfterReturning 执行返回后通知
- } catch(e) {
- // @AfterThrowing 抛出异常通知
- }
对controller/UserController.java
文件的userList
方法增加了exception
参数
- /**
- * 查询用户列表
- * @return
- */
- @RequestMapping(value = "/user/list/{exception}")
- public List<User> userList(@PathVariable("exception") Boolean exception) {
- if (exception) {
- throw new Error("测试抛出异常!");
- }
-
- return userRepository.findAll();
- }
通过以上两种情况测试可以看到环绕通知在正常、异常两种情况都可以执行到。
Github查看本文完整示例 chapter3-1
Github: Spring Boot实战系列
简单总结一下,拦截器相比过滤器有更细粒度的控制,依赖于Spring容器,可以在请求之前或之后启动,过滤器主要依赖于servlet,过滤器能做的,拦截器基本上都能做。
参考:
spring boot 过滤器、拦截器的区别与使用_hwm的专栏-CSDN博客_springboot过滤器和拦截器的区别
在spring-boot工程中添加spring mvc拦截器 - 简书
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。