记录一下半年前做的一个电商的项目,每个业务方法上都有一个日志注解,是基于AOP注解实现的,然后有一天突然发现部分业务方法被调用的时候没有日志记录,后面调试的时候发现一个很奇怪的东西,如果业务方法是其他bean调用的话就能够正常记录,如果是bean调用自身函数时就失效了。因为之前的代码找不到了,那么接下来随便做一个示例跟大帅们探讨一下知识!!!
问题描述
自定义一个注解@Loggable,并使用AOP来实现日志记录
自定义注解
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 Loggable {
}
AOP切面类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Before("@annotation(com.example.demo.annotation.Loggable)")
public void before(JoinPoint joinPoint) {
System.out.println("方法前: " + joinPoint.getSignature().getName());
}
@After("@annotation(com.example.demo.annotation.Loggable)")
public void after(JoinPoint joinPoint) {
System.out.println("方法后: " + joinPoint.getSignature().getName());
}
}
服务类
import com.example.demo.annotation.Loggable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Loggable
public void externalCall() {
System.out.println("欢迎来到51CTO");
}
@Loggable
public void internalCall() {
this.externalCall(); // 使用this的方式调用
}
}
控制器
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/external")
public String externalCall() {
userService.externalCall();
return "外部调用已记录";
}
@GetMapping("/internal")
public String internalCall() {
userService.internalCall();
return "内部调用已记录";
}
}
测试功能
当访问/external接口时,日志能够正常记录:
方法前: externalCall
欢迎来到51CTO
方法后: externalCall
然而,当访问/internal接口时,日志无法记录:
方法前: internalCall
欢迎来到51CTO
方法后: internalCall
原因分析
AOP代理对象的工作原理是通过动态代理来增强目标对象的方法。Spring AOP默认使用JDK动态代理或CGLIB代理。当我们在同一个类中通过this调用另一个方法时,实际上是在调用原始对象的方法,而不是代理对象的方法。因此,AOP切面无法拦截到这个方法调用。
解决方案
为了使AOP切面能够拦截到内部方法调用,我们需要确保调用的是代理对象的方法。一种常见的解决方法是通过@Autowired注入自身,然后通过注入的对象调用方法。
修改后的服务类
import com.example.demo.annotation.Loggable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserService self;
@Loggable
public void externalCall() {
System.out.println("欢迎来到51CTO");
}
@Loggable
public void internalCall() {
self.externalCall(); // 通过注入的self对象调用方法
}
}
测试结果
修改后,再次访问/internal端点,日志能够正常记录:
方法前: internalCall
方法前: externalCall
欢迎来到51CTO
方法后: externalCall
方法后: internalCall
总结
当在同一个类中通过this调用方法时,AOP切面无法生效。为了避免这种情况,可以通过@Autowired注入自身,然后通过注入的对象调用方法。这样可以确保AOP切面能够正确地拦截到方法调用,从而实现预期的日志记录功能,如果各位大帅有其他的解决方法也欢迎在评论区留言讨论。
一个人写的烂软件将会给另一个人带来一份全职工作