Springboot interception mode

In real projects, we often need to output request parameters, response results, method time, uniform permission verification, etc.

This article begins with an introduction to three common interception implementations for HTTP requests and a comparison of the differences.

(1) Aspect-based interception

(2) HandlerInterceptor based interception

(3) Interception based on ResponseBodyAdvice

Recommended reading:

Unified logging Framework: github.com/houbb/auto-…

Springboot Example

To help you learn, let’s start with the most basic SpringBoot example.

Maven is introduced into

Import the necessary JAR packages.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9. RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.8.10</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.10</version>
    </dependency>
</dependencies>
<! -- Package as an executable jar -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
Copy the code

Start the class

Implement the simplest startup class.

@SpringBootApplication
public class Application {

    public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code

Define the Controller

For the sake of demonstration, let’s first implement a simple controller.

@RestController
public class IndexController {

    @RequestMapping("/index")
    public AsyncResp index(a) {
        AsyncResp asyncResp = new AsyncResp();
        asyncResp.setResult("ok");
        asyncResp.setRespCode("00");
        asyncResp.setRespDesc("Success");

        System.out.println("IndexController# index." + asyncResp);
        returnasyncResp; }}Copy the code

AsyncResp is defined as follows:

public class AsyncResp {

    private String respCode;

    private String respDesc;

    private String result;


    // getter & setter & toString()
}
Copy the code

Interception mode definition

Based on the Aspect

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/ * * * *@author binbin.hou
 * @since1.0.0 * /
@Aspect
@Component
@EnableAspectJAutoProxy
public class AspectLogInterceptor {

    /** * Log instance *@since1.0.0 * /
    private static final Logger LOG = LoggerFactory.getLogger(AspectLogInterceptor.class);

    /** * intercepts all public methods */ under controller
    @Pointcut("execution(public * com.github.houbb.springboot.learn.aspect.controller.. * (..) )"
    public void pointCut(a) {
        //
    }

    /** * Intercepting processing **@paramPoint Point information *@return result
     * @throws Throwable if any
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        try {
            / / 1. Set the MDC

            // Get the signature of the currently intercepted method
            String signatureShortStr = point.getSignature().toShortString();
            //2. Print input parameter information
            Object[] args = point.getArgs();
            LOG.info("{} parameter: {}", signatureShortStr, Arrays.toString(args));

            //3. Print the result
            Object result = point.proceed();
            LOG.info({} Result: {}", signatureShortStr, result);
            return result;
        } finally {
            / / remove the MDC}}}Copy the code

The advantage of this implementation is that it is more general and can be combined with annotations to achieve more flexible and powerful functionality.

It’s a personal favorite.

Main uses:

(1) Log entry/exit parameter

(2) Set TraceId in a unified manner

(3) Method call time statistics

Based on a HandlerInterceptor

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/ * * *@author binbin.hou
 * @since1.0.0 * /
@Component
public class LogHandlerInterceptor implements HandlerInterceptor {

    private Logger logger = LoggerFactory.getLogger(LogHandlerInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // Unified permission verification, routing, etc
        logger.info("LogHandlerInterceptor#preHandle "{}, request.getRequestURI());

        if (request.getDispatcherType().equals(DispatcherType.ASYNC)) {
            return true;
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("LogHandlerInterceptor# postHandle calls");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}}Copy the code

Then you need to specify the relationship between the corresponding URL and the interception mode to take effect:

import com.github.houbb.springboot.learn.aspect.aspect.LogHandlerInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/** * Spring MVC configuration *@since1.0.0 * /
@Configuration
public class SpringMvcConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private LogHandlerInterceptor logHandlerInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logHandlerInterceptor)
                .addPathPatterns("/ * *")
                .excludePathPatterns("/version");
        super.addInterceptors(registry); }}Copy the code

The advantage of this approach is the flexibility to specify different interception methods based on the URL.

The disadvantage is that it is mainly used at the Controller layer.

Based on the ResponseBodyAdvice

This interface has the beforeBodyWrite method whose body is the response body in the response object, so we can use this method to do something uniform on the response body.

Such as encryption, signature and so on.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;

/ * * *@author binbin.hou
 * @since1.0.0 * /
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    /** * Log instance *@since1.0.0 * /
    private static final Logger LOG = LoggerFactory.getLogger(MyResponseBodyAdvice.class);

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        // This place does not execute the beforeBodyWrite method if false is returned
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object resp, MethodParameter methodParameter, MediaType mediaType, Class
       > aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        String uri = serverHttpRequest.getURI().getPath();
        LOG.info("MyResponseBodyAdvice#beforeBodyWrite Request address: {}", uri);

        ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) serverHttpRequest;
        HttpServletRequest servletRequest = servletServerHttpRequest.getServletRequest();

        // Can do unified interception mode processing

        // The result can be dynamically modified
        LOG.info("MyResponseBodyAdvice#beforeBodyWrite Response result: {}", resp);
        returnresp; }}Copy the code

test

We launch the application, page access:

http://localhost:18080/index

Page response:

{" respCode ":" 00 ", "respDesc" : "success", "result" : "ok"}Copy the code

Back-end logging:

c.g.h.s.l.a.a.LogHandlerInterceptor : LogHandlerInterceptor# preHandle request address: / index C.G.H.S.L.A.A spect. AspectLogInterceptor: IndexController. The index () parameters: [] IndexController# index: AsyncResp {respCode = '00' respDesc = 'success', the result = 'ok'} C.G.H.S.L.A.A spect. AspectLogInterceptor: Results: the IndexController. The index () AsyncResp {respCode = '00' respDesc = 'success', the result = 'ok'} C.G.H.S.L.A.A spect. MyResponseBodyAdvice: MyResponseBodyAdvice# beforeBodyWrite request address: / index C.G.H.S.L.A.A spect. MyResponseBodyAdvice: MyResponseBodyAdvice#beforeBodyWrite Response result: AsyncResp {respCode = '00' respDesc = 'success', the result = 'ok'} C.G.H.S.L.A.A.L ogHandlerInterceptor: LogHandlerInterceptor# postHandle callCopy the code

The order of execution here is also quite clear and will not be repeated here.

Asynchronous execution

Of course, if only the above content, is not the focus of this article.

Let’s take a look at what happens if asynchronous execution is introduced.

Define asynchronous thread pools

Defining asynchronous thread pools in SpringBoot is very simple.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/** * request asynchronous processing configuration **@author binbin.hou
 */
@Configuration
@EnableAsync
public class SpringAsyncConfig {

    @Bean(name = "asyncPoolTaskExecutor")
    public AsyncTaskExecutor taskExecutor(a) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(10);
        executor.setCorePoolSize(10);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        returnexecutor; }}Copy the code

Controller that executes asynchronously

@RestController
public class MyAsyncController extends BaseAsyncController<String> {

