Spring學習筆記2(IOC註解方式&AOP)

Jaybo發表於2019-03-02

前言

Spring框架的學習路線:

  1. Spring第一天:Spring的IOC容器之XML的方式,Spring框架與Web專案整合
  2. Spring第二天:Spring的IOC容器之註解的方式,Spring的AOP技術
  3. Spring第三天:Spring的事務管理、Spring框架的JDBC模板
  4. Spring第四天:SSH三大框架的整合

這是第二天學習大綱:

Spring學習筆記2(IOC註解方式&AOP)

一、Spring框架的IOC基於註解的方式

傳統的 SSH(Structs2、Spring、Hibernate)架構的專案基本都是使用的配置檔案方式,很少用註解的方式,但其實 Struts2 有註解,Hibernate 也有註解。如果是 SSM(SpringMVC、Spring、Mybatis)這套架構的專案有可能使用到註解方式,所以重點會在這套架構學習中講解註解方式,但 SSH 該架構也是用到了 Spring,所以還是會先講解註解方式。

先想一想註解出現的目的是幹嘛呢?其實註解最主要一個目的就是要替代傳統 XML 檔案方式,用註解會變得更簡單,因為傳統 XML 檔案需要寫很多很多配置,如果多了會變得很臃腫。

1.1 Spring框架的IOC之註解方式的快速入門

註解方式大概步驟如下:

1. 步驟一:匯入註解開發所有需要的jar包
    * 引入IOC容器必須的6個jar包
    * 多引入一個:Spring框架的AOP的jar包,spring-aop的jar包

2. 步驟二:建立對應的包結構,編寫Java的類
    * UserService           -- 介面
    * UserServiceImpl       -- 具體的實現類

3. 步驟三:在src的目錄下,建立applicationContext.xml的配置檔案,然後引入約束。注意:因為現在想使用註解的方式,那麼引入的約束髮生了變化
    * 需要引入context的約束,具體的約束如下
        <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"> <!-- bean definitions here -->

        </beans>

4. 步驟四:在applicationContext.xml配置檔案中開啟元件掃描
    * Spring的註解開發:元件掃描
        <context:component-scan base-package="com.itheima.demo1"/>

    * 注意:可以採用如下配置
        <context:component-scan base-package="com.itheima"/> 這樣是掃描com.itheima包下所有的內容

5. 步驟五:在UserServiceImpl的實現類上新增註解
    * @Component(value="userService")   -- 相當於在XML的配置方式中 <bean id="userService" class="...">

6. 步驟六:編寫測試程式碼
    public class SpringDemo1 {
        @Test
        public void run1(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService us = (UserService) ac.getBean("userService");
            us.save();
        }
    }
複製程式碼

注:在匯入如下 6 個包之外

Spring學習筆記2(IOC註解方式&AOP)

還需匯入 Spring 框架的 AOP 的 jar 包 spring-aop-4.2.4.RELEASE.jar

簡單例子: 這裡簡單用程式碼來演示下。

配置檔案applicationContext.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"> <!-- bean definitions here -->	
	<!-- 開啟註解的掃描 -->
	<context:component-scan base-package="com.itheima.demo1"/>
</beans>
複製程式碼

UserService.java:(業務層介面)

public interface UserService {
	public void sayHell();
}
複製程式碼

UserServiceImpl.java:(業務層實現類)

/**
 * 元件註解,標記類
 * <bean id="userService" class="com.itheima.demo1.UserServiceImpl"> 等價於 @Component(value="userService")
 * @author Administrator
 */
@Component(value="userService")
public class UserServiceImpl implements UserService {
    
    // 是Java的註解,Spring框架支援該註解
	@Resource(name="userDao")
	private UserDao userDao;
    
    public void sayHell() {
        System.out.println("hello Spring!!");
        userDao.save();
    }
}
複製程式碼

執行:

/**
	 * 原來的方式
	 */
@Test
public void run1(){
    UserService us = new UserServiceImpl();
    us.sayHell();
}

/**
* 註解的方式
 */
@Test
public void run2(){
    // 獲取工廠,載入配置檔案
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 獲取物件
    UserService us = (UserService) ac.getBean("userService");
    us.sayHell();
}
複製程式碼

執行結果:hello Spring!!

可以看出註解方式與之前方式效果沒什麼區別。

1.2 Spring框架中Bean管理的常用註解

以下是主要內容:

1. @Component:元件.(作用在類上)

