springboot使用aop打印异步请求的日志报空指针

这是我的aop日志配置

package com.wtblog.service.config;

import com.wtblog.utils.ResultEntity;
import lombok.extern.slf4j.Slf4j;
import nl.bitwalker.useragentutils.UserAgent;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

@Aspect
@Component
@Slf4j
public class WebLogAspect {

    /**
     * 进入方法时间戳
     */
    private Long startTime;
    /**
     * 方法结束时间戳(计时)
     */
    private Long endTime;

    public WebLogAspect() {
    }

    /**
     * 定义请求日志切入点,其切入点表达式有多种匹配方式,这里是指定路径
     */
    @Pointcut("execution(* com.wtblog.service..*.*(..))")
    public void webLogPointcut() {
    }


    @Around("webLogPointcut()&&args(..,bindingResult)")
    public Object  doAround(ProceedingJoinPoint joinPoint, BindingResult bindingResult) throws Throwable {
        Object result = null;

        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //获取请求头中的User-Agent
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        //打印请求的内容
        startTime = System.currentTimeMillis();
        log.info("请求开始时间:{}", LocalDateTime.now());
        log.info("请求Url : {}", request.getRequestURL().toString());
        log.info("请求方式 : {}", request.getMethod());
        log.info("请求ip : {}", request.getRemoteAddr());
        log.info("请求参数 : {}", Arrays.toString(joinPoint.getArgs()));
        // 系统信息
        log.info("浏览器:{}", userAgent.getBrowser().toString());
        log.info("浏览器版本:{}", userAgent.getBrowserVersion());
        log.info("操作系统: {}", userAgent.getOperatingSystem().toString());

        List<ObjectError> ls = bindingResult.getAllErrors();
        if (ls != null && ls.size() > 0) {
            // 保存异常日志记录
            log.error("发生异常时间:{}", LocalDateTime.now());
            log.error("抛出异常:{}", ls.get(0).getDefaultMessage());
            return ResultEntity.failure().message(ls.get(0).getDefaultMessage());
        }
        result = joinPoint.proceed();
        return result;
    }


    /**
     * 前置通知:
     * 1. 在执行目标方法之前执行,比如请求接口之前的登录验证;
     * 2. 在前置通知中设置请求日志信息,如开始时间,请求参数,注解内容等
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before("webLogPointcut()")
    public void doBefore(JoinPoint joinPoint) {

        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //获取请求头中的User-Agent
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        //打印请求的内容
        startTime = System.currentTimeMillis();
        log.info("请求开始时间:{}", LocalDateTime.now());
        log.info("请求Url : {}", request.getRequestURL().toString());
        log.info("请求方式 : {}", request.getMethod());
        log.info("请求ip : {}", request.getRemoteAddr());
        log.info("请求参数 : {}", Arrays.toString(joinPoint.getArgs()));
        // 系统信息
        log.info("浏览器:{}", userAgent.getBrowser().toString());
        log.info("浏览器版本:{}", userAgent.getBrowserVersion());
        log.info("操作系统: {}", userAgent.getOperatingSystem().toString());
    }

    /**
     * 返回通知:
     * 1. 在目标方法【正常结束之后】执行
     * 1. 在返回通知中补充请求日志信息,如返回时间,方法耗时,返回值,并且保存日志信息
     *
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(returning = "ret", pointcut = "webLogPointcut()")
    public void doAfterReturning(Object ret) throws Throwable {
        endTime = System.currentTimeMillis();
        log.info("请求结束时间:{}", LocalDateTime.now());
        log.info("请求耗时:{}ms", (endTime - startTime));
        // 处理完请求,返回内容
        log.info("请求返回 : {}", ret);
    }

    /**
     * 异常通知:
     * 1. 在目标方法【非正常结束后】,发生异常或者抛出异常时执行
     * 1. 在异常通知中设置异常信息,并将其保存
     *
     * @param throwable
     */
    @AfterThrowing(value = "webLogPointcut()", throwing = "throwable")
    public void doAfterThrowing(Throwable throwable) {
        // 保存异常日志记录
        log.error("发生异常时间:{}", LocalDateTime.now());
        log.error("抛出异常:{}", throwable.getMessage());
    }
}

这是异步请求调用

@CrossOrigin
@RestController
@RequestMapping("/servicebloguser/blog-user")
@EnableAsync
public class BlogUserController {

    @Autowired
    private BlogUserService blogUserService;


