激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - springboot實現攔截器的3種方式及異步執行的思考

springboot實現攔截器的3種方式及異步執行的思考

2021-09-30 01:14老馬嘯西風 Java教程

實際項目中,我們經常需要輸出請求參數,響應結果,方法耗時,統一的權限校驗等。本文首先為大家介紹 HTTP 請求中三種常見的攔截實現,并且比較一下其中的差異。感興趣的可以了解一下

springboot 攔截器

實際項目中,我們經常需要輸出請求參數,響應結果,方法耗時,統一的權限校驗等。

本文首先為大家介紹 HTTP 請求中三種常見的攔截實現,并且比較一下其中的差異。
(1)基于 Aspect 的攔截器
(2)基于 HandlerInterceptor 的攔截器
(3)基于 ResponseBodyAdvice 的攔截器

推薦閱讀:

統一日志框架: https://github.com/houbb/auto-log

springboot實現攔截器的3種方式及異步執行的思考

springboot 入門案例

為了便于大家學習,我們首先從最基本的 springboot 例子講起。

maven 引入

引入必須的 jar 包。

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>1.5.9.RELEASE</version>
  5. </parent>
  6.  
  7. <dependencies>
  8. <dependency>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-starter-web</artifactId>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.aspectj</groupId>
  14. <artifactId>aspectjrt</artifactId>
  15. <version>1.8.10</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.aspectj</groupId>
  19. <artifactId>aspectjweaver</artifactId>
  20. <version>1.8.10</version>
  21. </dependency>
  22. </dependencies>
  23. <!-- Package as an executable jar -->
  24. <build>
  25. <plugins>
  26. <plugin>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-maven-plugin</artifactId>
  29. </plugin>
  30. </plugins>
  31. </build>

啟動類

實現最簡單的啟動類。

  1. @SpringBootApplication
  2. public class Application {
  3.  
  4. public static void main(String[] args) {
  5. SpringApplication.run(Application.class, args);
  6. }
  7.  
  8. }

定義 Controller

為了演示方便,我們首先實現一個簡單的 controller。

  1. @RestController
  2. public class IndexController {
  3.  
  4. @RequestMapping("/index")
  5. public AsyncResp index() {
  6. AsyncResp asyncResp = new AsyncResp();
  7. asyncResp.setResult("ok");
  8. asyncResp.setRespCode("00");
  9. asyncResp.setRespDesc("成功");
  10.  
  11. System.out.println("IndexController#index:" + asyncResp);
  12. return asyncResp;
  13. }
  14.  
  15. }

其中 AsyncResp 的定義如下:

  1. public class AsyncResp {
  2.  
  3. private String respCode;
  4.  
  5. private String respDesc;
  6.  
  7. private String result;
  8.  
  9. // getter & setter & toString()
  10. }

攔截器定義

 

基于 Aspect

  1. import org.aspectj.lang.ProceedingJoinPoint;
  2. import org.aspectj.lang.annotation.Around;
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.aspectj.lang.annotation.Pointcut;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import org.springframework.context.annotation.EnableAspectJAutoProxy;
  8. import org.springframework.stereotype.Component;
  9.  
  10. import java.util.Arrays;
  11.  
  12. /**
  13. *
  14. * @author binbin.hou
  15. * @since 1.0.0
  16. */
  17. @Aspect
  18. @Component
  19. @EnableAspectJAutoProxy
  20. public class AspectLogInterceptor {
  21.  
  22. /**
  23. * 日志實例
  24. * @since 1.0.0
  25. */
  26. private static final Logger LOG = LoggerFactory.getLogger(AspectLogInterceptor.class);
  27.  
  28. /**
  29. * 攔截 controller 下所有的 public方法
  30. */
  31. @Pointcut("execution(public * com.github.houbb.springboot.learn.aspect.controller..*(..))")
  32. public void pointCut() {
  33. //
  34. }
  35.  
  36. /**
  37. * 攔截處理
  38. *
  39. * @param point point 信息
  40. * @return result
  41. * @throws Throwable if any
  42. */
  43. @Around("pointCut()")
  44. public Object around(ProceedingJoinPoint point) throws Throwable {
  45. try {
  46. //1. 設置 MDC
  47.  
  48. // 獲取當前攔截的方法簽名
  49. String signatureShortStr = point.getSignature().toShortString();
  50. //2. 打印入參信息
  51. Object[] args = point.getArgs();
  52. LOG.info("{} 參數: {}", signatureShortStr, Arrays.toString(args));
  53.  
  54. //3. 打印結果
  55. Object result = point.proceed();
  56. LOG.info("{} 結果: {}", signatureShortStr, result);
  57. return result;
  58. } finally {
  59. // 移除 mdc
  60. }
  61. }
  62. }