2. Spring中提供@Component的三個衍生註解:(功能目前來講是一致的)
    * @Controller       -- 作用在WEB層
    * @Service          -- 作用在業務層
    * @Repository       -- 作用在持久層

    * 說明:這三個註解是為了讓標註類本身的用途清晰,Spring在後續版本會對其增強

3. 屬性注入的註解(說明:使用註解注入的方式,可以不用提供set方法)
    * 如果是注入的普通型別,可以使用value註解
        * @Value            -- 用於注入普通型別

    * 如果注入的是物件型別,使用如下註解
        * @Autowired        -- 預設按型別進行自動裝配
            * 如果想按名稱注入
            * @Qualifier    -- 強制使用名稱注入

    * @Resource             -- 相當於@Autowired和@Qualifier一起使用
        * 強調:Java提供的註解
        * 屬性使用name屬性
複製程式碼

繼續用程式碼演示,承上面程式碼例子,我們修改程式碼:

UserServiceImpl.java:

@Component(value="userService")  
// @Scope(value="prototype")
public class UserServiceImpl implements UserService {
    // 給name屬性注入美美的字串,setName方法還可以省略不寫
    @Value(value="美美")
    private String name;
    /*public void setName(String name) {
		this.name = name;
	}*/

    // @Autowired 按型別自動裝配
    // @Autowired
    // @Qualifier(value="userDao")		// 按名稱注入

    // 是Java的註解,Spring框架支援該註解
    @Resource(name="userDao")
    private UserDao userDao;

    public void sayHell() {
        System.out.println("hello Spring!!"+name);
        userDao.save();
    }
}
複製程式碼

幾點強調的:

  1. @Component(value="userService") 可更改為 @Service(value="userService")

  2. 使用 @Value(value="美美") 給 name 屬性注入美美的字串,setName 方法還可以省略不寫

  3. @Autowired 按型別自動裝配什麼意思呢?用程式碼解釋下:

    UserDao.java:

    public interface UserDao 
        public void save();
    }
    複製程式碼

    UserDaoImpl.java:

    /**
     * UserDaoImpl交給IOC的容器
     * @author Administrator
     */
    @Repository(value="userDao")
    public class UserDaoImpl implements UserDao {
        public void save() {
            System.out.println("儲存客戶...");
        }
    }
    複製程式碼

    若使用 @Autowired

    @Autowired
    private UserDao userDao;
    複製程式碼

    進行屬性注入,它會自動找 UserDao 介面的實現類物件,如下圖為 UserDaoImpl 類物件,再解釋下:

    Spring學習筆記2(IOC註解方式&AOP)

    首先 IOC 容器解析完,會有很多物件,比如有 UserDaoImpl、UserServiceImpl 物件(可以觀察程式碼,這兩個類有進行注入),其中 UserServiceImpl 有成員屬性 UserDao,@Autowired 屬性注入的意思就是它會去找容器的 UserDao 實現類物件,發現有一個實現類 UserDaoImpl 物件,然後預設就把這個注入進來了,這就是按型別自動裝配。但這種方式不好,因為如果 UserDao 有多個實現類,就不知道裝配哪個了(即注入不能成功,參考:當一個介面有多個實現類時,@Autowired會出問題嗎?)。(可以試著把@Repository(value="userDao") 的 value 值改為其他值,可以發現還是能執行 UserDaoImpl 物件的 sava 方法,因為 @Autowired 按型別自動裝配的。)

    一般按名稱來裝配,即通過給 ID 名稱注入。怎麼做的呢?比如:

    UserDaoImpl.java 改為:

    @Repository(value="ud")
    public class UserDaoImpl implements UserDao {
        public void save() {
            System.out.println("儲存客戶...");
        }
    }
    複製程式碼

    然後可以使用 @Qualifier 注入

    @Qualifie(value="ud") //按名稱 ud 注入
    private UserDao userDao;
    複製程式碼

    另外可以使用 Java 提供的一個註解,Spring 框架支援該註解。

    @Resource(name="userDao")
    private UserDao userDao;
    複製程式碼

    如下圖,可以看到所在包的不同

    Spring學習筆記2(IOC註解方式&AOP)

    網上相關資料:

1.3 Bean的作用範圍和生命週期的註解

1. Bean的作用範圍註解
    * 註解為@Scope(value="prototype"),作用在類上。值如下:
        * singleton     -- 單例,預設值
        * prototype     -- 多例

2. Bean的生命週期的配置(瞭解)
    * 註解如下:
        * @PostConstruct    -- 相當於init-method
        * @PreDestroy       -- 相當於destroy-method
