Spring AOP 代理創建方式


這里是指 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 大體邏輯都是:

  1. 填充 AdvisedSupport(ProxyCreatorSupport 是其子類),然后交給父類 ProxyCreatorSupport。
  2. 得到 JDK 或者 CGLIB 的 AopProxy
  3. 代理調用時候被 invoke 或者 intercept 方法攔截(分別在 JdkDynamicAopProxy 和 ObjenesisCglibAopProxy(CglibAopProxy的子類)中),並且在這兩個方法中調用 ProxyCreatorSupport#getInterceptorsAndDynamicInterceptionAdvice 方法去初始化 advice 和各個方法的映射關系,並緩存

 


https://blog.csdn.net/f641385712/article/details/88926243


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM