Spring 核心技術 AOP 實例

AOP 簡介

Aop(Aspect Oriented Programming),面向切面編程,這是對面向對象思想的一種補充。

面向切面編程,就是在程序運行時,不改變程序源碼的情況下,動態的增強方法的功能,常見的使用場景非常多:

  1. 日誌
  2. 事務
  3. 數據庫操作
  4. ....

這些操作中,無一例外,都有很多模板化的代碼,而解決模板化代碼,消除臃腫就是 Aop 的強項。在 Aop 中,有幾個常見的概念:

概念 說明
切點 要添加代碼的地方,稱作切點
通知(增強) 通知就是向切點動態添加的代碼
切面 切點 + 通知
連接點 切點的定義

AOP 的實現

在 Aop 實際上集基於 Java 動態代理來實現的。Java 中的動態代理有兩種實現方式:

  • cglib
  • jdk

動態代理

基於 JDK 的動態代理實例。

1. 定義一個計算器接口

public interface MyCalculator {
    
    int add(int a, int b);
}

2. 定義計算機接口的實現

public class MyCalculatorImpl implements MyCalculator {
    
    public int add(int a, int b) {
        return a + b;
    }
}

3. 定義代理類

public class CalculatorProxy {
    
    public static Object getInstance(final MyCalculatorImpl myCalculator) {
        return Proxy.newProxyInstance(CalculatorProxy.class.getClassLoader(), myCalculator.getClass().getInterfaces(), new InvocationHandler() {
            
            /**
             * @param proxy 代理對象
             * @param method 代理的方法
             * @param args 方法的參數
             * @return
             * @throws Throwable
             */
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method.getName()+"方法開始執行啦...");
                Object invoke = method.invoke(myCalculator, args);
                System.out.println(method.getName()+"方法執行結束啦...");
                return invoke;
            }
        });
    }
}

Proxy.newProxyInstance 方法接收三個參數,第一個是一個 classloader,第二個是代理多項實現的接口,第三個是代理對象方法的處理器,所有要額外添加的行爲都在 invoke 方法中實現。

XML 配置 AOP

1. 在 pom.xml 中引入 Spring 和 AOP 相關依賴

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.9.RELEASE</version>
</dependency>

<!-- AOP Begin -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.5</version>
</dependency>
<!-- AOP Over -->

2. 接下來,定義通知/增強,但是單純定義自己的行爲即可,不再需要註解

public class LogAspect {

    public void before(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法開始執行了...");
    }
    
    public void after(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法執行結束了...");
    }

    public void returing(JoinPoint joinPoint,Integer r) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法返回:"+r);
    }
    
    public void afterThrowing(JoinPoint joinPoint,Exception e) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法拋異常了:"+e.getMessage());
    }
    
    public Object around(ProceedingJoinPoint pjp) {
        Object proceed = null;
        try {
            //這個相當於 method.invoke 方法,我們可以在這個方法的前後分別添加日誌,就相當於是前置/後置通知
            proceed = pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return proceed;
    }
}

3. 接下來在 Spring 中配置 AOP

<bean class="com.antoniopeng.hello.spring.aop.LogAspect" id="logAspect"/>
<aop:config>
    <aop:pointcut id="pc1" expression="execution(* com.antoniopeng.hello.spring.aop.commons.*.*(..))"/>
    
    <aop:aspect ref="logAspect">
        <aop:before method="before" pointcut-ref="pc1"/>
        <aop:after method="after" pointcut-ref="pc1"/>
        <aop:after-returning method="returing" pointcut-ref="pc1" returning="r"/>
        <aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
        <aop:around method="around" pointcut-ref="pc1"/>
    </aop:aspect>
</aop:config>

4. 最後,在 Main 方法中加載配置文件

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyCalculatorImpl myCalculator = ctx.getBean(MyCalculatorImpl.class);
        myCalculator.add(3, 4);
        myCalculator.min(5, 6);
    }
}

本文發於:https://antoniopeng.com

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章