aspect 方法入参 获取_SpringBoot异步方法

本文介绍了如何使用SpringBoot的@Async注解实现异步方法,以解决将AOP日志保存到数据库时增加接口响应时间的问题。通过创建LogAspect和LoginController,实现了请求日志的记录和入库。在遇到有返回值的方法时,利用Future进行处理。同时,文章强调了启用@EnableAsync、配置线程池和注意的要点,如避免在本类内调用@Async方法。最后,作者分享了使用SpringBoot异步方法的便捷性和个人感悟。
摘要由CSDN通过智能技术生成
a934e4532701f3030775dde2f61562af.png

前言

  最近呢xxx接到了一个任务,是需要把AOP打印出的请求日志,给保存到数据库。xxx一看这个简单啊,不就是保存到数据库嘛。一顿操作猛如虎,过了20分钟就把这个任务完成了。xxx作为一个优秀的程序员,发现这样同步保存会增加了接口的响应时间。这肯定难不倒xxx,当即决定使用多线程来处理这个问题。终于在临近饭点完成了。准备边吃边欣赏自己的杰作时,外卖小哥临时走来了一句,搞这样麻烦干啥,你加个@Async不就可以了。

实现一个精简版的请求日志输出。

LogAspect

@Slf4j@Aspect@Componentpublic class LogAspect {    @Pointcut("execution(* com.hxh.log.controller.*.*(..)))")    public void saveLog(){}    @Before("saveLog()")    public void saveLog(JoinPoint joinPoint) {        // 获取HttpServletRequest        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();        assert attributes != null;        HttpServletRequest request = attributes.getRequest();        MethodSignature signature = (MethodSignature) joinPoint.getSignature();        //获取请求参数        String[] argNames = signature.getParameterNames();        Object[] args = joinPoint.getArgs();        log.info("请求路径:{},请求方式:{},请求参数:{},IP:{}",request.getRequestURI(),                request.getMethod(),                getRequestParam(argNames,args),                request.getRemoteAddr());    }    /**     * 组装请求参数     * @param argNames 参数名称     * @param args 参数值     * @return 返回JSON串     */    private String getRequestParam(String[] argNames, Object[] args){        HashMap params = new HashMap<>(argNames.length);        if(argNames.length > 0 && args.length > 0){            for (int i = 0; i 

LoginController

@RestControllerpublic class LoginController {    @PostMapping("/login")    public String login(@RequestBody LoginForm loginForm){        return loginForm.getUsername() + ":登录成功";    }}

测试一下

将项目启动然后测试一下。

72b976faa7e7e6a272c01731d756f367.png

控制台已经打印出了请求日志。

ef744b626feba719591b20298fc67da2.png

模拟入库

将日志保存到数据库。

LogServiceImpl

@Slf4j@Servicepublic class LogServiceImpl implements LogService {    @Override    public void saveLog(RequestLog requestLog) throws InterruptedException {        // 模拟入库需要的时间        Thread.sleep(2000);        log.info("请求日志保存成功:{}",requestLog);    }}

改造一下LogAspect添加日志入库

@Before("saveLog()")public void saveLog(JoinPoint joinPoint) throws InterruptedException {    // 获取HttpServletRequest    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();    assert attributes != null;    HttpServletRequest request = attributes.getRequest();    MethodSignature signature = (MethodSignature) joinPoint.getSignature();    //获取请求参数    String[] argNames = signature.getParameterNames();    Object[] args = joinPoint.getArgs();    log.info("请求路径:{},请求方式:{},请求参数:{},IP:{}",request.getRequestURI(),            request.getMethod(),            getRequestParam(argNames,args),            request.getRemoteAddr());    // 日志入库    RequestLog requestLog = new RequestLog();    requestLog.setRequestUrl(request.getRequestURI());    requestLog.setRequestType(request.getMethod());    requestLog.setRequestParam(request.getRequestURI());    requestLog.setIp(request.getRemoteAddr());    logService.saveLog(requestLog);}

测试一下

8da81def1b04076aa18398b23a5e0c10.png

控制台已经打印出了请求日志。

09438143a44f77ea918c14565690fa90.png

使用`@Async`

  由于保存日志消耗了2s,导致接口的响应时间也增加了2s。这样的结果显然不是我想要的。所以我们就按外卖小哥的方法,在LogServiceImpl.saveLog()上加一个@Async试试。

@Slf4j@Servicepublic class LogServiceImpl implements LogService {    @Async    @Override    public void saveLog(RequestLog requestLog) throws InterruptedException {        // 模拟入库需要的时间        Thread.sleep(2000);        log.info("请求日志保存成功:{}",requestLog);    }}

重新启动项目测试一下。

45045c28757cca41bffe36108a02f15a.png

  发现耗时还是2s多,这外卖小哥在瞎扯吧,于是转身进入了baidu的知识海洋遨游,发现要在启动类加个@EnableAsync。

@EnableAsync@SpringBootApplicationpublic class LogApplication {    public static void main(String[] args)  {        SpringApplication.run(LogApplication.class, args);    }}

启动一下项目再来测试一下。

f790812b2473940ba93c2227d0c09b3b.png

这下可好启动都失败了。

2c10bdff65ba46d106c80460ed764fa4.png

  不要慌,先看一眼错误信息。因为有些service使用了CGLib这种动态代理而不是JDK原生的代理,导致问题的出现。所以我们需要给@EnableAsync加上proxyTargetClass=true。

@Slf4j@EnableAsync(proxyTargetClass=true)@SpringBootApplicationpublic class LogApplication {    public static void main(String[] args)  {        SpringApplication.run(LogApplication.class, args);    }}

重新启动下再测试一下。

c02212285830b61b55c310b882c40c07.png

这下就成功了嘛,接口响应耗时变成了324ms,已经不像之前消耗2s那样了。

8143789d34a07d671df1b7c6f93ff224.png

有返回值的方法

  由于saveLog()是没有返回值,假如碰到有返回值的情况该咋办呢?使用Future即可。

@Slf4j@Servicepublic class LogServiceImpl implements LogService {    @Async    @Override    public Future saveLog(RequestLog requestLog) throws InterruptedException {        // 模拟入库需要的时间        Thread.sleep(2000);        log.info("请求日志保存成功:{}",requestLog);        return new AsyncResult<>(true);    }}

配置线程池

  既然是异步方法,肯定是用其他的线程执行的,当然可以配置相应的线程池了。

@Configurationpublic class ThreadConfig {    /**     * 日志异步保存输出线程池     * @return 返回线程池     */    @Bean("logExecutor")    public Executor taskExecutor() {        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();        executor.setCorePoolSize(5);        executor.setMaxPoolSize(10);        executor.setQueueCapacity(200);        executor.setKeepAliveSeconds(60);        executor.setThreadNamePrefix("logExecutor-");        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());        executor.setWaitForTasksToCompleteOnShutdown(true);        executor.setAwaitTerminationSeconds(60);        return executor;    }}

在使用@Async的时候指定对应的线程池就好了。

@Slf4j@Servicepublic class LogServiceImpl implements LogService {    @Override    @Async("logExecutor")    public Future saveLog(RequestLog requestLog) throws InterruptedException {        // 模拟入库需要的时间        Thread.sleep(2000);        log.info("请求日志保存成功:{}",requestLog);        return new AsyncResult<>(true);    }}

注意的点

  • 使用之前需要在启动类开启@EnableAsync。
  • 只能在自身之外调用,在本类调用是无效的。
  • 所有的类都需要交由Spring容器进行管理。

总结

  @Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。

  虽然自己维护线程池也是可以实现相应的功能,但是我还是推荐使用SpringBoot自带的异步方法,简单方便,只需要@Async和@EnableAsync就可以了。

结尾

  为什么外卖小哥能看懂我写的代码?难道我以后也要去xxx?

  如果觉得对你有帮助,可以多多评论,多多点赞哦,也可以到我的主页看看,说不定有你喜欢的文章,也可以随手点个关注哦,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值