1. 程式人生 > 實用技巧 >SpringAOP 面向切面程式設計

SpringAOP 面向切面程式設計

AOP的相關概念

AOP:全稱是 Aspect Oriented Programming 即:面向切面程式設計。

簡單的說它就是把我們程式重複的程式碼抽取出來,在需要執行的時候,使用動態代理的技術,在不修改原始碼的
基礎上,對我們的已有方法進行增強。

AOP 的作用及優勢

作用:
在程式執行期間,不修改原始碼對已有方法進行增強。
優勢:
減少重複程式碼
提高開發效率
維護方便

AOP 的實現方式

AOP 的具體應用

使用自定義動態代理實現轉賬操作

pom.xml配置

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
    </dependencies>

配置account

 private int id;
    private String name;
    private Float money;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Float getMoney() {
        return money;
    }

    public void setMoney(Float money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }

bean.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.itheim"/>
    <bean id="proxyAccountService" factory-bean="proxyService" factory-method="getaccountService"/>

    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"/>
    <!-- 配置代理物件 -->
    <bean id="proxyService" class="com.itheim.proxy.proxyService">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="service" ref="accountService"/>
    </bean>
    <bean id="transactionManager" class="com.itheim.Utils.TransactionManager">
        <property name="connectionUtils" ref="ConnectionUtils"/>
    </bean>
    <!-- 配置連線物件 -->
    <bean id="ConnectionUtils" class="com.itheim.Utils.ConnectionUtils">
        <property name="dataSource" ref="ds"/>
    </bean>
    <bean id="accountService" class="com.itheim.service.Imp.accountServiceImp"/>

    <!-- 配置資料來源 -->
    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"/>
        <property name="user" value="root"/>
        <property name="password" value="root"/>
    </bean>

</beans>

配置業務邏輯層介面和實現類

public interface accountService {
    //業務層介面
    void transfer(String sourceName,String targetName,Float money);
}


public class accountServiceImp implements accountService {
    @Autowired
    private accountDao accountDao;
    public void transfer(String sourceName, String targetName, Float money) {
        Account account= accountDao.findByAccount(sourceName);
        Account account1 = accountDao.findByAccount(targetName);
        account.setMoney(account.getMoney()-money);
        //更新使用者資料
        accountDao.updateMoney(account);
//        int i=1/0;
        account1.setMoney(account1.getMoney()+money);
        accountDao.updateMoney(account1);

    }
}

配置資料訪問層介面和實現類

public interface accountDao {
    public void updateMoney(Account account);

    public Account findByAccount(String name);
}