複製程式碼

如 UserServiceImpl.java:(多例)

@Component(value="userService")
@Scope(value="prototype")
public class UserServiceImpl implements UserService {
 ....

     @PostConstruct
     public void init(){
     System.out.println("初始化...");
 }
}
複製程式碼

二、Spring框架整合JUnit單元測試

Spring 框架整合 JUnit 單元測試,什麼意思呢?我們可以先看下之前都是怎麼進行 JUnit 單元測試的,如下某個例子:

@Test
public void run2(){
    // 獲取工廠,載入配置檔案
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 獲取物件
    UserService us = (UserService) ac.getBean("userService");
    us.sayHell();
}
複製程式碼

可以看到每次測試我們都得手動 new 這麼個工廠,載入配置檔案,這樣很麻煩。為了方便我們測試,Spring 提供了整合 JUnit 單元測試方案。步驟如下:

1. 為了簡化了JUnit的測試,使用Spring框架也可以整合測試
2. 具體步驟
    * 要求:必須先有JUnit的環境(即已經匯入了JUnit4的開發環境)!!

    * 步驟一:在程式中引入:spring-test.jar
    * 步驟二:在具體的測試類上新增註解
        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration("classpath:applicationContext.xml")
        public class SpringDemo1 {

            @Resource(name="userService")
            private UserService userService;

            @Test
            public void demo2(){
                userService.save();
            }
        }
複製程式碼

程式碼演示:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo2 {
    
    @Resource(name="userService")
    private UserService UserService;

    @Test
    public void run1(){
        // 原來:獲取工廠,載入配置檔案,getBean()
        UserService.sayHell();	//直接呼叫就行
    }
}
複製程式碼

三、AOP的概述

在講什麼 AOP 之前,我們先看例子,如下圖:

Spring學習筆記2(IOC註解方式&AOP)

如上圖。在企業開發中我們可能需要寫很多很多 DAO 層程式碼,可能有的是儲存使用者資訊,有的是儲存書籍資訊,每個方法裡面都寫了很多程式碼,但假如有一天有新的需求,比如在儲存使用者資訊的時候記錄日誌,那不得不開啟 DAO 層原始碼去修改,如果儲存書籍資訊也得記錄日誌,那也得去修改 BookDao 程式碼。這樣的方式不是特別理想,萬一不小心的修改,但一上線就報錯了出問題了。

那有什麼其他的方式,在不改變原始碼的情況下,記錄日誌的功能加上?有的,AOP 可以。

另外,像上圖,還可以看到每個 DAO 層都有提交事務這個步驟,每次都得寫提交事務的程式碼,這樣其實很麻煩,AOP 可以解決該問題。

1、什麼是 AOP 的技術?

  • 在軟體業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計
  • AOP是一種程式設計正規化,隸屬於軟工範疇,指導開發者如何組織程式結構
  • AOP最早由AOP聯盟的組織提出的,制定了一套規範.Spring將AOP思想引入到框架中,必須遵守AOP聯盟的規範
  • 通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術
  • AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型
  • 利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率

2、AOP:面向切面程式設計(思想。—— 解決OOP遇到一些問題)

3、AOP採取橫向抽取機制,取代了傳統縱向繼承體系重複性程式碼(效能監視、事務管理、安全檢查、快取)

4、為什麼要學習AOP:可以在不修改原始碼的前提下,對程式進行增強!!

我們舉例來理解下 AOP。我們可以先了解下模組化手機,百度百科 模組化手機

Spring學習筆記2(IOC註解方式&AOP)

簡單講就是把手機分成好幾個不同模組,比如攝像頭模組、電池模組、螢幕模組。這樣可以明顯看到有很多好處,解耦合了,並且可以自定義,比如攝像頭換別的更好的攝像頭。

其實 AOP 也是這個思想,如圖:

Spring學習筆記2(IOC註解方式&AOP)

把「獲取session」、「開啟事務」等等看做一個個模組。其中有的模組可以直接拿來用就行,也可以自定義。我們可以把很多重複模組直接拿出來編寫就行,AOP 採取了橫向抽取機制。(這就是 AOP 的巨集觀思想)

四、AOP的底層實現原理

Srping 框架的 AOP 技術底層是採用的代理技術。如圖:

Spring學習筆記2(IOC註解方式&AOP)