    @ApiOperation(value = "用户使用邮箱注册,进行验证,获取验证码")
    @GetMapping("user/get/verification/code/by/email")
    public ResultEntity userGetVerificationCodeByEmail(@RequestParam(value = "email") String email) {
        try {
            blogUserService.userGetVerificationCodeByEmail(email);
            return ResultEntity.success();
        } catch (WTException e) {
            e.printStackTrace();
            return ResultEntity.failure().code(e.getCode()).message(e.getMsg());
        } catch (Exception e) {
            e.printStackTrace();
            return ResultEntity.failure();
        }
    }

}
package com.wtblog.service.servicebloguser.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wtblog.exception.WTException;
import com.wtblog.service.servicebloguser.entity.BlogRegistInfoVO;
import com.wtblog.service.servicebloguser.entity.BlogUser;
import com.wtblog.service.servicebloguser.mapper.BlogUserMapper;
import com.wtblog.service.servicebloguser.service.BlogUserService;
import com.wtblog.service.utils.MD5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.UUID;
import java.util.regex.Pattern;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author WT-X
 */
@Service
public class BlogUserServiceImpl extends ServiceImpl<BlogUserMapper, BlogUser> implements BlogUserService {

    @Autowired
    private JavaMailSenderImpl javaMailSender;

    

    // 用户使用邮箱注册,进行验证,获取验证码
    @Async
    @Override
    public void userGetVerificationCodeByEmail(String email) {
        // 校验email数据格式
        String regEx1 = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";
        if (email == null || email == "" || !Pattern.compile(regEx1).matcher(email).matches()) {
            throw new WTException(20001, "邮件有误,请确认后填写~");
        }
        // 生成验证码
        String emailServiceCode = UUID.randomUUID().toString().replace("-", "").substring(0, 4);
        // 设置发件者信息
        SimpleMailMessage message = new SimpleMailMessage();
        // 主题
        message.setSubject("注册验证码");
        // 邮件内容
        message.setText("注册验证码是:" + emailServiceCode);
        // 发件者邮箱
        message.setFrom("12100000@qq.com");
        // 收件者邮箱
        message.setTo(email);
        try {
            javaMailSender.send(message);
        }catch (Exception e){
            throw new WTException(20001, "邮件发送失败发送~");
        }

    }




}

已经测试过了,在屏蔽掉日志打印那块,程序正常进行。。。而且日志本身打印其他没出过问题。。。来个大佬指导一下!

异步线程调用RequestContextHolder.getRequestAttributes()的问题引起的

解决方法:

1.去掉异步调用 

2. 启动类添加以下代码

    @Bean
    public RequestContextListener requestContextListener(){
        return new RequestContextListener();
    }

 

ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

HttpServletRequest request = attributes.getRequest();

调试一下,看看attributes是不是空值

最好写个if语句判断一下。

对了,还有报错信息

 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | DirectJDKLog.java:173 | org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/] | Initializing Spring DispatcherServlet 'dispatcherServlet'
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | FrameworkServlet.java:525 | org.springframework.web.servlet.DispatcherServlet | Initializing Servlet 'dispatcherServlet'
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | FrameworkServlet.java:547 | org.springframework.web.servlet.DispatcherServlet | Completed initialization in 7 ms
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | WebLogAspect.java:98 | com.wtblog.service.config.WebLogAspect | 请求开始时间:2021-05-26T23:32:19.632
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | WebLogAspect.java:99 | com.wtblog.service.config.WebLogAspect | 请求Url : http://localhost:8001/servicebloguser/blog-user/user/get/verification/code/by/email
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | WebLogAspect.java:100 | com.wtblog.service.config.WebLogAspect | 请求方式 : GET
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | WebLogAspect.java:101 | com.wtblog.service.config.WebLogAspect | 请求ip : 0:0:0:0:0:0:0:1
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | WebLogAspect.java:102 | com.wtblog.service.config.WebLogAspect | 请求参数 : [1961002626@qq.com]
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | WebLogAspect.java:104 | com.wtblog.service.config.WebLogAspect | 浏览器:CHROME9
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | WebLogAspect.java:105 | com.wtblog.service.config.WebLogAspect | 浏览器版本:90.0.4430.93
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | WebLogAspect.java:106 | com.wtblog.service.config.WebLogAspect | 操作系统: WINDOWS
 | 2021-05-26 23:32:19 | ERROR | task-1 | WebLogAspect.java:136 | com.wtblog.service.config.WebLogAspect | 发生异常时间:2021-05-26T23:32:19.643
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | WebLogAspect.java:120 | com.wtblog.service.config.WebLogAspect | 请求结束时间:2021-05-26T23:32:19.643
 | 2021-05-26 23:32:19 | ERROR | task-1 | WebLogAspect.java:137 | com.wtblog.service.config.WebLogAspect | 抛出异常:null
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | WebLogAspect.java:121 | com.wtblog.service.config.WebLogAspect | 请求耗时:13ms
 | 2021-05-26 23:32:19 | INFO  | http-nio-8001-exec-1 | WebLogAspect.java:123 | com.wtblog.service.config.WebLogAspect | 请求返回 : ResultEntity(success=true, code=20000, message=成功, data={})
 | 2021-05-26 23:32:19 | ERROR | task-1 | SimpleAsyncUncaughtExceptionHandler.java:39 | org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler | Unexpected exception occurred invoking async method: public void com.wtblog.service.servicebloguser.service.impl.BlogUserServiceImpl.userGetVerificationCodeByEmail(java.lang.String)
