spring boot 自定义注解 记录service层日志 AOP
程序员文章站
2022-05-02 11:33:31
...
现在项目中要实现一个小功能,就是在当前系统中调用其他服务的接口,如果只是用日志记录的话查找问题就会很麻烦,所以现在要实现的就是用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中
参考文件
java/springboot自定义注解实现AOP
Spring Aop实例@Aspect、@Before、@[email protected] 注解方式配置
springboot—spring aop 实现系统操作日志记录存储到数据库
[email protected] 失效原因及解决办法
Spring之AOP注解失效原因和解决方法
上一篇: 软件工程笔记
推荐阅读
-
Spring AOP +自定义注解 + Spel表达式 实现审计日志
-
Spring Boot AOP记录用户操作日志
-
Spring boot实现AOP记录操作日志
-
Spring Boot分布式系统实践【基础模块构建3.3】注解轻松实现操作日志记录
-
spring+aop+自定义注解实现操作日志记录
-
spring+aop+自定义注解实现操作日志记录
-
springboot aop 自定义注解方式实现一套完善的日志记录(完整源码)
-
Spring Boot如何通过自定义注解实现日志打印详解
-
Spring Boot 中使用自定义注解,AOP 切面打印出入参日志及Dubbo链路追踪透传traceId
-
Spring3.0 + 自定义注解实现操作日志记录功能 AOP自定义注解javaspring