解釋下,如上圖,代理物件可以控制目標物件的訪問,原來沒有代理物件,其他的請求可以直接訪問目標物件,讓目標物件程式碼執行,現在有了代理物件,其訪問先要經過代理物件,代理物件中可以寫任何程式碼,做很多功能,比如在目標物件執行之前執行記錄日誌。這樣的方式可以看到,我們並沒改變 save 方法,但仍能加上了想要的功能。AOP 底層就是這種思想。有兩種代理方式:

  • JDK 動態代理(針對有介面的方式)
  • CGLIB 技術,代理模式,採用生成類的子類的方式(針對沒有介面)
1. Srping框架的AOP技術底層也是採用的代理技術,代理的方式提供了兩種
    1. 基於JDK的動態代理
        * 必須是面向介面的,只有實現了具體介面的類才能生成代理物件

    2. 基於CGLIB動態代理
        * 對於沒有實現了介面的類,也可以產生代理,產生這個類的子類的方式

2. Spring的傳統AOP中根據類是否實現介面,來採用不同的代理方式
    1. 如果實現類介面,使用JDK動態代理完成AOP
    2. 如果沒有實現介面,採用CGLIB動態代理完成AOP
複製程式碼

① JDK 動態代理:(程式碼瞭解,理解原理)

UserDao.java:

public interface UserDao {
	public void save();	
	public void update();
}
複製程式碼

UserDaoImpl.java:

public class UserDaoImpl implements UserDao {
	public void save() {
		System.out.println("儲存使用者...");
	}	
	public void update() {
		System.out.println("修改使用者...");
	}
}
複製程式碼

MyProxyUtils.java:

/**
 * 使用JDK的方式生成代理物件
 * @author Administrator
 */
public class MyProxyUtils {
	public static UserDao getProxy(final UserDao dao) {
		// 使用Proxy類生成代理物件
		UserDao proxy = (UserDao) Proxy.newProxyInstance(dao.getClass().getClassLoader(),
				dao.getClass().getInterfaces(), new InvocationHandler() {
					
					// 代理物件方法一執行,invoke方法就會執行一次
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						if("save".equals(method.getName())){
							System.out.println("記錄日誌...");
							// 開啟事務
						}
						// 提交事務
						// 讓dao類的save或者update方法正常的執行下去
						return method.invoke(dao, args);
					}
				});
		
		// 返回代理物件
		return proxy;
	}
}
複製程式碼

測試:

@Test
public void run1(){
    // 目標物件
    UserDao dao = new UserDaoImpl();
    dao.save();
    dao.update();

    System.out.println("=============================");
    // 使用工具類,獲取到代理物件
    UserDao proxy = MyProxyUtils.getProxy(dao);
    // 呼叫代理物件的方法
    proxy.save();
    proxy.update();
}
複製程式碼

執行結果:

Spring學習筆記2(IOC註解方式&AOP)

② CGLIB的代理技術:(程式碼瞭解)

CGLIB 其實本身是一個單獨的專案,被 Spring 單獨拿過來了作為它內在的一種技術。

在使用之前,需要引入CGLIB 的開發的 jar 包,在 Spring 框架核心包中已經引入了 CGLIB 的開發包了,所以直接匯入 Spring 核心開發包 spring-core-4.2.4.RELEASE.jar 就匯入了 CGLIB 開發的 jar 包。

BookDaoImpl.java:(實現類)

public class BookDaoImpl {
    public void save(){
        System.out.println("儲存圖書...");
    }
    public void update(){
        System.out.println("修改圖書...");
    }
}
複製程式碼

MyCglibUtils.java:

public class MyCglibUtils {
    /**
	 * 使用CGLIB方式生成代理的物件
	 * @return
	 */
    public static BookDaoImpl getProxy() {
        Enhancer enhancer = new Enhancer();
        // 設定父類
        enhancer.setSuperclass(BookDaoImpl.class);
        // 設定回撥函式
        enhancer.setCallback(new MethodInterceptor() {
            // 代理物件的方法執行,回撥函式就會執行
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                if(method.getName().equals("save")){
                    System.out.println("記錄日誌...");
                }
                // 正常執行
                return methodProxy.invokeSuper(obj, args);
            }
        });
        // 生成代理物件
        BookDaoImpl proxy = (BookDaoImpl) enhancer.create();
        return proxy;
    }
}
複製程式碼

測試:

@Test
public void run1(){
    // 目標物件
    BookDaoImpl dao = new BookDaoImpl();
    dao.save();
    dao.update();
    System.out.println("============================");
    // 使用CGLIB方式生成代理物件
    BookDaoImpl proxy = MyCglibUtils.getProxy();
    proxy.save();
    proxy.update();
}
複製程式碼