這種實現的優點是比較通用,可以結合注解實現更加靈活強大的功能。

是個人非常喜歡的一種方式。
主要用途:
(1)日志的出參/入參
(2)統一設置 TraceId
(3)方法的調用耗時統計

基于 HandlerInterceptor

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.stereotype.Component;
  4. import org.springframework.web.servlet.HandlerInterceptor;
  5. import org.springframework.web.servlet.ModelAndView;
  6.  
  7. import javax.servlet.DispatcherType;
  8. import javax.servlet.http.HttpServletRequest;
  9. import javax.servlet.http.HttpServletResponse;
  10.  
  11. /**
  12. * @author binbin.hou
  13. * @since 1.0.0
  14. */
  15. @Component
  16. public class LogHandlerInterceptor implements HandlerInterceptor {
  17.  
  18. private Logger logger = LoggerFactory.getLogger(LogHandlerInterceptor.class);
  19.  
  20. @Override
  21. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  22. // 統一的權限校驗、路由等
  23. logger.info("LogHandlerInterceptor#preHandle 請求地址:{}", request.getRequestURI());
  24.  
  25. if (request.getDispatcherType().equals(DispatcherType.ASYNC)) {
  26. return true;
  27. }
  28.  
  29. return true;
  30. }
  31.  
  32. @Override
  33. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  34. logger.info("LogHandlerInterceptor#postHandle 調用");
  35. }
  36.  
  37. @Override
  38. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  39.  
  40. }
  41. }

然后需要指定對應的 url 和攔截器之間的關系才會生效:

  1. import com.github.houbb.springboot.learn.aspect.aspect.LogHandlerInterceptor;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  6.  
  7. /**
  8. * spring mvc 配置
  9. * @since 1.0.0
  10. */
  11. @Configuration
  12. public class SpringMvcConfig extends WebMvcConfigurerAdapter {
  13.  
  14. @Autowired
  15. private LogHandlerInterceptor logHandlerInterceptor;
  16.  
  17. @Override
  18. public void addInterceptors(InterceptorRegistry registry) {
  19. registry.addInterceptor(logHandlerInterceptor)
  20. .addPathPatterns("/**")
  21. .excludePathPatterns("/version");
  22. super.addInterceptors(registry);
  23. }
  24. }

這種方式的優點就是可以根據 url 靈活指定不同的攔截器。
缺點是主要用于 Controller 層。

基于 ResponseBodyAdvice

此接口有beforeBodyWrite方法,參數body是響應對象response中的響應體,那么我們就可以用此方法來對響應體做一些統一的操作。

比如加密,簽名等。

  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.core.MethodParameter;
  4. import org.springframework.http.MediaType;
  5. import org.springframework.http.converter.HttpMessageConverter;
  6. import org.springframework.http.server.ServerHttpRequest;
  7. import org.springframework.http.server.ServerHttpResponse;
  8. import org.springframework.http.server.ServletServerHttpRequest;
  9. import org.springframework.web.bind.annotation.ControllerAdvice;
  10. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
  11.  
  12. import javax.servlet.http.HttpServletRequest;
  13.  
  14. /**
  15. * @author binbin.hou
  16. * @since 1.0.0
  17. */
  18. @ControllerAdvice
  19. public class MyResponseBodyAdvice implements ResponseBodyAdvice<Object> {
  20.  
  21. /**
  22. * 日志實例
  23. * @since 1.0.0
  24. */
  25. private static final Logger LOG = LoggerFactory.getLogger(MyResponseBodyAdvice.class);
  26.  
  27. @Override
  28. public boolean supports(MethodParameter methodParameter, Class aClass) {
  29. //這個地方如果返回false, 不會執行 beforeBodyWrite 方法
  30. return true;
  31. }
  32.  
  33. @Override
  34. public Object beforeBodyWrite(Object resp, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
  35. String uri = serverHttpRequest.getURI().getPath();
  36. LOG.info("MyResponseBodyAdvice#beforeBodyWrite 請求地址:{}", uri);
  37.  
  38. ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) serverHttpRequest;
  39. HttpServletRequest servletRequest = servletServerHttpRequest.getServletRequest();
  40.  
  41. // 可以做統一的攔截器處理
  42.  
  43. // 可以對結果做動態修改等
  44. LOG.info("MyResponseBodyAdvice#beforeBodyWrite 響應結果:{}", resp);
  45. return resp;
  46. }
  47. }

 

