这里是指 Spring 应用层的方式,不是指底层实现的方式。
底层实现方式熟悉的有两种:JDK 动态代理和 CGLIB 代理:https://www.cnblogs.com/jhxxb/p/10520345.html
Spring 应用层提供了多种代理创建方式:ProxyFactoryBean、ProxyFactory、AspectJProxyFactory
pom 依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency>
ProxyFactoryBean
// 业务 public interface MathCalculator { int div(int i, int j); } @Service public class MathCalculatorImpl implements MathCalculator { @Override public int div(int i, int j) { System.out.println("MathCalculator...div...方法执行"); return i / j; } } // 定义一个前置通知 @Component("logMethodBeforeAdvice") public class LogMethodBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("this is LogMethodBeforeAdvice"); } } // 注册一个代理 Bean public class AopConfig { @Bean public ProxyFactoryBean proxyFactoryBean(MathCalculator mathCalculator) { ProxyFactoryBean factoryBean = new ProxyFactoryBean(); // 代理的目标对象 效果同setTargetSource(@Nullable TargetSource targetSource) // 此处需要注意的是,这里如果直接new,那么该类就不能使用@Autowired之类的注入 因此建议此处还是从容器中去拿 // 因此可以写在入参上(这也是标准的写法~~) // factoryBean.setTarget(new HelloServiceImpl()); factoryBean.setTarget(mathCalculator); // setInterfaces和setProxyInterfaces的效果是相同的。设置需要被代理的接口, // 若没有实现接口,那就会采用cglib去代理 // 需要说明的一点是:这里不设置也能正常被代理(若你没指定,Spring 内部会去帮你找到所有的接口,然后全部代理上)设置的好处是只代理指定的接口 factoryBean.setInterfaces(MathCalculator.class); // factoryBean.setProxyInterfaces(new Class[]{HelloService.class}); // 需要植入进目标对象的bean列表 此处需要注意:这些bean必须实现类 org.aopalliance.intercept.MethodInterceptor或 org.springframework.aop.Advisor的bean ,配置中的顺序对应调用的顺序 factoryBean.setInterceptorNames("logMethodBeforeAdvice"); // 若设置为 true,强制使用 cglib,默认是 false 的 // factoryBean.setProxyTargetClass(true); return factoryBean; } } public class AopTest { @Test public void aopTest() { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AopConfig.class); System.out.println("SpringVersion: " + SpringVersion.getVersion()); System.out.println("================================================"); // expected single matching bean but found 2: mathCalculatorImpl,proxyFactoryBean // 如果通过类型获取,会找到两个 Bean:一个我们自己的实现类、一个 ProxyFactoryBean 所生产的代理类,而此处我们显然是希望要生成的代理类的,因此我们只能通过名称来(或者加上 @Primary) // MathCalculator bean = applicationContext.getBean(MathCalculator.class); MathCalculator bean = (MathCalculator) applicationContext.getBean("proxyFactoryBean"); bean.div(1, 1); System.out.println("================================================"); System.out.println(bean); System.out.println(bean.getClass()); // class com.sun.proxy.$Proxy28 用的 JDK 动态代理 // 顺便说一句:这样也是没错得。因为 Spring AOP 代理出来的每个代理对象,都默认实现了这个接口(它是个标记接口) // 它这个也就类似于所有的 JDK 代理出来的,都是 Proxy 的子类是一样的思想 SpringProxy springProxy = (SpringProxy) bean; System.out.println("================================================"); mathCalculator.div(1, 0); applicationContext.close(); } }
虽然很多时候都是结合 IOC 容器一起使用,但是它并不依赖 IOC:
public class PointcutTest { @Test public void expressionPointcutTest() { String pointcutExpression = "execution(int aop.PointcutTest.Person.run())"; // 会拦截 Person.run() 方法 // String pointcutExpression = "args()"; // 所有没有入参的方法会被拦截。 比如:run() 会拦截,但是 run(int i) 不会被拦截 // AspectJExpressionPointcut 支持的表达式一共有 11 种(也就是 Spring 全部支持的切点表达式类型) // String pointcutExpression = "@annotation(org.springframework.test.context.transaction.AfterTransaction)"; // 拦截标有 @AfterTransaction 此注解的任意方法们 // ============================================================= ProxyFactory factory = new ProxyFactory(new Person()); // 声明一个 aspectj 切点,一张切面 AspectJExpressionPointcut cut = new AspectJExpressionPointcut(); cut.setExpression(pointcutExpression); // 设置切点表达式 // 声明一个通知(此处使用环绕通知 MethodInterceptor ) Advice advice = (MethodInterceptor) invocation -> { System.out.println("============>放行前拦截..."); Object obj = invocation.proceed(); System.out.println("============>放行后拦截..."); return obj; }; // 切面 = 切点 + 通知 // 它还有个构造函数:DefaultPointcutAdvisor(Advice advice); 用的切面就是 Pointcut.TRUE,所以如果你要指定切面,请使用自己指定的构造函数 // Pointcut.TRUE:表示啥都返回 true,也就是说这个切面作用于所有的方法上/所有的方法 // addAdvice();方法最终内部都是被包装成一个 `DefaultPointcutAdvisor`,且使用的是 Pointcut.TRUE 切面,因此需要注意这些区别,相当于 new DefaultPointcutAdvisor(Pointcut.TRUE,advice); Advisor advisor = new DefaultPointcutAdvisor(cut, advice); factory.addAdvisor(advisor); Person p = (Person) factory.getProxy(); // 执行方法 p.run(); p.run(10); p.say(); p.sayHi("Jack"); p.say("Tom", 666); }
static class Person { public int run() { System.out.println("我在run..."); return 0; } public void run(int i) { System.out.println("我在run...<" + i + ">"); } public void say() { System.out.println("我在say..."); } public void sayHi(String name) { System.out.println("Hi," + name + ",你好"); } public int say(String name, int i) { System.out.println(name + "----" + i); return 0; } } }
ProxyFactory
代理的 Bean 都是 new 出来的,和 Spring 容器没啥关系,可以直接创建代理用
public class AopTest { public static void main(String[] args) { ProxyFactory proxyFactory = new ProxyFactory(new MathCalculatorImpl()); // 添加两个 Advise,一个匿名内部类表示 proxyFactory.addAdvice((AfterReturningAdvice) (returnValue, method, args1, target) -> System.out.println("AfterReturningAdvice method=" + method.getName())); proxyFactory.addAdvice(new LogMethodBeforeAdvice()); MathCalculator proxy = (MathCalculator) proxyFactory.getProxy(); System.out.println(proxy.div(1, 1)); } }
AspectJProxyFactory
只需要配置切面、通知、切点表达式就能自动的实现切入的效果,整个代理的过程全部由 Spring 内部完成,无侵入,使用方便,也是当前使用最多的方式
@Aspect static class MyAspect { @Pointcut("execution(* div(..))") private void beforeAdd() { } @Before("beforeAdd()") public void before1() { System.out.println("-----------before-----------"); } } public class AopTest { public static void main(String[] args) { AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new MathCalculatorImpl()); // 此处 MyAspect 类上的 @Aspect 注解不能少 proxyFactory.addAspect(MyAspect.class); // proxyFactory.setProxyTargetClass(true); // 是否用 CGLIB 代理 MathCalculator proxy = proxyFactory.getProxy(); System.out.println(proxy.div(1, 1)); System.out.println(proxy.getClass()); // class com.sun.proxy.$Proxy7 } }
切面类定义中定义了一个 Advisor(必须有 @Aspect 注解标注),其对应了一个 MethodBeforeAdvice,实际上是一个 AspectJMethodBeforeAdvice,该 Advice 对应的是 before1() 方法。
切面类定义中还定义了一个 Pointcut,是一个 AspectJExpressionPointcut。
该 Advisor 的语义为拦截所有方法名为 div 的方法,在它之前执行 MyAspect.before1() 方法。
虽然我们可以自己通过编程的方式使用 AspectjProxyFactory 创建基于 @Aspect 标注的切面类的代理,但是通过配置 <aop:aspectj-autoproxy/>(@EnableAspectJAutoProxy) 使用基于 Aspectj 注解风格的 Aop 时,Spring 内部不是通过 AspectjProxyFactory 创建的代理对象,而是通过 ProxyFactory
总结
这三个类本身没有什么关系,但都继承自 ProxyCreatorSupport,创建代理对象的核心逻辑都是在 ProxyCreatorSupport 中实现的
AspectJProxyFactory、ProxyFactoryBean、ProxyFactory 大体逻辑都是:
- 填充 AdvisedSupport(ProxyCreatorSupport 是其子类),然后交给父类 ProxyCreatorSupport。
- 得到 JDK 或者 CGLIB 的 AopProxy
- 代理调用时候被 invoke 或者 intercept 方法拦截(分别在 JdkDynamicAopProxy 和 ObjenesisCglibAopProxy(CglibAopProxy的子类)中),并且在这两个方法中调用 ProxyCreatorSupport#getInterceptorsAndDynamicInterceptionAdvice 方法去初始化 advice 和各个方法的映射关系,并缓存