@Repository("accountDao")
public class accountDaoImp implements accountDao {
    @Autowired
    private QueryRunner runner;
    @Autowired
    private ConnectionUtils connectionUtils;
    public void updateMoney(Account account){
        try {
            runner.update(connectionUtils.getThreadConnection(),"update account set money=? where name=?",account.getMoney(),account.getName());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public Account findByAccount(String name) {
        try {
            return runner.query(connectionUtils.getThreadConnection(),"select * from account where name=? ",new BeanHandler<Account>(Account.class),name);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

    }
}

配置連線工具類

/**
 * 連線的工具類,它用於從資料來源中獲取一個連線,並且實現和執行緒的繫結
 */

public class ConnectionUtils {
    private ThreadLocal<Connection> tl=new ThreadLocal<Connection>();
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Connection getThreadConnection() {
        Connection conn = tl.get();
        try {
            if (conn==null){
                conn = dataSource.getConnection();
                tl.set(conn);

            }
            return conn;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    public void removeConnect(){
        tl.remove();
    }
}

配置控制事務的類

/**
 * 控制事務的類 開啟事務 提交事務 回滾事務 關閉連線
 */
public class TransactionManager {
    private  ConnectionUtils connectionUtils;

    public  void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * 開啟事務
     */
    public  void beginTransaction() throws SQLException {
        connectionUtils.getThreadConnection().setAutoCommit(false);
    }

    /**
     * 提交事務
     */
    public void Commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滾事務
     */
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /**
     * 釋放連線
     */
    public void close(){
        try {
            connectionUtils.getThreadConnection().close();
            connectionUtils.removeConnect();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

基於介面實現編寫動態代理類

public class proxyService {

    private TransactionManager transactionManager;
    private accountService service;

    public void setService(accountService service) {
        this.service = service;
    }

    public final void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public accountService getaccountService() throws SQLException {
        return (accountService) Proxy.newProxyInstance(service.getClass().getClassLoader(),service.getClass().getInterfaces(), new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object rtValue=null;
                try {
                    transactionManager.beginTransaction();
                    rtValue=method.invoke(service,args);
                    transactionManager.Commit();
                    return rtValue;
                } catch (Exception e) {
                    //當發生異常時回滾事務
                    transactionManager.rollback();
                    throw new RuntimeException(e);
                }finally {

                    //釋放連線
                    transactionManager.close();

                }
            }
        });


    }
}

基於子類實現的動態代理

/**
* 基於子類的動態代理
* 要求:
* 被代理物件不能是最終類
* 用到的類:
* Enhancer
* 用到的方法:
* create(Class, Callback)
* 方法的引數:
* Class:被代理物件的位元組碼
* Callback:如何代理
* @param args
*/

public static void main(String[] args) {
final Actor actor = new Actor();
Actor cglibActor = (Actor) Enhancer.create(actor.getClass(),
new MethodInterceptor() {
/**
* 執行被代理物件的任何方法,都會經過該方法。在此方法內部就可以對被代理物件的任何
方法進行增強。
*
* 引數:
* 前三個和基於介面的動態代理是一樣的。
* MethodProxy:當前執行方法的代理物件。
* 返回值:
* 當前執行方法的返回值
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
String name = method.getName();
Float money = (Float) args[0];
Object rtValue = null;
if("basicAct".equals(name)){
//基本演出
if(money > 2000){
rtValue = method.invoke(actor, money/2);
}
}
if("dangerAct".equals(name)){
//危險演出
if(money > 5000){
rtValue = method.invoke(actor, money/2);
}
}
return rtValue;
}
});
cglibActor.basicAct(10000);
cglibActor.dangerAct(100000);
}
}

測試類

public class Client {
    public static void main(String[] args) throws SQLException {
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
        accountService service = (accountService) ac.getBean("proxyAccountService");
        service.transfer("zhangsan","lisi",200f);
    }
}

AOP 相關術語

Joinpoint( 連線點):
所謂連線點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因為 spring 只支援方法型別的
連線點。
Pointcut( 切入點):
所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義。
Advice( 通知/ 增強):
所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知。
通知的型別:前置通知,後置通知,異常通知,最終通知,環繞通知。
Introduction( 引介):
引介是一種特殊的通知在不修改類程式碼的前提下, Introduction 可以在執行期為類動態地新增一些方
法或 Field。
Target( 目標物件):
代理的目標物件。
Weaving( 織入):
是指把增強應用到目標物件來建立新的代理物件的過程。
spring 採用動態代理織入,而 AspectJ 採用編譯期織入和類裝載期織入。
Proxy (代理):
一個類被 AOP 織入增強後,就產生一個結果代理類。
Aspect( 切面):
是切入點和通知(引介)的結合。

學習 spring 中的 AOP 要明白的事

a 、開發階段(我們做的)
編寫核心業務程式碼(開發主線):大部分程式設計師來做,要求熟悉業務需求。
把公用程式碼抽取出來,製作成通知。(開發階段最後再做):AOP 程式設計人員來做。
在配置檔案中,宣告切入點與通知間的關係,即切面。:AOP 程式設計人員來做。
b 、執行階段(Spring 框架完成的)
Spring 框架監控切入點方法的執行。一旦監控到切入點方法被執行,使用代理機制,動態建立目標對
象的代理物件,根據通知類別,在代理物件的對應位置,將通知對應的功能織入,完成完整的程式碼邏輯執行。

使用Spring AOP 實現轉賬操作

導包

bean.xml 匯入約束

xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
<!--配置事務管理器-->
    <bean id="transactionManager" class="com.itheim.Utils.TransactionManager">
        <property name="connectionUtils" ref="ConnectionUtils"/>
    </bean>
<!-- 配置aop -->
aop:config:
作用:用於宣告開始 aop 的配置

<aop:config>
        aop:pointcut :
           作用:
               用於配置切入點表示式。就是指定對哪些類的哪些方法進行增強。
           屬性:
                expression:用於定義切入點表示式。
                id:用於給切入點表示式提供一個唯一標識
        <aop:pointcut id="tl" expression="execution(* com.itheim.service.Imp.*.*(..))"/>

        <!-- aop:aspect:
        作用:用於配置切面。
         屬性:
       id:給切面提供一個唯一標識。
       ref:引用配置好的通知類 bean 的 id。-->

<!-- 
  aop:before
作用:
用於配置前置通知。指定增強的方法在切入點方法之前執行
屬性:
method:用於指定通知類中的增強方法名稱
ponitcut-ref:用於指定切入點的表示式的引用
poinitcut:用於指定切入點表示式
執行時間點:
切入點方法執行之前執行

aop:after-returning
作用:
用於配置後置通知
屬性:
method :指定通知中方法的名稱。
pointct :定義切入點表示式
pointcut-ref :指定切入點表示式的引用
執行時間點:
切入點方法正常執行之後。它和異常通知只能有一個執行

aop:after-throwing
作用:
用於配置異常通知
屬性:
method :指定通知中方法的名稱。
pointct :定義切入點表示式
pointcut-ref :指定切入點表示式的引用
執行時間點:
切入點方法執行產生異常後執行。它和後置通知只能執行一個

aop:after
作用:
用於配置最終通知
屬性:
method :指定通知中方法的名稱。
pointct :定義切入點表示式
pointcut-ref :指定切入點表示式的引用
執行時間點:
無論切入點方法執行時是否有異常,它都會在其後面執行。
-->

        <aop:aspect id="txAdvice" ref="transactionManager">
            <aop:before method="beginTransaction" pointcut-ref="tl"/>
            <aop:after-returning method="Commit" pointcut-ref="tl"/>
            <aop:after-throwing method="rollback" pointcut-ref="tl"/>
            <aop:after method="close" pointcut-ref="tl"/>
        </aop:aspect>
</aop:config>

切入點表示式說明

execution:匹配方法的執行(常用)
execution(表示式)
表示式語法:execution([修飾符] 返回值型別 包名.類名.方法名(引數))
寫法說明:
全匹配方式:
public void
com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
訪問修飾符可以省略
void
com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
返回值可以使用*號,表示任意返回值
*
com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
包名可以使用*號,表示任意包,但是有幾級包,需要寫幾個*
* *.*.*.*.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
使用..來表示當前包,及其子包
* com..AccountServiceImpl.saveAccount(com.itheima.domain.Account)
類名可以使用*號,表示任意類
* com..*.saveAccount(com.itheima.domain.Account)
方法名可以使用*號,表示任意方法
* com..*.*( com.itheima.domain.Account)
引數列表可以使用*,表示引數可以是任意資料型別,但是必須有引數
* com..*.*(*)
引數列表可以使用..表示有無引數均可,有引數可以是任意型別
* com..*.*(..)
全通配方式:
* *..*.*(..)
注:
通常情況下,我們都是對業務層的方法進行增強,所以切入點表示式都是切到業務層實現類。
execution(* com.itheima.service.impl.*.*(..))

環繞通知

配置方式:
<aop:config>
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/>
<aop:aspect id="txAdvice" ref="txManager">
<!-- 配置環繞通知 -->
<aop:around method="transactionAround" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
aop:around :
作用:
用於配置環繞通知
屬性:
method:指定通知中方法的名稱。
pointct:定義切入點表示式
pointcut-ref:指定切入點表示式的引用
說明:
它是 spring 框架為我們提供的一種可以在程式碼中手動控制增強程式碼什麼時候執行的方式。
注意:
通常情況下,環繞通知都是獨立使用的

pom.xml匯入包

//解析bean.xml中的aop:pointcut表示式
<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
</dependency>

基於註解的