測試

我們啟動應用,頁面訪問:
http://localhost:18080/index
頁面響應:
{"respCode":"00","respDesc":"成功","result":"ok"}

后端日志:

c.g.h.s.l.a.a.LogHandlerInterceptor      : LogHandlerInterceptor#preHandle 請求地址:/index
c.g.h.s.l.a.aspect.AspectLogInterceptor  : IndexController.index() 參數: []
IndexController#index:AsyncResp{respCode='00', respDesc='成功', result='ok'}
c.g.h.s.l.a.aspect.AspectLogInterceptor  : IndexController.index() 結果: AsyncResp{respCode='00', respDesc='成功', result='ok'}
c.g.h.s.l.a.aspect.MyResponseBodyAdvice  : MyResponseBodyAdvice#beforeBodyWrite 請求地址:/index
c.g.h.s.l.a.aspect.MyResponseBodyAdvice  : MyResponseBodyAdvice#beforeBodyWrite 響應結果:AsyncResp{respCode='00', respDesc='成功', result='ok'}
c.g.h.s.l.a.a.LogHandlerInterceptor      : LogHandlerInterceptor#postHandle 調用

這里執行的先后順序也比較明確,此處不再贅述。

異步執行

當然,如果只是上面這些內容,并不是本篇文章的重點。
接下來,我們一起來看下,如果引入了異步執行會怎么樣。

定義異步線程池

springboot 中定義異步線程池,非常簡單。

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.core.task.AsyncTaskExecutor;
  4. import org.springframework.scheduling.annotation.EnableAsync;
  5. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  6.  
  7. /**
  8. * 請求異步處理配置
  9. *
  10. * @author binbin.hou
  11. */
  12. @Configuration
  13. @EnableAsync
  14. public class SpringAsyncConfig {
  15.  
  16. @Bean(name = "asyncPoolTaskExecutor")
  17. public AsyncTaskExecutor taskExecutor() {
  18. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  19. executor.setMaxPoolSize(10);
  20. executor.setQueueCapacity(10);
  21. executor.setCorePoolSize(10);
  22. executor.setWaitForTasksToCompleteOnShutdown(true);
  23. return executor;
  24. }
  25.  
  26. }

異步執行的 Controller

  1. @RestController
  2. public class MyAsyncController extends BaseAsyncController<String> {
  3.  
  4. @Override
  5. protected String process(HttpServletRequest request) {
  6. return "ok";
  7. }
  8.  
  9. @RequestMapping("/async")
  10. public AsyncResp hello(HttpServletRequest request) {
  11. AsyncResp resp = super.execute(request);
  12.  
  13. System.out.println("Controller#async 結果:" + resp);
  14. return resp;
  15. }
  16. }

其中 BaseAsyncController 的實現如下:

  1. @RestController
  2. public abstract class BaseAsyncController<T> {
  3.  
  4. protected abstract T process(HttpServletRequest request);
  5.  
  6. @Autowired
  7. private AsyncTaskExecutor taskExecutor;
  8.  
  9. protected AsyncResp execute(HttpServletRequest request) {
  10. // 異步響應結果
  11. AsyncResp resp = new AsyncResp();
  12. try {
  13. taskExecutor.execute(new Runnable() {
  14. @Override
  15. public void run() {
  16. try {
  17. T result = process(request);
  18.  
  19. resp.setRespCode("00");
  20. resp.setRespDesc("成功");
  21. resp.setResult(result.toString());
  22.  
  23. } catch (Exception exception) {
  24. resp.setRespCode("98");
  25. resp.setRespDesc("任務異常");
  26. }
  27. }
  28. });
  29. } catch (TaskRejectedException e) {
  30. resp.setRespCode("99");
  31. resp.setRespDesc("任務拒絕");
  32. }
  33.  
  34. return resp;
  35. }
  36. }

