這里是指 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 和各個方法的映射關系,並緩存