您好,登錄后才能下訂單哦!
這篇文章主要講解了“springboot有哪些實現攔截器的方式及異步執行是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“springboot有哪些實現攔截器的方式及異步執行是什么”吧!
springboot 攔截器
springboot 入門案例
maven 引入
啟動類
定義 Controller
攔截器定義
基于 Aspect
基于 HandlerInterceptor
基于 ResponseBodyAdvice
測試
異步執行
定義異步線程池
異步執行的 Controller
思考
測試
反思
實際項目中,我們經常需要輸出請求參數,響應結果,方法耗時,統一的權限校驗等。
本文首先為大家介紹 HTTP 請求中三種常見的攔截實現,并且比較一下其中的差異。
(1)基于 Aspect 的攔截器
(2)基于 HandlerInterceptor 的攔截器
(3)基于 ResponseBodyAdvice 的攔截器
推薦閱讀:
統一日志框架: https://github.com/houbb/auto-log
為了便于大家學習,我們首先從最基本的 springboot 例子講起。
引入必須的 jar 包。
<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>
實現最簡單的啟動類。
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
為了演示方便,我們首先實現一個簡單的 controller。
@RestController public class IndexController { @RequestMapping("/index") public AsyncResp index() { AsyncResp asyncResp = new AsyncResp(); asyncResp.setResult("ok"); asyncResp.setRespCode("00"); asyncResp.setRespDesc("成功"); System.out.println("IndexController#index:" + asyncResp); return asyncResp; } }
其中 AsyncResp 的定義如下:
public class AsyncResp { private String respCode; private String respDesc; private String result; // getter & setter & toString() }
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 { /** * 日志實例 * @since 1.0.0 */ private static final Logger LOG = LoggerFactory.getLogger(AspectLogInterceptor.class); /** * 攔截 controller 下所有的 public方法 */ @Pointcut("execution(public * com.github.houbb.springboot.learn.aspect.controller..*(..))") public void pointCut() { // } /** * 攔截處理 * * @param point point 信息 * @return result * @throws Throwable if any */ @Around("pointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { try { //1. 設置 MDC // 獲取當前攔截的方法簽名 String signatureShortStr = point.getSignature().toShortString(); //2. 打印入參信息 Object[] args = point.getArgs(); LOG.info("{} 參數: {}", signatureShortStr, Arrays.toString(args)); //3. 打印結果 Object result = point.proceed(); LOG.info("{} 結果: {}", signatureShortStr, result); return result; } finally { // 移除 mdc } } }
這種實現的優點是比較通用,可以結合注解實現更加靈活強大的功能。
是個人非常喜歡的一種方式。
主要用途:
(1)日志的出參/入參
(2)統一設置 TraceId
(3)方法的調用耗時統計
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 * @since 1.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 { // 統一的權限校驗、路由等 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 調用"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
然后需要指定對應的 url 和攔截器之間的關系才會生效:
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 配置 * @since 1.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); } }
這種方式的優點就是可以根據 url 靈活指定不同的攔截器。
缺點是主要用于 Controller 層。
此接口有beforeBodyWrite方法,參數body是響應對象response中的響應體,那么我們就可以用此方法來對響應體做一些統一的操作。
比如加密,簽名等。
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> { /** * 日志實例 * @since 1.0.0 */ private static final Logger LOG = LoggerFactory.getLogger(MyResponseBodyAdvice.class); @Override public boolean supports(MethodParameter methodParameter, Class aClass) { //這個地方如果返回false, 不會執行 beforeBodyWrite 方法 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 請求地址:{}", uri); ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) serverHttpRequest; HttpServletRequest servletRequest = servletServerHttpRequest.getServletRequest(); // 可以做統一的攔截器處理 // 可以對結果做動態修改等 LOG.info("MyResponseBodyAdvice#beforeBodyWrite 響應結果:{}", resp); return resp; } }
我們啟動應用,頁面訪問:
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 中定義異步線程池,非常簡單。
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; /** * 請求異步處理配置 * * @author binbin.hou */ @Configuration @EnableAsync public class SpringAsyncConfig { @Bean(name = "asyncPoolTaskExecutor") public AsyncTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setMaxPoolSize(10); executor.setQueueCapacity(10); executor.setCorePoolSize(10); executor.setWaitForTasksToCompleteOnShutdown(true); return executor; } }
@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 結果:" + resp); return resp; } }
其中 BaseAsyncController 的實現如下:
@RestController public abstract class BaseAsyncController<T> { protected abstract T process(HttpServletRequest request); @Autowired private AsyncTaskExecutor taskExecutor; protected AsyncResp execute(HttpServletRequest request) { // 異步響應結果 AsyncResp resp = new AsyncResp(); try { taskExecutor.execute(new Runnable() { @Override public void run() { try { T result = process(request); resp.setRespCode("00"); resp.setRespDesc("成功"); resp.setResult(result.toString()); } catch (Exception exception) { resp.setRespCode("98"); resp.setRespDesc("任務異常"); } } }); } catch (TaskRejectedException e) { resp.setRespCode("99"); resp.setRespDesc("任務拒絕"); } return resp; } }
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有哪些實現攔截器的方式及異步執行是什么”的內容了,經過本文的學習后,相信大家對springboot有哪些實現攔截器的方式及異步執行是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。