execute 的實現也比較簡單:
(1)主線程創建一個 AsyncResp,用于返回。
(2)線程池異步執行具體的子類方法,并且設置對應的值。

思考

接下來,問大家一個問題。
如果我們請求 http://localhost:18080/async,那么:
(1)頁面得到的返回值是什么?
(2)Aspect 日志輸出的返回值是?
(3)ResponseBodyAdvice 日志輸出的返回值是什么?
你可以在這里稍微停一下,記錄下你的答案。

測試

我們頁面請求 http://localhost:18080/async。

頁面響應如下:

{"respCode":"00","respDesc":"成功","result":"ok"}

后端的日志:

c.g.h.s.l.a.a.LogHandlerInterceptor      : LogHandlerInterceptor#preHandle 請求地址:/async
c.g.h.s.l.a.aspect.AspectLogInterceptor  : MyAsyncController.hello(..) 參數: [org.apache.catalina.connector.RequestFacade@7e931750]
Controller#async 結果:AsyncResp{respCode='null', respDesc='null', result='null'}
c.g.h.s.l.a.aspect.AspectLogInterceptor  : MyAsyncController.hello(..) 結果: AsyncResp{respCode='null', respDesc='null', result='null'}
c.g.h.s.l.a.aspect.MyResponseBodyAdvice  : MyResponseBodyAdvice#beforeBodyWrite 請求地址:/async
c.g.h.s.l.a.aspect.MyResponseBodyAdvice  : MyResponseBodyAdvice#beforeBodyWrite 響應結果:AsyncResp{respCode='00', respDesc='成功', result='ok'}
c.g.h.s.l.a.a.LogHandlerInterceptor      : LogHandlerInterceptor#postHandle 調用

對比一下,可以發現我們上面問題的答案:
(1)頁面得到的返回值是什么?

{"respCode":"00","respDesc":"成功","result":"ok"}

可以獲取到異步執行完成的結果。
(2)Aspect 日志輸出的返回值是?

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

無法獲取異步結果。
(3)ResponseBodyAdvice 日志輸出的返回值是什么?

AsyncResp{respCode='00', respDesc='成功', result='ok'}

可以獲取到異步執行完成的結果。

反思

可以發現,spring 對于頁面的響應也許和我們想的有些不一樣,并不是直接獲取同步結果。
寫到這里,發現自己對于 mvc 的理解一直只是停留在表面,沒有真正理解整個流程。
Aspect 的形式在很多框架中都會使用,不過這里會發現無法獲取異步的執行結果,存在一定問題。

到此這篇關于springboot實現攔截器的3種方式及異步執行的思考的文章就介紹到這了,更多相關springboot 攔截器內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://juejin.cn/post/6982562317040877582

延伸 · 閱讀

精彩推薦
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7472021-02-04
主站蜘蛛池模板: 操操插插 | 日韩色电影| 欧美成人一级片 | 中国女警察一级毛片视频 | 久久美女免费视频 | 91aa.app | 一级黄色播放 | 电影91 | 黄视频在线网站 | 中文字幕 在线观看 | 久久久久久久一区二区三区 | 国产午夜精品一区二区三区视频 | 国产成人在线视频播放 | 免费毛片儿 | 久色porn| 久久久综合视频 | 色综合狠狠 | 日本成人一区二区三区 | 欧美激情在线播放 | 久久精品国产亚洲7777 | 成人小视频在线播放 | 麻豆一区二区99久久久久 | 一区二区三区日本在线观看 | 亚洲码无人客一区二区三区 | 国产伦久视频免费观看视频 | 日韩在线播放中文字幕 | h视频在线免费看 | 一级裸体视频 | 日韩男女在线 | 欧美日韩免费看 | 一区二区三区国产视频 | 免费毛片电影 | 青草av.久久免费一区 | 欧美综合日韩 | 韩国草草影院 | arabxxxxvideos| 日本一区二区三区四区高清视频 | 国产伦久视频免费观看视频 | 九九午夜 | 大胆在线日本aⅴ免费视频 永久免费毛片 | av影院在线播放 |