本文最后更新于:March 10, 2023 am
SpringBoot框架中有两个非常重要的策略:开箱即用和约定优于配置。其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
目录 过滤器Filter 介绍
J2EE中的Servlet技术,基于Servlet的函数回调实现。在Filter中可以获取到请求中的 request和 response,但无法获取到响应的信息。
可以拦截所有请求。
使用步骤
实现 Filter 接口,重写 doFilter()方法。
通过调用 FilterChain.doFilter() 方法进行放行请求。
配置
使用xml。
使用@WebFilter注解,并在启动类上添加 @ServletComponentScan 注解。
直接使用 @Component 或者 @Configuration注解,这样的话@WebFilter配置的路径会失效。最终结果是拦截全部请求。因为加上 @Configuration或 @Component 的话就会执行2次过滤,一次是全局过滤,一次是只过滤urlPatterns中的路径,于是就出现了虽然配置了urlPatterns,但是却过滤了全局路径
多个Filter将会按照类的字母顺序进行排序执行。
示例
启动类
package com.tothefor.request;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.web.servlet.ServletComponentScan;@SpringBootApplication @ServletComponentScan public class RequestApplication { public static void main (String[] args) { SpringApplication.run(RequestApplication.class, args); } }
过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.tothefor.request.filter;import com.alibaba.fastjson.JSON;import org.springframework.context.annotation.Configuration;import org.springframework.core.annotation.Order;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import java.io.IOException;@WebFilter(value = "/*") public class filter_1 implements Filter { @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.print("filter_1 开始... " ); System.out.print("filter_1 拿到的request为:" + (servletRequest)); System.out.println(" filter_1 拿到的response为:" + (servletResponse)); filterChain.doFilter(servletRequest,servletResponse); System.out.println("filter_1 结束..." ); } }
Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.tothefor.request;import com.alibaba.fastjson.JSONObject;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class TestCont { @RequestMapping("/get") public Object get () { return new JSONObject() .fluentPut("flag" ,"ok" ) ; } }
然后访问对应路径。
打印
filter 开始... 拿到的request为:org.apache.catalina.connector.RequestFacade@6a2e82e8 拿到的response为:org.apache.catalina.connector.ResponseFacade@719a8436 filter 结束...
异常 演示出现异常的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.tothefor.request;import com.alibaba.fastjson.JSONObject;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class TestCont { @RequestMapping("/get") public Object get () { try { int i = 2 /0 ; }catch (Exception e){ e.printStackTrace(); } return new JSONObject() .fluentPut("flag" ,"ok" ) ; } }
输出:
filter_1 开始... filter_1 拿到的request为:org.apache.catalina.connector.RequestFacade@1398a1ee filter_1 拿到的response为:org.apache.catalina.connector.ResponseFacade@60845921 java.lang.ArithmeticException: / by zero filter_1 结束...
可以看见,不管是否抛出了异常,Filter里面都能执行。
拦截器Interceptor 介绍
来自Spring,不依赖于servlet容器,但依赖于Spring。
通过注入的方式可以获取到Spring中存在的Bean。只对action请求起作用,并可以获取到action请求的上下文。
使用步骤
实现 HandlerInterceptor
接口 或 继承 HandlerInterceptorAdapter
类,建议使用接口。
实现 preHandle
方法(在处理请求前运行),实现 postHandle
方法(在处理请求完毕后运行)。
再新建一个配置类,继承 WebMvcConfigurer
接口,再实现addInterceptors
方法,并在方法中注册该拦截器、配置拦截路径(不配置默认拦截所有请求)。
示例
拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.tothefor.request.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class interceptor_1 implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器 interceptor_1 preHandle 执行" ); return true ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("拦截器 interceptor_1 postHandle 执行" ); } }
配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.tothefor.request.interceptor;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new interceptor_1()); } }
接口还是同上。
输出
拦截器 interceptor_1 preHandle 执行 拦截器 interceptor_1 postHandle 执行
异常 访问接口均同上。看输出为:
拦截器 interceptor_1 preHandle 执行 java.lang.ArithmeticException: / by zero 拦截器 interceptor_1 postHandle 执行
ControllerAdvice 介绍
来自Spring,依赖于Spring。全局异常处理。
可以使用注解@ControllerAdvice,实现 ResponseBodyAdvice、RequestBodyAdvice 等接口,用于web项目的返回数据加强。
使用步骤
创建一个类,给类加上注解@RestControllerAdvice
。
在类中写一个方法,给方法加上注解 @ExceptionHandler(Exception.class)
,括号里写要拦截的异常,方法的参数也是这个异常或者这个异常的父类。
常配合自定义异常使用。
只有在抛出异常时,才会触发。正常执行是不会触发的。
示例
配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.tothefor.request.advice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice public class CustomException { @ExceptionHandler(Exception.class) public String allExceptionHandler (Exception e) { System.out.println("异常 CustomException 执行" ); return "系统异常,请稍后再试" ; } }
请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.tothefor.request;import com.alibaba.fastjson.JSONObject;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class TestCont { @RequestMapping("/get") public Object get () { try { int i = 2 /0 ; }catch (Exception e){ throw new RuntimeException("出错了" ); } return new JSONObject() .fluentPut("flag" ,"ok" ) ; } }
输出
异常 CustomException 执行 2023-02-25 16:38:16.316 WARN 7293 --- [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [java.lang.RuntimeException: 出错了]
界面展示的内容即为方法的返回值。
AOP 介绍
AOP是一种面向切面编程的实现,实现的方式还挺多的,有SpringAOP,也有AspectJ、CGLIB等。
使用步骤
创建一个类,给类加上@Aspect
和 @Component
注解。
在类中,定义切入点 @Pointcut()
,可以定义在注解上(使用注解的类或方法即切入点),也可以直接指定切入的范围。
根据需要定义 @Before("pointcut()")
和 @After("pointcut()")
,然后在内部写处理逻辑。
示例
依赖
<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > </dependency >
配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package com.tothefor.request.aop;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 java.util.Date;@Component @Aspect public class LogAopAspect { private static final Logger log = LoggerFactory.getLogger(LogAopAspect.class); @Pointcut(value = "@annotation(com.tothefor.request.aop.LogAopAnnotation)") public void logPoint () { } @Before(value = "logPoint()") public void beforeLogAop (JoinPoint joinPoint) { System.out.println("=========== beforeLogAop ============== " +new Date()); } @After(value = "logPoint()") public void afterLogAop (JoinPoint joinPoint) { System.out.println("=========== afterLogAop ============== " +new Date()); } @Around(value = "logPoint()") public Object aroundLogAop (ProceedingJoinPoint pjp) throws Throwable { System.out.println("=========== aroundLogAop ============== " +new Date()); return pjp.proceed(); } }
请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.tothefor.request;import com.alibaba.fastjson.JSONObject;import com.tothefor.request.aop.LogAopAnnotation;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class TestCont { @LogAopAnnotation @RequestMapping("/get") public Object get () { return new JSONObject() .fluentPut("flag" ,"ok" ) ; } }
输出
=========== aroundLogAop ============== Sat Feb 25 16:59:13 CST 2023 =========== beforeLogAop ============== Sat Feb 25 16:59:13 CST 2023 =========== afterLogAop ============== Sat Feb 25 16:59:14 CST 2023
异常 =========== aroundLogAop ============== Sat Feb 25 17:01:03 CST 2023 =========== beforeLogAop ============== Sat Feb 25 17:01:03 CST 2023 =========== afterLogAop ============== Sat Feb 25 17:01:03 CST 2023 2023-02-25 17:01:03.278 ERROR 7732 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 出错了] with root cause java.lang.RuntimeException: 出错了
整合 前面四种都使用。
正常请求 一切都是正常执行的。输出如下:
filter_1 开始... filter_1 拿到的request为:org.apache.catalina.connector.RequestFacade@73012211 filter_1 拿到的response为:org.apache.catalina.connector.ResponseFacade@6e5ef450 拦截器 interceptor_1 preHandle 执行 =========== aroundLogAop ============== Sat Feb 25 17:03:53 CST 2023 =========== beforeLogAop ============== Sat Feb 25 17:03:53 CST 2023 =========== afterLogAop ============== Sat Feb 25 17:03:53 CST 2023 拦截器 interceptor_1 postHandle 执行 filter_1 结束...
异常情况 中途抛出了异常。输出如下:
filter_1 开始... filter_1 拿到的request为:org.apache.catalina.connector.RequestFacade@79b1d11b filter_1 拿到的response为:org.apache.catalina.connector.ResponseFacade@2daf71e2 拦截器 interceptor_1 preHandle 执行 =========== aroundLogAop ============== Sat Feb 25 17:05:10 CST 2023 =========== beforeLogAop ============== Sat Feb 25 17:05:10 CST 2023 =========== afterLogAop ============== Sat Feb 25 17:05:10 CST 2023 异常 CustomException 执行 2023-02-25 17:05:10.690 WARN 7808 --- [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [java.lang.RuntimeException: 出错了] filter_1 结束...
总结 可以看到,执行顺序是:Filter过滤器 > Interceptor拦截器 > ControllerAdvice > AOP。
解释:Filter和Interceptor的执行顺序是可以直接看出来的。AOP、ControllerAdvice 的执行顺序得看打印的顺序。先是aop,然后才是异常被捕获,反方向先执行,说明 ControllerAdvice 是在 AOP 外面一层的。
另外,当抛出的异常被 ControllerAdvice 捕获之后, Interceptor 拦截器不会再有后置处理了,但是Filter过滤器还是有后置处理的 。
如图所示:
完整代码 Controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.tothefor.request;import com.alibaba.fastjson.JSONObject;import com.tothefor.request.aop.LogAopAnnotation;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class TestCont { @LogAopAnnotation @RequestMapping("/get") public Object get () { try { int i = 2 /0 ; }catch (Exception e){ throw new RuntimeException("出错了" ); } return new JSONObject() .fluentPut("flag" ,"ok" ) ; } }
过滤器Filter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.tothefor.request.filter;import com.alibaba.fastjson.JSON;import org.springframework.context.annotation.Configuration;import org.springframework.core.annotation.Order;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import java.io.IOException;@WebFilter(value = "/*") public class filter_1 implements Filter { @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.print("filter_1 开始... " ); System.out.print("filter_1 拿到的request为:" + (servletRequest)); System.out.println(" filter_1 拿到的response为:" + (servletResponse)); filterChain.doFilter(servletRequest,servletResponse); System.out.println("filter_1 结束..." ); } }
拦截器Interceptor
拦截器类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.tothefor.request.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class interceptor_1 implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器 interceptor_1 preHandle 执行" ); return true ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("拦截器 interceptor_1 postHandle 执行" ); } }
配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.tothefor.request.interceptor;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new interceptor_1()); } }
ControllerAdvice 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.tothefor.request.advice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice public class CustomException { @ExceptionHandler(Exception.class) public String allExceptionHandler (Exception e) { System.out.println("异常 CustomException 执行" ); return "系统异常,请稍后再试" ; } }
AOP
自定义注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.tothefor.request.aop;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface LogAopAnnotation { String value () default "墨水记忆" ; }
切面类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package com.tothefor.request.aop;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 java.util.Date;@Component @Aspect public class LogAopAspect { private static final Logger log = LoggerFactory.getLogger(LogAopAspect.class); @Pointcut(value = "@annotation(com.tothefor.request.aop.LogAopAnnotation)") public void logPoint () { } @Before(value = "logPoint()") public void beforeLogAop () { System.out.println("=========== beforeLogAop ============== " +new Date()); } @After(value = "logPoint()") public void afterLogAop () { System.out.println("=========== afterLogAop ============== " +new Date()); } @Around(value = "logPoint()") public Object aroundLogAop (ProceedingJoinPoint pjp) throws Throwable { System.out.println("=========== aroundLogAop ============== " +new Date()); return pjp.proceed(); } }