SpringBoot-(三十一)SpringBoot请求的执行与返回顺序

本文最后更新于: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将会按照类的字母顺序进行排序执行。

示例

启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;

/**
* @Author DragonOne
* @Date 2023/2/14 23:03
* @墨水记忆 www.tothefor.com
*/

@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;

/**
* @Author DragonOne
* @Date 2023/2/14 23:10
* @墨水记忆 www.tothefor.com
*/
@RestController
public class TestCont {

@RequestMapping("/get")
public Object get(){
return new JSONObject()
.fluentPut("flag","ok")
;
}
}

然后访问对应路径。

打印

1
2
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;

/**
* @Author DragonOne
* @Date 2023/2/14 23:10
* @墨水记忆 www.tothefor.com
*/
@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")
;
}
}

输出:

1
2
3
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;

/**
* @Author DragonOne
* @Date 2023/2/25 16:17
* @墨水记忆 www.tothefor.com
*/
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;

/**
* @Author DragonOne
* @Date 2023/2/25 16:18
* @墨水记忆 www.tothefor.com
*/

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new interceptor_1());
}
}

接口还是同上。

输出

1
2
拦截器 interceptor_1 preHandle 执行
拦截器 interceptor_1 postHandle 执行

异常

访问接口均同上。看输出为:

1
2
3
拦截器 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;

/**
* @Author DragonOne
* @Date 2023/2/25 16:31
* @墨水记忆 www.tothefor.com
*/


@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;

/**
* @Author DragonOne
* @Date 2023/2/14 23:10
* @墨水记忆 www.tothefor.com
*/
@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")
;
}
}

输出

1
2
异常 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()"),然后在内部写处理逻辑。

示例

依赖

1
2
3
4
<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;

/**
* @Author DragonOne
* @Date 2022/8/10 13:42
* @Title
* @Description
*/
@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());
}

//环绕增强,是在before前就会触发
@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;

/**
* @Author DragonOne
* @Date 2023/2/14 23:10
* @墨水记忆 www.tothefor.com
*/
@RestController
public class TestCont {

@LogAopAnnotation
@RequestMapping("/get")
public Object get(){
return new JSONObject()
.fluentPut("flag","ok")
;
}
}

输出

1
2
3
=========== 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

异常

1
2
3
4
5
6
=========== 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: 出错了

整合

前面四种都使用。

正常请求

一切都是正常执行的。输出如下:

1
2
3
4
5
6
7
8
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 结束...

异常情况

中途抛出了异常。输出如下:

1
2
3
4
5
6
7
8
9
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;

/**
* @Author DragonOne
* @Date 2023/2/14 23:10
* @墨水记忆 www.tothefor.com
*/
@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;

/**
* @Author DragonOne
* @Date 2023/2/14 23:03
* @墨水记忆 www.tothefor.com
*/
@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;

/**
* @Author DragonOne
* @Date 2023/2/25 16:17
* @墨水记忆 www.tothefor.com
*/
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;

/**
* @Author DragonOne
* @Date 2023/2/25 16:18
* @墨水记忆 www.tothefor.com
*/

@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;

/**
* @Author DragonOne
* @Date 2023/2/25 16:31
* @墨水记忆 www.tothefor.com
*/


@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;

/**
* @Author DragonOne
* @Date 2022/8/10 13:29
* @Title
* @Description
*/
@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;

/**
* @Author DragonOne
* @Date 2022/8/10 13:42
* @Title
* @Description
*/
@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());
}

//环绕增强,是在before前就会触发
@Around(value = "logPoint()")
public Object aroundLogAop(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("=========== aroundLogAop ============== "+new Date());
return pjp.proceed();
}

}