    @Override
    protected String process(HttpServletRequest request) {
        return "ok";
    }

    @RequestMapping("/async")
    public AsyncResp hello(HttpServletRequest request) {
        AsyncResp resp = super.execute(request);

        System.out.println(Controller#async result: + resp);
        returnresp; }}Copy the code

BaseAsyncController is implemented as follows:

@RestController
public abstract class BaseAsyncController<T> {

    protected abstract T process(HttpServletRequest request);

    @Autowired
    private AsyncTaskExecutor taskExecutor;

    protected AsyncResp execute(HttpServletRequest request) {
        // Asynchronous response result
        AsyncResp resp = new AsyncResp();
        try {
            taskExecutor.execute(new Runnable() {
                @Override
                public void run(a) {
                    try {
                        T result = process(request);

                        resp.setRespCode("00");
                        resp.setRespDesc("Success");
                        resp.setResult(result.toString());

                    } catch (Exception exception) {
                        resp.setRespCode("98");
                        resp.setRespDesc("Task exception"); }}}); }catch (TaskRejectedException e) {
            resp.setRespCode("99");
            resp.setRespDesc("Mission rejection.");
        }

        returnresp; }}Copy the code

Execute is also relatively simple to implement:

(1) The main thread creates an AsyncResp that returns.

(2) The thread pool asynchronously executes the concrete subclass method and sets the corresponding value.

thinking

Now, let me ask you a question.

If we request to http://localhost:18080/async, then:

(1) What is the return value of the page?

(2) What is the return value of the Aspect log output?

(3) What is the return value of ResponseBodyAdvice?

You can pause here and take notes on your answers.

test

Our page http://localhost:18080/async.

The page responds as follows:

{" respCode ":" 00 ", "respDesc" : "success", "result" : "ok"}Copy the code

Backend logging:

c.g.h.s.l.a.a.LogHandlerInterceptor : LogHandlerInterceptor# preHandle request address: / async C.G.H.S.L.A.A spect. AspectLogInterceptor: MyAsyncController. Hello (..) Parameters: The [org. Apache. Catalina. RequestFacade @ 7 e931750] Controller# async results: AsyncResp{respCode='null', respDesc='null', result='null'} c.g.h.s.l.a.aspect.AspectLogInterceptor : MyAsyncController.hello(..) Results: AsyncResp {respCode = 'null', respDesc = 'null', the result = 'null'} C.G.H.S.L.A.A spect. MyResponseBodyAdvice: MyResponseBodyAdvice# beforeBodyWrite request address: / async C.G.H.S.L.A.A spect. MyResponseBodyAdvice: MyResponseBodyAdvice#beforeBodyWrite Response result: AsyncResp {respCode = '00' respDesc = 'success', the result = 'ok'} C.G.H.S.L.A.A.L ogHandlerInterceptor: LogHandlerInterceptor# postHandle callCopy the code

The answer to our question above can be found by comparison:

(1) What is the return value of the page?

{" respCode ":" 00 ", "respDesc" : "success", "result" : "ok"}

You can get the result of asynchronous completion.

(2) What is the return value of the Aspect log output?

AsyncResp{respCode='null', respDesc='null', result='null'}

Unable to get asynchronous results.

(3) What is the return value of ResponseBodyAdvice?

AsyncResp{respCode='00', respDesc=' success ', result='ok'}

You can get the result of asynchronous completion.

This might seem a little strange, but what’s the underlying reason? How do you test that?

Asynchronous execution

why

In essence, asynchronous execution has little to do with the mechanics of Spring itself.

It’s just that the method itself takes time to execute asynchronously, and the later the interception is, the better it is to get the corresponding information if the asynchronous execution is finished.

Verify the way

How do you test this conjecture?

We can add a sleep to the process.

Code adjustment