執行結果:

Spring學習筆記2(IOC註解方式&AOP)

////////////////////////////////////////////////////////////

來了解下企業中正常編寫程式碼順序:

Spring學習筆記2(IOC註解方式&AOP)

如上,我們很多人寫程式,就是直接幹,從 JSP 頁面到 Action 層,到 Service 層 再到 Dao 層。

在企業中一般都會是這樣,先定義 service 介面,因為做一個系統,系統的業務是固定的,一般介面都是技術帶頭人定義的,然後在編寫 service 實現類,再 Dao 層介面,再 Dao 層實現類,然後寫完了進行測試,測試完成了再去寫頁面。一般大公司都是這個規範,如果是小公司可能就是直接幹,遇到問題再說。所以在大公司工作是很枯燥的,因為有可能你只負責某一塊。

五、AOP功能基於AspectJ的配置檔案方式

5.1 AOP的相關術語

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

下圖解釋下:

Spring學習筆記2(IOC註解方式&AOP)

5.2 AspectJ的XML方式完成AOP的開發

大概步驟:

1. 步驟一:建立JavaWEB專案,引入具體的開發的jar包
    * 先引入Spring框架開發的基本開發包
    * 再引入Spring框架的AOP的開發包
        * spring的傳統AOP的開發的包
            * spring-aop-4.2.4.RELEASE.jar
            * com.springsource.org.aopalliance-1.0.0.jar

        * aspectJ的開發包
            * com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
            * spring-aspects-4.2.4.RELEASE.jar

2. 步驟二:建立Spring的配置檔案,引入具體的AOP的schema約束
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

3. 步驟三:建立包結構,編寫具體的介面和實現類
    * com.itheima.demo2
        * CustomerDao           -- 介面
        * CustomerDaoImpl       -- 實現類

4. 步驟四:將目標類配置到Spring中
    <bean id="customerDao" class="com.itheima.demo3.CustomerDaoImpl"/>

5. 步驟五:定義切面類
    public class MyAspectXml {
        // 定義通知
        public void log(){
            System.out.println("記錄日誌...");
        }
    }

6. 步驟六:在配置檔案中定義切面類
    <bean id="myAspectXml" class="com.itheima.demo3.MyAspectXml"/>

7. 步驟七:在配置檔案中完成aop的配置
    <aop:config>
        <!-- 引入切面類 -->
        <aop:aspect ref="myAspectXml">
            <!-- 定義通知型別:切面類的方法和切入點的表示式 -->
            <aop:before method="log" pointcut="execution(public * com.itheima.demo3.CustomerDaoImpl.save(..))"/>
        </aop:aspect>
    </aop:config>

8. 完成測試
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class Demo3 {
        @Resource(name="customerDao")
        private CustomerDao customerDao;
        @Test
        public void run1(){
            customerDao.save();
            customerDao.update();
            customerDao.delete();
        }
    }
複製程式碼

CustomerDao.java:

public interface CustomerDao {
	public void save();
	public void update();
}
複製程式碼

CustomerDaoImpl.java:

public class CustomerDaoImpl implements CustomerDao {
	public void save() {
		// 模擬異常
		// int a = 10/0;	
		System.out.println("儲存客戶...");
	}
	public void update() {
		System.out.println("修改客戶...");
	}
}
複製程式碼

MyAspectXml.java:

/**
 * 切面類:切入點 + 通知
 * @author Administrator
 */
public class MyAspectXml {
	
	/**
	 * 通知(具體的增強)
	 */
	public void log(){
		System.out.println("記錄日誌...");
	}
	
	/**
	 * 最終通知:方法執行成功或者出現異常,都會執行
	 */
	public void after(){
		System.out.println("最終通知...");
	}
	
	/**
	 * 方法執行之後,執行後置通知。程式出現了異常,後置通知不會執行的。
	 */
	public void afterReturn(){
		System.out.println("後置通知...");
	}
	
	/**
	 * 環繞通知:方法執行之前和方法執行之後進行通知,預設的情況下,目標物件的方法不能執行的。需要手動讓目標物件的方法執行
	 */
	public void around(ProceedingJoinPoint joinPoint){
		System.out.println("環繞通知1...");
		try {
			// 手動讓目標物件的方法去執行
			joinPoint.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("環繞通知2...");
	}
}
複製程式碼

applicationContext.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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
	
