Filter、Interceptor和Aspect对请求的拦截,有什么不同?
在使用Spring MVC开发RESTful API的时候,我们经常会使用Java的拦截机制来处理请求,Filter是Servlet Api过滤器,Interceptor则是Spring自带的拦截器,而Aspect切面是Spring AOP一个概念,主要的使用场景有:日志记录、事务控制和异常处理,该篇文章主要说说它们是如何实现的以及他们之间的差别,在这过程中也会探讨全局异常处理机制的原理以及异常处理过程。
Filter
-
介绍:
java的过滤器,依赖于Sevlet,和框架无关的,是所有过滤组件中外层的,从粒度来说是大的,它主要是在过滤器中修改字符编码(CharacterEncodingFilter)、过滤掉没用的参数、简单的安全校验(比如登录不登录之类)
-
实现和配置方式
1.直接实现Filter接口+@Component
2.@Bean+@Configuration(第三方Filter)
3.web.xml配置方式
Filter的实现方式
@Componentpublic class TimeFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("初始化TimeFilter...");}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {System.out.println("-------TimeFilter Start--------");long start = new Date().getTime();filterChain.doFilter(request, response);System.out.println("TimeFilter执行耗时:" + (new Date().getTime() - start));System.out.println("-------TimeFilter End--------");}@Overridepublic void destroy() {System.out.println("销毁TimeFilter...");}}
@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter {@AutowiredTimeInterceptor timeInterceptor;@Beanpublic FilterRegistrationBean charsetFilter(){FilterRegistrationBean registrationBean = new FilterRegistrationBean();TimeFilter timeFilter = new TimeFilter();CharsetFilter charsetFilter = new CharsetFilter();registrationBean.setFilter(charsetFilter);registrationBean.setFilter(timeFilter);//相当于@webFilter的@WebInitParam()注解的作用Map<String,String> paramMap = new HashMap<>();paramMap.put("charset","utf-8");registrationBean.setInitParameters(paramMap);//相当于@webFilter的 urlPatterns = "/*"的作用List<String> urls = new ArrayList<>();urls.add("/*");//urls.add("/user/*");registrationBean.setUrlPatterns(urls);return registrationBean;}
//请求路径的{id}回传到方法里也就是传到(@PathVariable String id)的id里@RequestMapping(value = "/user/{id:\\d+}",method = RequestMethod.GET)@JsonView(User.UserDetailView.class) //这里因为UserDetailView继承了UserSimpleView所有会返回username和password@ApiOperation("获取用户信息")public User getInfo(@PathVariable Integer id) {// throw new UserNotExistException(id);System.out.println("进入getInfo()服务");User user = new User();user.setId(1);user.setUsername("jacklin");user.setPassword("123");return user;}
-
从上述结果,我们可以分析得出,当客户端发送请求,到达Controller方法之前,先执行Filter初始化操作,接着进入Controller的方法体,后执行完成,通过分析我们明白了Filter的工作原理和方法的执行顺序!
Interceptor
-
简介:
spring框架的拦截器,主要依赖于Spring MVC框架,它是在 service 或者一个方法调用前,调用一个方法,或者在方法调用后,调用一个方法。
-
实现和配置方式:
实现HandlerInterceptor接口看,并重写preHandle、postHandle、afterCompletion方法。
-
解释说明:
SpringMVC中的Interceptor是链式的调用的,在一个应用中或者是在一个请求中可以同时存在多个Interceptor,每个Inteceptor的调用都会按照它的声明顺序依次执行,而且先执行的Intecptor的preHandler方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断是否要继续进行下去。
该方法的返回值是Boolean类型的,当它返回为false时,表示请求结束,后续的Interceptor和Controller都不会再执行;
当返回值为true 时就会继续调用下一个Interceptor的preHandle方法,如果已经是后一个Interceptor的时候就会是调用当前请求的Controller方法。
Interceptor拦截器的实现方式
/*** @Author 林必昭* @Date 2019/7/4 13:15*/@Componentpublic class TimeInterceptor implements HandlerInterceptor {/*** preHandle方法的返回值是boolean值,当返回的是false时候,不会进入controller里的方法*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("------->preHandle");System.out.println("控制器类名:" + ((HandlerMethod) handler).getBean().getClass().getName()); //获取类名System.out.println("控制器中的对应的方法名:" + ((HandlerMethod) handler).getMethod().getName()); //获取类中方法名request.setAttribute("startTime", new Date().getTime());return true;}@Overridepublic 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("TimeInterceptor 执行耗时:" + " " + (new Date().getTime() - start));}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception {System.out.println("------->afterCompletion");Long start = (Long) request.getAttribute("startTime");System.out.println("TimeInterceptor 执行耗时:" + " " + (new Date().getTime() - start));System.out.println("Exception is " + e);}}
@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter {@AutowiredTimeInterceptor timeInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(timeInterceptor);}}
@RequestMapping(value = "/user/{id:\\d+}",method = RequestMethod.GET)@JsonView(User.UserDetailView.class) //这里因为UserDetailView继承了UserSimpleView所有会返回username和password@ApiOperation("获取用户信息")public User getInfo(@PathVariable Integer id) {/*** 当抛出UserNotExistException异常的时候,会跳到ControllerExceptionHandler的handleUserNotExistException方法* 进行相应的处理*/throw new RuntimeException("user not exist!!"); //这里抛出一个RuntimeException// System.out.println("进入getInfo()服务");// User user = new User();// user.setId(1);// user.setUsername("jacklin");// user.setPassword("123");// return user;}
结果很明显了,当控制层出现异常的时候,异常没有被全局处理器处理,到达拦截器,拦截器会捕获到异常,这时候只执行了preHandle和afterCompletionn方法,并没有执行postHandle方法,控制台也输出了异常信息。
public class UserNotExistException extends RuntimeException {private static final long serialVersionUID = -9136501205369741760L;private String id;public UserNotExistException(String id){super("user is not exist...");this.id = id;}public String getId() {return id;}public void setId(String id) {this.id = id;}}
/*** 全局异常处理,负责处理controller抛出的异常** @Author 林必昭* @Date 2019/7/4 11:31*/@ControllerAdvicepublic class GlobalExceptionHandler {@ExceptionHandler(UserNotExistException.class)@ResponseBody@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) //服务器内部错误public Map<String, Object> handleUserNotExistException(UserNotExistException ex) {Map<String, Object> resultMap = new HashMap<>();resultMap.put("id", ex.getId());resultMap.put("message", ex.getMessage());return resultMap;}}
public User getInfo(@PathVariable Integer id) {/*** 当抛出UserNotExistException异常的时候,会跳到ControllerExceptionHandler的handleUserNotExistException方法* 进行相应的处理*///throw new RuntimeException("user not exist!!");throw new UserNotExistException("user not exist!!")}
Aspect
<!-- 切面 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
切面拦截的实现方式
@Aspect@Componentpublic class TimeAspect {/*** 切入点*/@Around("execution(* com.lbz.web.controller.UserController.*(..))") //UserController下的任何方法被调用都会执行这个切片public Object handleControllerMethod(ProceedingJoinPoint point) throws Throwable {System.out.println("TimeAspect start");long start = new Date().getTime();Object object = point.proceed(); //proceed中文意思是继续的意思,也就是切入,相当于filterChain.doFilter()Object[] args = point.getArgs(); //与Filter和Interceptor的区别是,可以获取到UserController里方法的参数for (Object arg : args) {System.out.println("控制层的方法对应参数是:" + arg);}System.out.println("TimeAspect执行耗时:" + (new Date().getTime() - start));System.out.println("TimeAspect end");return object;}}
总结:
原创视频|使用 Docker 快速搭建本地 Mysql 环境
原创视频 | Spring MVC居然还能这样导出Excel
相关文章