spring boot 自定义注解 记录service层日志 AOP

现在项目中要实现一个小功能,就是在当前系统中调用其他服务的接口,如果只是用日志记录的话查找问题就会很麻烦,所以现在要实现的就是用AOP来处理调用某一个方法时记录调用的详细信息,并保存到数据库中。

1.引入AOP依赖

<!--spring切面aop依赖-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在application.properties文件里加这样一条配置
spring.aop.auto=true //这个配置我的例子中没有加 也正常运行

application.yml文件这样配置
spring:
  aop:
    auto: true

2.创建记录的日志表与数据库对应


import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

public class TInvokCoreLog implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 记录日志ID
     */
    private Long invokCoreLogId;

    /**
     * 调用参数,以;分隔
     */
    private String invokParam;

    /**
     * 调用方法
     */
    private String invokMethod;

    /**
     * 调用返回值或异常信息
     */
    private String invokReturn;

    /**
     * 调用路径
     */
    private String invokProturl;

    /**
     * 开始时间
     */
    private Date startTime;

    /**
     * 结束时间
     */
    private Date endTime;

    /**
     * 调用耗时 单位秒
     */
    private BigDecimal invokCost;

    /**
     * 调用状态 Y:成功 N:失败
     */
    private String invokStatus;
    }

3.创建自定义注解


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(
{ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface InvokCoreLog
{
	String value() default "";
}

4.上述注解的实现类,需要AOP处理的逻辑


import java.math.BigDecimal;
import java.util.Date;

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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.micro.core.enums.CoreConstant;
import com.micro.core.exceptions.ValidateException;
import com.micro.serv.training.dao.mybatis.TInvokCoreLog;
import com.micro.serv.training.enums.YesOrNoType;
import com.micro.serv.training.service.CommonService;

@Aspect
@Component
public class InvokCoreLogAspect
{
	private static final Logger log = LoggerFactory.getLogger(this.getClass());
	@Autowired
	private CommonService commonService;

    //定义切点 @Pointcut
    //在注解的位置切入代码  括号中为上述自定义注解的包路经
	@Pointcut("@annotation(com.service.aop.InvokCoreLog)")
	public void serviceAspect()
	{
	}

	@Around("serviceAspect()")
	public Object doServiceAround(ProceedingJoinPoint joinPoint) throws Throwable
	{
		Object returnObj = null;
		Date startDate = new Date();
		try
		{
			returnObj = joinPoint.proceed();
			this.saveTInvokCoreLog(joinPoint, returnObj, startDate, YesOrNoType.Y.getCode());
			return returnObj;
		}
		catch (Throwable e)
		{
			returnObj = e.getMessage();
			this.saveTInvokCoreLog(joinPoint, returnObj, startDate, YesOrNoType.N.getCode());
			return e;
		}
	}

	private void saveTInvokCoreLog(ProceedingJoinPoint joinPoint, Object returnObj, Date startDate, String invokStatus)
	{
		Date endDate = new Date();
		TInvokCoreLog tInvokCoreLog = new TInvokCoreLog();
		tInvokCoreLog.setInvokCoreLogId(commonService.generatePK());
		tInvokCoreLog.setInvokParam(this.getParam(joinPoint));
		tInvokCoreLog.setInvokMethod(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName());
		tInvokCoreLog.setInvokReturn(returnObj.getClass().getName() + ":" + JSON.toJSONString(returnObj));
		tInvokCoreLog.setInvokProturl(String.valueOf(joinPoint.getArgs()[1]));
		tInvokCoreLog.setStartTime(startDate);
		tInvokCoreLog.setEndTime(endDate);
		tInvokCoreLog.setInvokCost(new BigDecimal((startDate.getTime() - endDate.getTime()) / 1000 % 60));
		tInvokCoreLog.setInvokStatus(invokStatus);
		//保存数据库,这里打印出来查看
		log.info(JSON.toJSONString(tInvokCoreLog));
	}
	

    /**
     * 获取方法参数
     *
     * @param joinPoint
     * @return
     */
    private String getParam(ProceedingJoinPoint joinPoint)
    {
        StringBuilder params = new StringBuilder();
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0)
        {
            for (int i = 0; i < joinPoint.getArgs().length; i++)
            {
                params.append(joinPoint.getArgs()[i].getClass()).append(":").append(JSON.toJSONString(joinPoint.getArgs()[i])).append(";");
            }
        }
        return params.toString();
    }
	
}

5.在方法上添加注解

注意点: 方法前缀一定是 public 才会生效


    @Override
    @InvokCoreLog
    public UserReq grantDivide(String orderId, UserReq userReq) throws Exception
    {
        logger.info("====================进入UserServiceImpl,执行业务逻辑====================");
        this.getCode(orderId, userReq);
        logger.info("====================结束UserServiceImpl====================");
        return userReq;
    }

至此,当Controller层调用grantDivide方法时会先通过InvokCoreLog的实现类InvokCoreLogAspect,当执行InvokCoreLogAspect中的joinPoint.proceed();时,才会进入到service层,我们就记录了一次地宫用grantDivide方法的详细信息:包括调用的时间,调用耗时,传入参数,返回值等等信息

但是在实际业务中我们需要调用的往往不是直接从Controller调用,而是经过了很多方法才最终调用了我们需要记录日志的方法;我的当前的场景就是,Controller调Service,Service内部经过了一个方法的处理才最终才会到记录的方法(同样是在Service中),这个时候直接加注解是不生效的,

实际业务代码(模拟)


    @Override
    public UserReq grantDivide(String orderId, UserReq userReq) throws Exception
    {
        this.getCode(orderId, userReq);
        return userReq;
    }

    @InvokCoreLog
    public UserReq getCode(String orderId, UserReq userReq)
    {
        logger.info("====================进入UserServiceImpl,执行业务逻辑====================");
        logger.info("====================结束UserServiceImpl====================");
        return userReq;
    }

调用UserService中的grantDivide()方法时,Spring的动态代理帮我们动态生成了一个代理的对象,暂且叫他$UserService。所以调用hello()方法实际上是代理对象$UserService调用的。但是在grantDivide()方法内调用同一个类的另外一个注解方法getCode()时,实际上是通过this.getCode()执行的, this 指的是UserService 对象,并不是$UserService代理对象调用的,没有走代理。所以注解失效

转自Spring之AOP注解失效原因和解决方法

解决方法 通过实现ApplicationContext获取代理对象。新建获取代理对象的工具类SpringUtil


import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringUtil implements ApplicationContextAware
{

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
    {
        if (SpringUtil.applicationContext == null)
        {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext()
    {
        return applicationContext;
    }

    //通过name获取 Bean.
    public static Object getBean(String name)
    {
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz)
    {
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz)
    {
        return getApplicationContext().getBean(name, clazz);
    }

}

将实际业务代码(模拟)修改为:将this改为SpringUtil.getBean(this.getClass())即可


    @Override
    public UserReq grantDivide(String orderId, UserReq userReq) throws Exception
    {
        SpringUtil.getBean(this.getClass()).getCode(orderId, userReq);
        return userReq;
    }

    @InvokCoreLog
    public UserReq getCode(String orderId, UserReq userReq)
    {
        logger.info("====================进入UserServiceImpl,执行业务逻辑====================");
        logger.info("====================结束UserServiceImpl====================");
        return userReq;
    }

重新启动项目,会发现调用getCode方法时进入了AOP的InvokCoreLogAspect中

后续项目中遇到几个问题,记录一下

1.@Around注解返回值及返回异常问题

        try
        {
            //调用 proceed() 方法才会真正的执行实际被代理的方法
            proceed = joinPoint.proceed();
            //这里获取返回值返回即可
            return proceed;
        } catch (Throwable throwable)
        {
            //这里处理时直接throw就好,不需要额外new
            throw throwable;
        }

2.获取方法注解属性变量

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SystemControllerLog annotation = method.getAnnotation(SystemControllerLog.class);
        //获取变量值
        annotation.value();

参考资料

java/springboot自定义注解实现AOP

Spring Aop实例@Aspect、@Before、@AfterReturning@Around 注解方式配置

springboot—spring aop 实现系统操作日志记录存储到数据库

spring@Transactional 失效原因及解决办法

Spring之AOP注解失效原因和解决方法

java @interface自定义注解和通过反射获取注解属性值

JAVA @interface自定义注解快速实战,传参不同,注解属性值不同

发布了12 篇原创文章 · 获赞 2 · 访问量 3708

猜你喜欢

转载自blog.csdn.net/qq_36249132/article/details/95752383