	<!-- 配置客戶的dao -->
	<bean id="customerDao" class="com.itheima.demo3.CustomerDaoImpl"/>
	
	<!-- 編寫切面類配置好 -->
	<bean id="myAspectXml" class="com.itheima.demo3.MyAspectXml"/>
	
	<!-- 配置AOP -->
	<aop:config>
		<aop:aspect ref="myAspectXml">
			<!-- 切入點的表示式 
				1. execution()	固定的,不能不寫
				2. public 可以省略不寫
				3. void,返回值可以出現 * 表示任意的返回值,返回值型別不能不寫
				4. 可以使用 * 代替的,不能不編寫的,簡寫方式:*..*方法
				5. *DaoImpl
				6. 方法 save*
				7. 方法的引數:
			-->
			<!-- <aop:before method="log" pointcut="execution(public void com.itheima.demo3.CustomerDaoImpl.save())"/> -->
			<!-- public 可以省略不寫 -->
			<!-- <aop:before method="log" pointcut="execution(void com.itheima.demo3.CustomerDaoImpl.save())"/> -->
			
			<!-- void,返回值可以出現 * 表示任意的返回值,返回值型別不能不寫 -->
			<!-- <aop:before method="log" pointcut="execution(* com.itheima.demo3.CustomerDaoImpl.save())"/> -->
			
			<!-- 包名可以使用 * 代替,不能不寫 -->
			<!-- <aop:before method="log" pointcut="execution(* com.itheima.*.CustomerDaoImpl.save())"/> -->
			
			<!-- 包的簡寫的方式,任意的包的結構 -->
			<!-- <aop:before method="log" pointcut="execution(* *..*.CustomerDaoImpl.save())"/> -->
			
			<!-- 編寫類的寫法 -->
			<!-- <aop:before method="log" pointcut="execution(* *..*.*DaoImpl.save())"/> -->
			
			<!-- 方法編寫 -->
			<!-- <aop:before method="log" pointcut="execution(* *..*.*DaoImpl.save*())"/> -->
			
			<!-- 引數列表:出現一個*,表示一個引數,任意引數使用 .. -->
			<aop:before method="log" pointcut="execution(* *..*.*DaoImpl.save*(..))"/>
		</aop:aspect>
	</aop:config>

</beans>

複製程式碼

5.3 切入點的表示式

1. 再配置切入點的時候,需要定義表示式,重點的格式如下:execution(public * *(..)),具體展開如下:
    * 切入點表示式的格式如下:
        * execution([修飾符] 返回值型別 包名.類名.方法名(引數))

    * 修飾符可以省略不寫,不是必須要出現的。
    * 返回值型別是不能省略不寫的,根據你的方法來編寫返回值。可以使用 * 代替。
    * 包名例如:com.itheima.demo3.BookDaoImpl
        * 首先com是不能省略不寫的,但是可以使用 * 代替
        * 中間的包名可以使用 * 號代替
        * 如果想省略中間的包名可以使用 .. 

    * 類名也可以使用 * 號代替,也有類似的寫法:*DaoImpl
    * 方法也可以使用 * 號代替
    * 引數如果是一個引數可以使用 * 號代替,如果想代表任意引數使用 ..
複製程式碼

5.4 AOP的通知型別

1. 前置通知
    * 在目標類的方法執行之前執行。
    * 配置檔案資訊:<aop:after method="before" pointcut-ref="myPointcut3"/>
    * 應用:可以對方法的引數來做校驗

2. 最終通知
    * 在目標類的方法執行之後執行,如果程式出現了異常,最終通知也會執行。
    * 在配置檔案中編寫具體的配置:<aop:after method="after" pointcut-ref="myPointcut3"/>
    * 應用:例如像釋放資源

3. 後置通知
    * 方法正常執行後的通知。       
    * 在配置檔案中編寫具體的配置:<aop:after-returning method="afterReturning" pointcut-ref="myPointcut2"/>
    * 應用:可以修改方法的返回值

4. 異常丟擲通知
    * 在丟擲異常後通知
    * 在配置檔案中編寫具體的配置:<aop:after-throwing method="afterThorwing" pointcut-ref="myPointcut3"/> 
    * 應用:包裝異常的資訊

5. 環繞通知
    * 方法的執行前後執行。
    * 在配置檔案中編寫具體的配置:<aop:around method="around" pointcut-ref="myPointcut2"/>
    * 要注意:目標的方法預設不執行,需要使用ProceedingJoinPoint對來讓目標物件的方法執行。
複製程式碼

相關文章