SpringBoot interception

In actual projects, we often need to output request parameters, response results, method time consumption, uniform permission check and so on.

This article begins with a look at three common implementations of interception in HTTP requests and a comparison of the differences.

(1) Aspect-based interception

(2) Intercept mode based on HandlerInterceptor

(3) Interception method based on responseBodyAdvice

Recommended reading:

Unified logging framework: https://github.com/houbb/auto-log

SpringBoot entry case

To help you learn, let’s start with the most basic example of Spring Boot.

Maven is introduced into

Introduce 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> < grouppid >org.springframework.boot</ grouppid > <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> <! -- Package as an executable jar --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>

Start the class

Implement the simplest startup class possible.

@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}

Define the Controller

For demonstration purposes, let’s first implement a simple Controller.

@RestController public class IndexController { @RequestMapping("/index") public AsyncResp index() { AsyncResp asyncResp = new AsyncResp(); asyncResp.setResult("ok"); asyncResp.setRespCode("00"); AsyncResp. SetRespDesc (" success "); System.out.println("IndexController#index: "+ AsyncResp); return asyncResp; }}

The definition of AsyncResp is as follows:

public class AsyncResp {

    private String respCode;

    private String respDesc;

    private String result;


    // getter & setter & toString()
}

Definition of Interception Mode

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 * @Since 1.0.0 */ @Aspect @Component @EnableAspectJAutoProxy public class AspectLogInterceptor {/ * * * * LOG instance @ since 1.0.0 * / private static final Logger LOG = LoggerFactory.getLogger(AspectLogInterceptor.class); / * * * intercept controller for all public methods * / the @pointcut (" execution (public * com. Making. Houbb. Springboot. Learn. Aspect. Controller.. * (..) Throws Throwable if any */;)") public void pointCut() {//} /** * @Param * @return result * @throws Throwable if any */ @Around("pointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { try { //1. SignatureShortStr = Point.getSignature ().toShortString(); Object[] args = point.getArgs(); // Args = point.getArgs(); Log.info ("{} parameters: {}", signatureShortStr, Arrays.toString(args)); //3. Object result = point.proceed(); Log.info ("{} result: {}", signatureShortStr, result); return result; } finally {// remove MDC}}

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

It’s a way that I like very much.

Main uses:

(1) Logging out/in

(2) TraceID is uniformly set

(3) Time consuming statistics of method invocation

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; /** * @Component public class LoghandlerInterceptor implements HandlerInterceptor {/** * @Component public class LoghandlerInterceptor implements HandlerInterceptor {  private Logger logger = LoggerFactory.getLogger(LogHandlerInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Logger. Info ("LogHandlerInterceptor# Prehandle ") throws Exception {// Logger. Info ("LogHandlerInterceptor# Prehandle ") throws Exception {// Logger. {}", request.getRequestURI()); if (request.getDispatcherType().equals(DispatcherType.ASYNC)) { return true; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Throws Exception {Loger.info ("LogHandlerInterceptor# Posthandle call "); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }

Then you need to specify the relationship between the corresponding URL and the blocking method for this 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 MVCConfigurerAdapter public class Spring MVCConfig extends WebMVCConfigurerAdapter { @Autowired private LogHandlerInterceptor logHandlerInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(logHandlerInterceptor) .addPathPatterns("/**") .excludePathPatterns("/version"); super.addInterceptors(registry); }}

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 in the Controller layer.

Based on the ResponseBodyAdvice

This interface has the beforeBodyWrite method and the body is the response body in the response object Response, so we can use this method to do something consistent with the response body.

Encryption, signature, etc.

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 * @Since 1.0.0 */ @ControllerAdvice public class MyResponseBodyAdvice implements ResponseBodyAdvice < Object > {/ * * * * LOG instance @ since 1.0.0 * / private static final Logger LOG = LoggerFactory.getLogger(MyResponseBodyAdvice.class); Override public Boolean supports(methodParameter, methodParameter, Class aClass) {// This place returns false, Does not execute the beforeBodyWrite method return true; } @Override public Object beforeBodyWrite(Object resp, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<? >> 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(); LOG. Info (" myResponseBodyAdvice# beforeBodyWrite response results: {}", resp); return resp; }}

test

We launch the application, page access:

http://localhost:18080/index

Page response:

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

Backend 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 results: AsyncResp {respCode = '00' respDesc = 'success', the result = 'ok'} C.G.H.S.L.A.A.L ogHandlerInterceptor: LogHandlerInterceptor# postHandle call

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

Asynchronous execution

Of course, that’s not the point of this article.

Next, let’s look at what happens if asynchronous execution is introduced.

Define asynchronous thread pools

The asynchronous thread pool is defined in SpringBoot, which 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; Public class SpringAsyncConfig {@bean (name = @bean); /** * request asynchronization ** @Author binbin.hou */ @Configure@EnableAsync public class SpringAsyncConfig {@bean (name = @bean) "asyncPoolTaskExecutor") public AsyncTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setMaxPoolSize(10); executor.setQueueCapacity(10); executor.setCorePoolSize(10); executor.setWaitForTasksToCompleteOnShutdown(true); return executor; }}

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); Println ("Controller#async result: "+ resp); return resp; }}

