过滤器、拦截器、切片是java web开发中常用的技术,本篇讲解一下它们的使用方法及三者区别。
一、过滤器(Filter)
首先说明一下过滤器不是Spring提供的,不要被标题误导。它其实是Servlet的变种,是Java EE定义的。
1、实现Filter,统计每一个请求的耗时
@Component public class TimeFilter implements Filter{
@Override public void destroy() { System.out.println("TimeFilter destroy"); } @Override public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { System.out.println("TimeFilter start"); Long startTime=new Date().getTime(); arg2.doFilter(arg0, arg1); System.out.println("TimeFilter 耗时:"+(new Date().getTime()-startTime)); System.out.println("TimeFilter end"); } @Override public void init(FilterConfig arg0) throws ServletException { System.out.println("TimeFilter init"); } }
@RestController @RequestMapping("/user") public class UserController { @GetMapping(value="/{id}") public User getUserInfo(@PathVariable String id){ System.out.println("进入getUserInfo服务"); User user=new User(); user.setUsername("tom"); return user; } }
public class User { private String id; private String username; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
启动服务,执行初始化方法,控制台输出:TimeFilter init。浏览器访问http://localhost/user/1,打印日志如下:
2、使用FilterRegistrationBean注入Filter
上面的例子中使用@Component注解,将自定义的Filter纳入Spring管理,如果是第三方的Filter通常我们可以在web.xml中配置过滤器,但是Spring Boot项目中没有web.xml,怎样加载第三方的Filter呢?请看下面的例子。
@Configuration public class WebConfig{ @Bean public FilterRegistrationBean<TimeFilter> createTimeFilterBean(){ FilterRegistrationBean<TimeFilter> filterRegistrationBean=new FilterRegistrationBean<TimeFilter>(); TimeFilter timeFilter=new TimeFilter(); filterRegistrationBean.setFilter(timeFilter); List<String> urls=new ArrayList<String>(); urls.add("/*");//哪些路径起作用 filterRegistrationBean.setUrlPatterns(urls); return filterRegistrationBean; } }
现在去掉TimeFilter类上的注解@Component,启动服务进行测试,效果与上面一致。
二、拦截器(Interceptor)
1、实现Interceptor,统计每一个请求的耗时
@Component public class TimeInterceptor implements HandlerInterceptor { @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { System.out.println("=======afterCompletion 耗时:========"+(new Date().getTime()-((Long)request.getAttribute("startTime")))); System.out.println("Exception is: "+arg3); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object arg2, ModelAndView arg3) throws Exception { System.out.println("=======postHandle 耗时:========"+(new Date().getTime()-((Long)request.getAttribute("startTime")))); } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("=======preHandle========"); request.setAttribute("startTime",new Date().getTime()); System.out.println(((HandlerMethod)handler).getBean().getClass().getName()); System.out.println(((HandlerMethod)handler).getMethod().getName()); return true; } }
@Configuration public class WebConfig extends WebMvcConfigurerAdapter{ @Autowired private TimeInterceptor timeInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(timeInterceptor); } }
启动服务,浏览器访问http://localhost/user/1,打印日志如下:
preHandle:在controller中业务方法调用之前调用。返回true才执行业务方法。
postHandle:在controller中业务方法调用之后调用,如果抛出了异常,那么不调用该方法。
afterCompletion:最后调用,无论抛不抛异常该方法都调用(如果抛出的异常被自定义异常处理,则不进入该方法)。
2、测试抛出异常的情况
修改UserController中getUserInfo方法
@GetMapping(value="/{id}") public User getUserInfo(@PathVariable String id){ System.out.println("进入getUserInfo服务"); throw new RuntimeException("抛出的异常信息"); }
启动服务,浏览器访问http://localhost/user/1,打印日志如下:
说明:自定义的拦截器不光会拦截自己写的方法,如果url匹配,也会拦截Spring自己的方法。
三、切片(Aspect)
切片实际是Spring核心之一AOP,即我们通常说的面向切片编程。
1、简单介绍AOP
1)切片就是一个类,要把一个类声明为切片需要满足两个元素:一个是切入点(注解);一个是增强(方法)。
2)切入点有两个作用:一是说明在哪些方法上起作用 ;二是说明在什么时候起作用 。
3)方法是起作用时执行的逻辑,即我们为具体功能增强的统一方法。
4)符合切入点声明条件的方法,在执行时会根据切入点的声明在该方法执行之前或之后或抛出异常时去调用增强。
2、演示切片,统计每一个请求的耗时
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
@Aspect @Component public class TimeAspect { @Around("execution(* com.edu.sl.controller.UserController.*(..))") public Object TimeControllerMethod(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("time aspect start"); Object args[]=pjp.getArgs(); for(Object arg:args){ System.out.println("arg is "+arg); } Long startTime=new Date().getTime(); Object object=pjp.proceed();//调被拦截的方法,object就是返回方法的值。 System.out.println("time aspect 用时:"+(new Date().getTime()-startTime)); System.out.println("time aspect end"); return object; } }
启动服务,浏览器访问http://localhost/user/1,打印日志如下:
1)切入点在什么时候起作用是注解名称决定的,Spring提供了5个注解:
@Before:相当于拦截器的preHandle,具体方法执行之前,先调用增强。
@After:具体方法执行之后,再调用增强。
@AfterThrowing:具体方法抛出异常时,调用增强。
@AfterReturning:具体方法返回数据后调用增强。(不常用)
@Around:包围,这种方式覆盖了前面的4种,工作中一般都用Around方式。
2)切入点在哪些方法上起作用是表达式决定的。
"execution(* com.imooc.web.controller.UserController.*(..))":第一个*表示任何返回值,第二个*表示任何方法,..表示任何参数。
该表达式表示的是UserController中任何方法都会满足切入点。 更多表达式写法请到官方文档
四、三种拦截方法的区别和拦截顺序
1、三者区别
过滤器能获取到HTTP的请求和响应,不能获取这个请求是哪个Controller的哪个方法处理的。
拦截器能获取到HTTP的请求和响应和这个请求是哪个Controller的哪个方法处理的。能获取到异常。不能获取拦截的方法中的具体参数值。
切片不能获取到HTTP的请求和响应,能获取这个请求是哪个Controller的哪个方法处理的。能获取拦截的方法中的具体参数值。
2、拦截顺序
拦截顺序:过滤器-->拦截器-->切片,如果抛出异常,过滤器-->拦截器-->异常处理(ControllerAdvice)-->切片