当一个切面对一个业务类生效时,我们使用的业务类对象实际上是Spring帮我们生成的一个代理对象,而这个代理的粒度,是类级别的。
自调用,是指一个类的方法调用本类的其他方法。
1、简单的 AOP 使用示例
示例代码中有3个类:
├── Main.java
├── TestAspect.java
└── TestBean.java
TestBean.java
package demo01;
import org.springframework.stereotype.Component;
@Component
public class TestBean {
public void hello() {
System.out.println("hello");
}
}
TestAspect.java
package demo01;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TestAspect {
// 定义切点(切入位置)
@Pointcut("execution(* demo01.TestBean.*(..))")
private void pointcut(){}
@Before("pointcut()")
public void before(JoinPoint joinPoint){
System.out.println("我是前置通知");
}
}
Main.java
package demo01;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Main.class);
TestBean testBean = ctx.getBean(TestBean.class);
testBean.hello();
}
}
执行结果如下:
我是前置通知
hello
2、代理的粒度是类级别的,所以自调用时不会走切面逻辑
我们对 TestBean 稍作改造,让hello函数:
@Component
public class TestBean {
public void hello() {
System.out.println("hello");
hi();
}
public void hi() {
System.out.println("hi");
}
}
如果我们调用 hi 函数,会输出:
我是前置通知
hi
如果我们调用 hello 函数,会输出:
我是前置通知
hello
hi
注意,hi之前没有我是前置通知
。
Spring的事务管理中有个 @Transactional
注解可以方便的管理事务,也是基于 AOP 实现的。该注解在下面的情况下会失效:外部类调用本类的一个没有 @Transactional
注解的函数,该函数调用本类的一个有 @Transactional
注解的函数。失效原因就是因为代理是类级别的。
3、为什么要这样设计?
技术上能实现自调用的时也走切面逻辑,比如 cglib 的 MethodInterceptor (Java:使用 cglib 实现动态代理)。
有些场景自调用走代理更合适,而另外一些场景不走代理更合适。选择类级别的代理是权衡的结果。
我们有办法让自调用走代理吗?有。
4、让自调用走代理的方法1:自装配
将 TestBean 改造为:
@Component
public class TestBean {
@Autowired
private TestBean self; // 这个装配的对象是代理对象
public void hello() {
System.out.println("hello");
self.hi();
}
public void hi() {
System.out.println("hi");
}
}
如果一个 Bean 没有被代理,也可以这样使用。如果在你的测试中运行失败,请将 Spring 升级到最新版本。
5、让自调用走代理的方法2:启用 exposeProxy
使用 @EnableAspectJAutoProxy
注解时,指定 exposeProxy 为 true。
@EnableAspectJAutoProxy(exposeProxy=true)
业务逻辑中使用AopContext.currentProx
获取当前的代理。
比如将 TestBean 改造为:
// letianbiji.com
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Component;
@Component
public class TestBean {
public void hello() {
System.out.println("hello");
TestBean self = (TestBean) AopContext.currentProxy(); // 获取当前代理
self.hi();
}
public void hi() {
System.out.println("hi");
}
}