The implementation of BaseAsyncController is as follows:

@RestController public abstract class BaseAsyncController<T> { protected abstract T process(HttpServletRequest request);  @Autowired private AsyncTaskExecutor taskExecutor; Protected AsyncResp Execute (HttpServletRequest Request) {// AsyncResp result AsyncResp = new AsyncResp(); try { taskExecutor.execute(new Runnable() { @Override public void run() { 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 (" Task denied "); } return resp; }}

Execute’s implementation is also relatively simple:

(1) The main thread creates an AsyncResp for return.

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

thinking

Now, let’s 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 the responseBodyAdvice log output?

You can pause here for a moment and record your answer.

test

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

The page response is as follows:

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

Logging on the back end:

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 results: AsyncResp {respCode = '00' respDesc = 'success', the result = 'ok'} C.G.H.S.L.A.A.L ogHandlerInterceptor: LogHandlerInterceptor# postHandle call

By comparison, you can find the answer to our question above:

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

{” respCode “:” 00 “, “respDesc” : “success”, “result” : “ok”}

You can get the result of the completion of the asynchronous execution.

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

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

Unable to retrieve asynchronous result.

(3) What is the return value of the responseBodyAdvice log output?

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

You can get the result of the completion of the asynchronous execution.

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

Asynchronous execution

why

In essence, asynchronous execution has little to do with Spring’s own mechanisms.

It’s just that the asynchronous execution of the method itself takes time, the more backward the intercepting method, if the asynchronous execution is finished, just can get the corresponding information.

Verify the way

How do you test this conjecture?

We can add a sleep to the process.

Code adjustment

  • BaseAsyncController.java

Execute adds some log information about the execution to make it easier to see the time.

TaskExecutor. Execute (new Runnable() {@Override public void run() {try {logger.info(" Asyncrespp # Execute starts async. "); ); T result = process(request); resp.setRespCode("00"); Resp. SetRespDesc (" success "); resp.setResult(result.toString()); Logger.info (" Asyncrespp #execute asynchronously complete the execution." ); } catch (Exception exception) { resp.setRespCode("98"); Resp.setRespDesc (" Task Exception "); }}});
  • MyAsyncController.java

Add sleep time at execution time.

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

test

Page to http://localhost:18080/async

The page returns as follows:

{"respCode":null,"respDesc":null,"result":null}

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 request address: / 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: Asyncrespp #execute starts asynchronously. 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 results: 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 calls 2021-07-10 09:16:13.729 INFO 11008 -- [ltaskExecutor-1] C.G.H.S.L.A.C.B aseAsyncController: AsyncResp# execute asynchronous execution.

You can see that Spring itself is still executing as normal because Process takes so long to execute that all three interception methods fail to fetch asynchronous content.

reflection

Writing here, their harvest is still a lot.

(1) The name of interceptor

The original title uses three different types of interceptors. Admittedly, it is not strictly necessary to lump them all together.

Otherwise, as stated in the comment section, a filter could be called an interceptor.

So the interceptor is modified to intercept mode uniformly.

(2) the problem of understanding knowledge

When I first implemented it, because the process time was so short, I was led to believe that Spring had a special processing mechanism.

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

summary

I hope you found this article helpful, and if you have other thoughts, please feel free to share them in the comments section.

The geeks’ thumb up collection and forwarding are the biggest motivation for Lao Ma to keep writing!

I am an old horse, looking forward to the next meeting with you.