springboot有哪些实现拦截器的方式及异步执行是什么
这篇文章主要讲解了“springboot有哪些实现拦截器的方式及异步执行是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“springboot有哪些实现拦截器的方式及异步执行是什么”吧!
目录
springboot 拦截器
springboot 入门案例
maven 引入
启动类
定义 Controller
拦截器定义
基于 Aspect
基于 HandlerInterceptor
基于 ResponseBodyAdvice
测试
异步执行
定义异步线程池
异步执行的 Controller
思考
测试
反思
springboot 拦截器
实际项目中,我们经常需要输出请求参数,响应结果,方法耗时,统一的权限校验等。
本文首先为大家介绍 HTTP 请求中三种常见的拦截实现,并且比较一下其中的差异。
(1)基于 Aspect 的拦截器
(2)基于 HandlerInterceptor 的拦截器
(3)基于 ResponseBodyAdvice 的拦截器
推荐阅读:
统一日志框架: https://github.com/houbb/auto-log
springboot 入门案例
为了便于大家学习,我们首先从最基本的 springboot 例子讲起。
maven 引入
引入必须的 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>
启动类
实现最简单的启动类。
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
定义 Controller
为了演示方便,我们首先实现一个简单的 controller。
@RestControllerpublic 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()}
拦截器定义
基于 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;@Aspect@Component@EnableAspectJAutoProxypublic class AspectLogInterceptor { private static final Logger LOG = LoggerFactory.getLogger(AspectLogInterceptor.class); @Pointcut("execution(public * com.github.houbb.springboot.learn.aspect.controller..*(..))") public void pointCut() { // } @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)方法的调用耗时统计
基于 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;@Componentpublic 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;@Configurationpublic class SpringMvcConfig extends WebMvcConfigurerAdapter { @Autowired private LogHandlerInterceptor logHandlerInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(logHandlerInterceptor) .addPathPatterns("@ControllerAdvicepublic class MyResponseBodyAdvice implements ResponseBodyAdvice<Object> { 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;@Configuration@EnableAsyncpublic 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; }}
异步执行的 Controller
@RestControllerpublic 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 的实现如下:
@RestControllerpublic 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有哪些实现拦截器的方式及异步执行是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341