深入學習Spring框架(三)- AOP面向切面

ki16發表於2019-07-15

  

1.什麼是AOP?

  AOP為 Aspect Oriented Programming 的縮寫,即面向切面程式設計, 通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術.。AOP是OOP的延續, 是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離, 從而使得業務邏輯各部分之間的耦合度降低, 提高程式的可重用性, 同時提高了開發的效率。

 

2.AOP與傳統開發對比

  傳統開發模型: 縱向的程式設計

  

 

  面向切面程式設計: 縱橫配合的程式設計

  

3.為什麼用AOP?

  在我們的專案中幾乎每個業務都需要有事務控制、日誌管理、許可權控制等操作。這樣就造成了需要寫大量的重複程式碼。例如:

  

//模擬事務管理器
public class MyTransactionManager {
    public void begin() {
        System.out.println("開啟事務");
    }
    
    public void commit() {
        System.out.println("提交事務");
    }
    
    public void rollback() {
        System.out.println("回滾事務");
    }
    
    public void close() {
        System.out.println("關閉session");
    }
}
//Service層程式碼
public class UserServiceImpl implements UserService {
    // 引入事務管理器
    private MyTransactionManager txManager = new MyTransactionManager();

    @Override
    public void insert(String username) {
        try {
            txManager.begin();
            System.out.println("呼叫dao層插入方法");
            txManager.commit();
        } catch (Exception e) {
            txManager.rollback();
        } finally {
            txManager.close();
        }
    }

    @Override
    public void update(String username) {
        try {
            txManager.begin();
            System.out.println("呼叫dao層修改方法");
            txManager.commit();
        } catch (Exception e) {
            txManager.rollback();
        } finally {
            txManager.close();
        }
    }
}

每個需要對資料庫資料進行修改的業務都要進行事務處理,就會有大量的重複程式碼,所以就需要用APO來減少重複程式碼、提高開發效率、方便維護。

 

4.AOP的實現方式(原理)

  AOP的底層是使用動態代理模式來實現的。(JDK動態代理和CGLIB代理技術)
  動態代理,就是使用者使用的不是真實的物件,而是使用的一個代理物件,而這個代理物件中包含的就是真實的物件,代理物件就是不改變原有物件的功能方法的基礎之上封裝新的功能。
  AOP的原理就是把需要增強的程式碼(重複的程式碼)定義在代理類中,本身只保留核心的程式碼。在方法執行時切到代理物件中執行。

  

5.JDK動態代理

  JDK動態代理是Java官方的代理,使用JDK官方的Proxy類建立代理物件。

  5.1 java.lang.reflect.Proxy 類:

Java 動態代理機制生成的所有動態代理類的父類,它提供了一組靜態方法來為一組介面動態地生成代理類及其物件。
主要方法:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler hanlder)
引數:

loader :類載入器
interfaces : 模擬的介面
hanlder :代理執行處理器(通過實現java.lang.reflect.InvocationHandler介面)

方法職責:為指定類載入器、一組介面及呼叫處理器生成動態代理類例項
返回:動態生成的代理物件

  5.2 java.lang.reflect.InvocationHandler介面:

public Object invoke(Object proxy, Method method, Object[] args)
引數:

proxy :生成的代理物件
method :當前呼叫的真實方法物件
args :當前呼叫方法的實參

方法職責:負責集中處理動態代理類上的所有方法呼叫
返回: 真實方法的返回結果

5.3 示例程式碼:

需要的jar包:

  整體結構:

pojo類

package com.gjs.pojo;

public class User {
    private Integer id;
    private String name;
    private Integer age;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public User() {
        super();
    }
    public User(Integer id, String name, Integer age) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
    }
    
}

service層

package com.gjs.service.impl;

import com.gjs.pojo.User;
import com.gjs.service.UserService;

public class UserServiceImpl implements UserService {

    @Override
    public void insert(User user) {
        System.out.println("呼叫dao層插入方法");
    }