java.lang.NullPointerException: null
	at com.wtblog.service.config.WebLogAspect.doBefore(WebLogAspect.java:93)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:626)
	at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:44)
	at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:55)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
	at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:55)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
	at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
	at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

 

| 2021-05-27 08:06:08 | ERROR | main | TomcatStarter.java:61 | org.springframework.boot.web.embedded.tomcat.TomcatStarter | Error starting Tomcat context. Exception: org.springframework.beans.factory.BeanCreationException. Message: Error creating bean with name 'requestContextListener' defined in com.wtblog.service.ServiceApplication: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.context.request.RequestContextListener]: Factory method 'requestContextListener' threw exception; nested exception is java.lang.NullPointerException
...
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestContextListener' defined in com.wtblog.service.ServiceApplication: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.context.request.RequestContextListener]: Factory method 'requestContextListener' threw exception; nested exception is java.lang.NullPointerException
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.context.request.RequestContextListener]: Factory method 'requestContextListener' threw exception; nested exception is java.lang.NullPointerException
Caused by: java.lang.NullPointerException: null

 

您好,我是有问必答小助手,您的问题已经有小伙伴解答了,您看下是否解决,可以追评进行沟通哦~

如果有您比较满意的答案 / 帮您提供解决思路的答案,可以点击【采纳】按钮,给回答的小伙伴一些鼓励哦~~

ps:问答VIP仅需29元,即可享受5次/月 有问必答服务,了解详情>>>https://vip.csdn.net/askvip?utm_source=1146287632

解决思路:遇到的问题有两个,①:日志打印异步操作报空指针,②:异步操作失效

问题①:是因为
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(),在获取上下文对象时失败了,那就干脆不让他获取!我直接在controller的方法中加一个HttpServletRequest request参数,在controller中时百分百可以获取到!然后在切入点上做文章,重新搞一个切入点,让他对我这个要进行异步操作的方法1对1VIP服务;接着,利用JoinPoint来获取上下文对象(也就是取controller方法中的HttpServletRequest request参数)这样一来就确保百分百成功,不会报空指针了!

问题②:按照百度的那些说法来就好了,比如必须要有@Async+@EnableAsync,我在实际操作中直接将@Async标注在@service中的方法上了,并没有单独抽出,也成功了!唯一要注意的就是不要在内部调用就ok了~

贴下代码:

1.这是日志那块

    @Before(value = "execution(* com.wtblog.service.servicebloguser.controller.BlogUserController.userGetVerificationCodeByEmail(..)) ")
    public void doBefore2(JoinPoint joinPoint) {
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        // 接收到请求,记录请求内容
        HttpServletRequest request = (HttpServletRequest) args.get(1);
        //获取请求头中的User-Agent
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        //打印请求的内容
        startTime = System.currentTimeMillis();
        log.info("请求开始时间:{}", LocalDateTime.now());
        log.info("请求协议 : {}", request.getScheme());
        log.info("请求Url : {}", request.getRequestURL());
        log.info("请求方式 : {}", request.getMethod());
        log.info("请求ip : {}", request.getRemoteAddr());
        log.info("请求参数 : {}", Arrays.toString(joinPoint.getArgs()));
        // 系统信息
        log.info("浏览器:{}", userAgent.getBrowser());
        log.info("浏览器版本:{}", userAgent.getBrowserVersion());
        log.info("操作系统: {}", userAgent.getOperatingSystem());
    }

2.controller那块

    @ApiOperation(value = "用户使用邮箱注册,进行验证,获取验证码")
    @GetMapping("user/get/verification/code/by/email")
    public ResultEntity userGetVerificationCodeByEmail(@RequestParam(value = "email") String email, HttpServletRequest request) {
        try {
            long startTime = System.currentTimeMillis();
            blogUserService.userGetVerificationCodeByEmail(email,startTime);
            return ResultEntity.success();
        } catch (WTException e) {
            e.printStackTrace();
            return ResultEntity.failure().code(e.getCode()).message(e.getMsg());
        } catch (Exception e) {
            e.printStackTrace();
            return ResultEntity.failure();
        }
    }

3.service那块,就不全贴了,领会意思即可

    // 用户使用邮箱注册,进行验证,获取验证码
    @Async
    @Override
    public void userGetVerificationCodeByEmail(String email,long startTime) {
        ...
        try {
            javaMailSender.send(message);
            long endTime = System.currentTimeMillis();
            log.info("请求结束时间:{}", LocalDateTime.now());
            log.info("请求耗时:{}ms", (endTime - startTime));
        }catch (Exception e){
            throw new WTException(20001, "邮件发送失败发送~");
        }

    }

4.主启动类上

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

}