  • BaseAsyncController.java

Execute logs are added to view the execution time.

taskExecutor.execute(new Runnable() {
    @Override
    public void run(a) {
        try {
            logger.info("AsyncResp#execute Asynchronously starts execution.");
            T result = process(request);
            resp.setRespCode("00");
            resp.setRespDesc("Success");
            resp.setResult(result.toString());
            logger.info("AsyncResp#execute Completes execution asynchronously.");
        } catch (Exception exception) {
            resp.setRespCode("98");
            resp.setRespDesc("Task exception"); }}});Copy the code
  • MyAsyncController.java

Add sleep time during execution.

@Override
protected String process(HttpServletRequest request) {
    try {
        TimeUnit.SECONDS.sleep(5);
        return "ok";
    } catch (InterruptedException e) {
        return "error"; }}Copy the code

test

Page to http://localhost:18080/async

The following page is displayed:

{"respCode":null,"respDesc":null,"result":null}
Copy the code

The corresponding log is as follows:

The 09:16:08 2021-07-10. 11008-661 the INFO [IO - 18080 - exec - 1] C.G.H.S.L.A.A.L ogHandlerInterceptor: LogHandlerInterceptor#preHandle / async 09:16:08 2021-07-10. 11008-685 the INFO/IO - 18080 - exec - 1 C.G.H.S.L.A.A spect. AspectLogInterceptor: MyAsyncController.hello(..) Parameters: The [org. Apache. Catalina. RequestFacade @ 1 d491e0] Controller# async results: AsyncResp{respCode='null', respDesc='null', Result = 'null'} 09:16:08 2021-07-10. 11008-722 the INFO [IO - 18080 - exec - 1] C.G.H.S.L.A.A spect. AspectLogInterceptor: MyAsyncController.hello(..) Results: AsyncResp{respCode='null', respDesc='null', Result = 'null'} 09:16:08 2021-07-10. 11008-722 the INFO] [lTaskExecutor - 1 C.G.H.S.L.A.C.B aseAsyncController: AsyncResp#execute Asynchronously starts execution. 09:16:08 2021-07-10. 11008-777 the INFO [IO - 18080 - exec - 1] C.G.H.S.L.A.A spect. MyResponseBodyAdvice: MyResponseBodyAdvice#beforeBodyWrite Request address: / async 09:16:08 2021-07-10. 11008-777 the INFO/IO - 18080 - exec - 1 C.G.H.S.L.A.A spect. MyResponseBodyAdvice: MyResponseBodyAdvice#beforeBodyWrite Response result: AsyncResp{respCode='null', respDesc='null', Result = 'null'} 09:16:08 2021-07-10. 11008-797 the INFO [IO - 18080 - exec - 1] C.G.H.S.L.A.A.L ogHandlerInterceptor: LogHandlerInterceptor#postHandle 2021-07-10 09:16:13.729 INFO 11008 C.G.H.S.L.A.C.B aseAsyncController: AsyncResp# execute asynchronous execution.Copy the code

You can see that Spring itself is still executing as normal, because process takes too long to execute, and all three interceptions fail to fetch asynchronous content.

reflection

Write here, own harvest is not little.

(1) The name of interceptor

So the title uses three interceptors at first. Admittedly, these are not strictly conflated.

Otherwise, as noted in the comments section, a filter could also be called an interceptor.

So the interceptor will be unified to the interception mode.

(2) Understanding of knowledge

When first implemented, the process was so short that it led to the illusion that Spring had special processing mechanisms.

Learning itself is still rigorous, so this paper has been revised.

summary

Hope you found this article helpful, and if you have any other ideas, please share them in the comments section.

All geeks’ likes, favorites and forwarding are the biggest motivation for Lao Ma to continue writing!

I am old ma, looking forward to meeting with you next time.