    @Override
    public void update(User user) {
        System.out.println("呼叫dao層修改方法");
    }

}

模擬事務管理器

package com.gjs.util;

//模擬事務管理器
public class MyTransactionManager {
    public void begin() {
        System.out.println("開啟事務");
    }
    
    public void commit() {
        System.out.println("提交事務");
    }
    
    public void rollback() {
        System.out.println("回滾事務");
    }
    
    public void close() {
        System.out.println("關閉session");
    }
}

JDK動態代理類:

package com.gjs.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.gjs.util.MyTransactionManager;
//JDK動態代理類
public class DynamicProxyHandeler {
    private Object target; // 被代理的物件
    private MyTransactionManager manager; // 事務管理器

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setManager(MyTransactionManager manager) {
        this.manager = manager;
    }

    public <T> T getProxyObject(Class<T> clz) {

        ClassLoader loader = this.getClass().getClassLoader();// 類載入器
        Class<?>[] interfaces = clz.getInterfaces();// 被代理物件的介面
        // InvocationHandler():Proxy的處理器,具體做正在增強的地方(需要開發者自己實現)
        Object proxyInstance = Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {

            /**
             * 處理器中具體做增強的方法
             * 
             * @param proxy
             *            代理物件
             * @param method
             *            被代理物件的方法
             * @param args
             *            被代理物件方法的引數
             * @return 被代理物件方法執行以後的結果
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    // 1.開啟事務
                    manager.begin();

                    // 執行被代理物件的犯法
                    result = method.invoke(target, args);

                    // 2.提交事務
                    manager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    // 3.回滾事務
                    manager.rollback();
                } finally {
                    // 4.關閉session
                    manager.close();
                }
                return result;
            }
        });
        return (T) proxyInstance;
    }
}

配置檔案:applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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
">
    
    <!-- 配置模擬的事務管理器 -->
    <bean id="manager" class="com.gjs.util.MyTransactionManager"/>
    
    <!-- 配置代理類 -->
    <bean id="dynamicProxyHandeler" class="com.gjs.proxy.DynamicProxyHandeler">
        <!-- 注入被代理的物件 -->
        <property name="target" >
            <bean class="com.gjs.service.impl.UserServiceImpl"/>
        </property>
        <!-- 注入事務管理器 -->
        <property name="manager" ref="manager"/>
    </bean>
    
</beans>

測試類

package com.gjs.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.gjs.pojo.User;
import com.gjs.proxy.DynamicProxyHandeler;
import com.gjs.service.UserService;
import com.gjs.service.impl.UserServiceImpl;

//表示先啟動Spring容器,把junit執行在Spring容器中
@RunWith(SpringJUnit4ClassRunner.class)
// 表示從哪裡載入資原始檔,預設從src(源目錄)下面載入
@ContextConfiguration("classpath:applicationContext.xml")
public class TestSpring {
    // 注入代理類的物件
    @Autowired
    private DynamicProxyHandeler dynamicProxyHandeler;

    @Test
    public void testInsert() throws Exception {
        // 通過代理類獲取代理物件
        UserService proxyObject = dynamicProxyHandeler.getProxyObject(UserServiceImpl.class);
        User user = new User(null, "張三", 18);
        proxyObject.insert(user);
    }

    @Test
    public void testUpdate() throws Exception {
        // 通過代理類獲取代理物件
        UserService proxyObject = dynamicProxyHandeler.getProxyObject(UserServiceImpl.class);
        User user = new User(1, "李四", 18);

        // 執行代理物件的修改方法
        proxyObject.update(user);
    }

    @Test
    public void test1() throws Exception {
        // 通過代理類獲取代理物件
        UserService proxyObject = dynamicProxyHandeler.getProxyObject(UserServiceImpl.class);
        proxyObject.toString();
    }
}

5.4 JDK動態代理的不足

1.JDK動態代理的物件必須要實現一個介面
2.需要為每個物件建立代理物件
3.動態代理的最小單位是類(所有類中的方法都會被處理),查詢方法不需要事務,可能不需要被代理

6.CGLIB動態代理

  CGLIB和JDK動態代理一樣都是動態代理,不同的是CGLIB代理沒有介面也可以進行代理,而且在Spring中已經整合了CGLIB代理,可以直接使用,不需要再匯入其他jar包。
  CGLIB代理使用Enhancer類代理

  

  6.1 示例程式碼:

  動態代理類:

package com.gjs.proxy;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;

import com.gjs.util.MyTransactionManager;
//CGLIB動態代理類
public class DynamicProxyHandeler {
    private Object target; // 被代理的物件
    private MyTransactionManager manager; // 事務管理器

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setManager(MyTransactionManager manager) {
        this.manager = manager;
    }

    public <T> T getProxyObject(Class<T> clz) {

        ClassLoader loader = this.getClass().getClassLoader();// 類載入器
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(loader); // 設定類載入器
        enhancer.setSuperclass(target.getClass()); // 設定代理物件的class

        /*
         * 代理的具體實現 InvocationHandler:org.springframework.cglib.proxy.InvocationHandler
         */
        enhancer.setCallback(new InvocationHandler() {
            /**
             * 處理器中具體做增強的方法
             * 
             * @param proxy
             *            代理物件
             * @param method
             *            被代理物件的方法
             * @param args
             *            被代理物件方法的引數
             * @return 被代理物件方法執行以後的結果
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    // 1.開啟事務
                    manager.begin();

                    // 執行被代理物件的犯法
                    result = method.invoke(target, args);

                    // 2.提交事務
                    manager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    // 3.回滾事務
                    manager.rollback();
                } finally {
                    // 4.關閉session
                    manager.close();
                }
                return result;
            }
        });
        Object proxyObject = enhancer.create();// 建立代理物件
        return (T) proxyObject;
    }
}

  其他程式碼參考jdk動態代理的示例程式碼

6.2 CGLIB動態代理使用要點

1,CGLIB可以生成目標類的子類,並重寫父類非final修飾符的方法。
2,要求類不能是final的,要代理的方法要是非final、非static、非private的。
3,動態代理的最小單位是類(所有類中的方法都會被處理);

 

7. 直接使用動態代理的缺陷

1.直接使用代理雖然解決程式碼重複問題,但卻需要在配置檔案中為每一個類都配置代理類
2.無法通過規則制定要攔截的方法,所有方法都會進行代理。例如:查詢的方法不需要進行事務處理、toString方法不需要代理。

  Spring提供了AOP的實現就能解決這些問題。

8.Spring的AOP

Spring的AOP可以通過規則設定來攔截方法,加入可以統一處理的程式碼。並且spring會根據目標類是否實現了介面來決定採用哪種動態代理的方式:
  若目標物件實現了介面,Spring就會使用JDK動態代理。
  若目標物件沒有實現任何介面,Spring就使用CGLIB庫生成目標物件的子類。

AOP相關術語:

Joinpoint(連線點):所謂連線點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支援方法型別的連線點。即根據規則指定攔截的方法,我們將每一個被攔截的方法稱為連線點。

Pointcut(切入點):切入點就是攔截方法設定的規則,即需要對哪些Joinpoint進行攔截的定義。

Advice(通知/增強):設定在方法之前攔截或者方法執行之後攔截或者方法出異常後攔截,或者方法之前和之後都攔截。我們將這些攔截場景稱為通知。 所謂通知是指攔截到Joinpoint之後所要做的事情就是通知。通知的型別:前置通知,後置通知,異常通知,最終通知,環繞通知。

Aspect(切面):切面就是我們的攔截處理類。是切入點和通知的結合。

Weaving(織入):把切面加入到物件,並建立出代理物件的過程。(這個過程由Spring自動來完成)

9.基於XML配置AOP

  整體結構:

1.匯入相關jar

  AOP 思想必須使用AspectJ語法,而AOP思想不是Spring框架設計出來的,而是叫一個AOP聯盟組織提出這種思想,所以需要匯入 AOP聯盟提供的 aspectjweaver.jar(aspectweaver織入包)

  2.建立配置檔案並引入宣告及名稱空間

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
    ">
    
</beans>

  3.編寫一個切面類

package com.gjs.util;

//模擬事務管理器
public class MyTransactionManager {
    public void begin() {
        System.out.println("開啟事務");
    }
    
    public void commit() {
        System.out.println("提交事務");
    }
    //Throwable e 引數名和 aop 異常增強的 after-throwing 的值相同
    public void rollback(Throwable e) {
        e.printStackTrace();
        System.out.println("回滾事務");
    }
    
    public void close() {
        System.out.println("關閉session");
    }
}

  4.新增AOP配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        ">
    
    <!-- 配置事務管理器 -->
    <bean id="manager" class="com.gjs.util.MyTransactionManager"/>
    
    <!-- 配置service實現類 -->
    <bean id="service" class="com.gjs.service.impl.UserServiceImpl"/>
    
    <!-- 
        AOP配置: W (where)W(when)W(what) 原則
            where:從哪裡切入
            when:什麼時候
            what:做什麼
     -->
     
     <!-- proxy-target-class:是否是基於類的代理 (CGLIB代理)。
         如果出現service類注入時注入了代理物件可以使用該屬性-->
     <aop:config proxy-target-class="true">
         <!-- 
             配置切入點(Pointcut):在哪些類,哪些方法上切入(where)
                 expression:切入點表示式 
                 id:切入點唯一標識
         -->
         <aop:pointcut expression="execution(* com.gjs.service..*.*(..))" id="pt"/>
         
         <!-- 配置切面 = 切入點(pointcut)+ 通知/增強(advice)
             ref : 引入需要增強的事務管理器
         -->
         <aop:aspect ref="manager">
             <!--
              when :什麼時候
                  之前(before)
                  之後(after-returning)
                  異常(after-throwing)
                  最終(after))
             -->
             <!-- what:做什麼(method) -->
             <!-- where:從哪裡切入(pointcut-ref) -->
             <aop:before method="begin" pointcut-ref="pt"/>
             <aop:after-returning method="commit" pointcut-ref="pt"/>
             <!-- 異常增強 
                 throwing : 異常,值 必須和對應方法名稱的 引數名稱一樣
                 丟擲的是頂級異常Throwable
                 -->
             <aop:after-throwing method="rollback" pointcut-ref="pt" throwing="e"/>
             <aop:after method="close" pointcut-ref="pt"/>
         </aop:aspect>
     </aop:config>
</beans>

 

  測試類

package com.gjs.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.gjs.pojo.User;
import com.gjs.service.impl.UserServiceImpl;

//表示先啟動Spring容器,把junit執行在Spring容器中
@RunWith(SpringJUnit4ClassRunner.class)
//表示從哪裡載入資原始檔,預設從src(源目錄)下面載入
@ContextConfiguration("classpath:applicationContext.xml")
public class TestSpring {
    // 注入代理類的物件
    @Autowired
    private UserServiceImpl service;

    @Test
    public void testInsert() throws Exception {
        User user = new User(null, "張三", 18);
        service.insert(user);
    }

    @Test
    public void testUpdate() throws Exception {
        User user = new User(1, "李四", 18);
        service.update(user);
    }

    @Test
    public void test1() throws Exception {
        service.toString();
    }
}

其他程式碼參考動態代理的示例程式碼

環繞增強(環繞通知方式)

切面類

package com.gjs.util;

import org.aspectj.lang.ProceedingJoinPoint;

//模擬事務管理器
public class MyTransactionManager {
    
public void allInOne(ProceedingJoinPoint pjo) {
        try {
            System.out.println("開啟事務");
            
            //執行具體的方法
            pjo.proceed();
            
            System.out.println("提交事務");
            
        } catch (Throwable e) {
            System.out.println("回滾事務");

        }finally {
            System.out.println("關閉session");
        }
        
    }
}

 配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        ">
    
    <!-- 配置事務管理器 -->
    <bean id="manager" class="com.gjs.util.MyTransactionManager"/>
    
    <!-- 配置service實現類 -->
    <bean id="service" class="com.gjs.service.impl.UserServiceImpl"/>
    
    <!-- 
        AOP配置: W (where)W(when)W(what) 原則
            where:從哪裡切入
            when:什麼時候
            what:做什麼
     -->
     
     <!-- proxy-target-class:是否是基於類的代理 (CGLIB代理)。
         如果出現service類注入時注入了代理物件可以使用該屬性-->
     <aop:config proxy-target-class="true">
         <!-- 
             配置切入點(Pointcut):在哪些類,哪些方法上切入(where)
                 expression:切入點表示式 
                 id:切入點唯一標識
         -->
         <aop:pointcut expression="execution(* com.gjs.service..*.*(..))" id="pt"/>
         
         <!-- 配置切面 = 切入點(pointcut)+ 通知/增強(advice)
             ref : 引入需要增強的事務管理器
         -->
         <aop:aspect ref="manager">
             <!-- 環繞增強:Spring事務管理就是使用環繞增強 -->
             <aop:around method="allInOne" pointcut-ref="pt"/>
         </aop:aspect>
     </aop:config>
</beans>

10.切入點表示式說明

execution(表示式)

  表示式語法:execution([修飾符] 返回值型別 包名.類名.方法名(引數))

寫法說明:(常用)

全匹配方式:
  public void com.gjs.service.impl.CustomerServiceImpl.saveCustomer()
訪問修飾符可以省略 :
  void com.zj.service.impl.CustomerServiceImpl.saveCustomer()
返回值可以使用*號,表示任意返回值:
  * com.zj.service.impl.CustomerServiceImpl.saveCustomer()
包名可以使用*號,表示任意包,但是有幾級包,需要寫幾個*:
  * *.*.*.*.CustomerServiceImpl.saveCustomer()
使用..來表示當前包,及其子包:
  * com..CustomerServiceImpl.saveCustomer()
類名可以使用*號,表示任意類:
  * com..*.saveCustomer()
方法名可以使用*號,表示任意方法:
  * com..*.*()
引數列表可以使用*,表示引數可以是任意資料型別,但是必須有引數:
  * com..*.*(*)
引數列表可以使用..表示有無引數均可,有引數可以是任意型別:
  * com..*.*(..)
全通配方式:
  * *..*.*(..)

11.常用標籤

11.1.<aop:config>

  作用:用於宣告開始aop的配置

11.2.<aop:aspect>

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

11.3.<aop:pointcut>

作用:用於配置切入點表示式
屬性:
expression:用於定義切入點表示式。
id:用於給切入點表示式提供一個唯一標識。

11.4.<aop:before>

作用:用於配置前置通知
屬性:
method:指定通知中方法的名稱。
pointct:定義切入點表示式
pointcut-ref:指定切入點表示式的引用

11.5.<aop:after-returning>

作用:用於配置後置通知,如果出了異常就一定不會呼叫切面的方法
屬性:
method:指定通知中方法的名稱。
pointct:定義切入點表示式
pointcut-ref:指定切入點表示式的引用

11.6.<aop:after-throwing>

作用:用於配置異常通知,只有出了異常才會呼叫切面對應的方法
屬性:
method:指定通知中方法的名稱。
pointct:定義切入點表示式
pointcut-ref:指定切入點表示式的引用

11.7.<aop:after>

作用: 用於配置最終通知,不管出不出異常,呼叫的切面的方法
屬性:
method:指定通知中方法的名稱。
pointct:定義切入點表示式
pointcut-ref:指定切入點表示式的引用

11.8.<aop:around>

作用:用於配置環繞通知
屬性:
method:指定通知中方法的名稱。
pointct:定義切入點表示式
pointcut-ref:指定切入點表示式的引用

 

12.基於註解配置AOP

  12.1 示例程式碼:

  切面類:

package com.gjs.util;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

//事務管理器
@Component 
@Aspect     //宣告此類為切面類, 等價於 <aop:aspect ref="manager">
public class MyTransactionManager {
    
    //設定切入點
    @Pointcut("execution(* com.gjs.service..*.*(..))")
    //相當於<aop:pointcut expression="execution(* com.gjs.service..*.*(..))" id="pt"/>
    public void pointcut() {}
    
    @Before("pointcut()")//相當於<aop:before method="begin" pointcut-ref="pt"/>
    public void begin() {
        System.out.println("開啟事務");
    }
    @After("pointcut()")//相當於<aop:after-returning method="commit" pointcut-ref="pt"/>
    public void commit() {
        System.out.println("提交事務");
    }
    @AfterThrowing(pointcut="pointcut()",throwing="e")
    // 相當於<aop:after-throwing method="rollback" pointcut-ref="pt" throwing="ex"/>
    public void rollback(Throwable e) {
        e.printStackTrace();
        System.out.println("回滾事務");
    }
    @AfterReturning("pointcut()")//相當於<aop:after method="close" pointcut-ref="pt"/>
    public void close() {
        System.out.println("關閉session");
    }
}

  配置檔案:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        ">
        
    <!-- 配置service實現類 -->
    <bean id="service" class="com.gjs.service.impl.UserServiceImpl"/>
    
    <!-- 配置註解元件掃描的包位置 -->
    <context:component-scan base-package="com.gjs"/>
    
    <!-- 開啟spring aop的註解配置 -->
    <!-- proxy-target-class="true",是否要建立基於類的代理(CGLIB),預設建立JDK動態代理,可能會出現service實現類注入不了的問題 -->
    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>

其他程式碼參考xml方式

  12.2環繞通知方式:(一般用這種方式)

  切面類:

package com.gjs.util;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

//模擬事務管理器
@Component
@Aspect // 宣告此類為切面類, 等價於 <aop:aspect ref="manager">
public class MyTransactionManager {
    // 設定切入點,相當於<aop:pointcut expression="execution(* com.gjs.service..*.*(..))" id="pt"/>
    @Pointcut("execution(* com.gjs.service..*.*(..))")
    public void pointcut() {
    }
    @Around("pointcut()")//相當於<aop:around method="allInOne" pointcut-ref="pt"/>
    public void allInOne(ProceedingJoinPoint pjo) {

        try {
            System.out.println("開啟事務");

            // 執行具體的方法
            pjo.proceed();

            System.out.println("提交事務");

        } catch (Throwable e) {
            System.out.println("回滾事務");

        } finally {
            System.out.println("關閉session");
        }

    }
}

配置檔案與上面相同

13.常用註解

13.1 @Aspect

  作用:把當前類宣告為切面類。

13.2 @Pointcut

作用:指定切入點表示式
屬性:
value:指定表示式的內容

13.3 @Before

作用:把當前方法看成是前置通知。
屬性:
value:用於指定切入點表示式,還可以指定切入點表示式的引用。

13.4 @After

作用:把當前方法看成是後置通知。報異常,就不執行
屬性:
value:用於指定切入點表示式,還可以指定切入點表示式的引用。

13.5 @AfterThrowing

作用:把當前方法看成是異常通知。只有報異常才執行
屬性:
value:用於指定切入點表示式,還可以指定切入點表示式的引用。

13.6 @AfterReturning

作用:把當前方法看成是最終通知。不管報不報異常都執行
屬性:
value:用於指定切入點表示式,還可以指定切入點表示式的引用。

13.7 @Around

作用:把當前方法看成是環繞通知。
屬性:
value:用於指定切入點表示式,還可以指定切入點表示式的引用。

注: @After和@AfterReturning 並沒有寫反,至於為什麼從命名和xml方式相反就不得而知了,可能是當時Spring的作者寫錯了,後面也沒有去改正

相關文章