Spring詳解(五)------AOP

  這章我們接着講 Spring 的核心概念---AOP,這也是 Spring 框架中最爲核心的一個概念。

  PS:本篇博客源碼下載鏈接:http://pan.baidu.com/s/1skZjg7r 密碼:dn42

1、AOP 什麼?

  AOP(Aspect Oriented Programming),通常稱爲面向切面編程。它利用一種稱爲"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性。

  什麼是切面,什麼是公共模塊,那麼我們概念少說,直接通過一個實例來看看 AOP 到底是什麼。

 

2、需求

  現在有一張表 User,然後我們要在程序中實現對 User 表的增加和刪除操作。

  要求:增加和刪除操作都必須要開啓事務,操作完成之後要提交事務。

  User.java

package com.ys.aop.one;

public class User {
	private int uid;
	private String uname;
	public int getUid() {
		return uid;
	}
	public void setUid(int uid) {
		this.uid = uid;
	}
	public String getUname() {
		return uname;
	}
	public void setUname(String uname) {
		this.uname = uname;
	}

}

  

3、解決辦法1:使用靜態代理

  第一步:創建 UserService 接口

package com.ys.aop.one;

public interface UserService {
	//添加 user
	public void addUser(User user);
	//刪除 user
	public void deleteUser(int uid);
}

  第二步:創建 UserService的實現類

package com.ys.aop.one;

public class UserServiceImpl implements UserService{
	@Override
	public void addUser(User user) {
		System.out.println("增加 User");
	}
	@Override
	public void deleteUser(int uid) {
		System.out.println("刪除 User");
	}
}

  第三步:創建事務類 MyTransaction

package com.ys.aop.one;

public class MyTransaction {
	//開啓事務
	public void before(){
		System.out.println("開啓事務");
	}
	//提交事務
	public void after(){
		System.out.println("提交事務");
	}
}

  

  第四步:創建代理類 ProxyUser.java

package com.ys.aop.one;

public class ProxyUser implements UserService{
	//真實類
	private UserService userService;
	//事務類
	private MyTransaction transaction;
	//使用構造函數實例化
	public ProxyUser(UserService userService,MyTransaction transaction){
		this.userService = userService;
		this.transaction = transaction;
	}
	@Override
	public void addUser(User user) {
		transaction.before();
		userService.addUser(user);
		transaction.after();
	}
	@Override
	public void deleteUser(int uid) {
		transaction.before();
		userService.deleteUser(uid);
		transaction.after();		
	}
}

  

  測試:

@Test
	public void testOne(){
		MyTransaction transaction = new MyTransaction();
		UserService userService = new UserServiceImpl();
		//產生靜態代理對象
		ProxyUser proxy = new ProxyUser(userService, transaction);
		proxy.addUser(null);
		proxy.deleteUser(0);
	}

  結果:

  

  這是一個很基礎的靜態代理,業務類UserServiceImpl 只需要關注業務邏輯本身,保證了業務的重用性,這也是代理類的優點,沒什麼好說的。我們主要說說這樣寫的缺點:

  ①、代理對象的一個接口只服務於一種類型的對象,如果要代理的方法很多,勢必要爲每一種方法都進行代理,靜態代理在程序規模稍大時就無法勝任了。

  ②、如果接口增加一個方法,比如 UserService 增加修改 updateUser()方法,則除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的複雜度。

 

 

4、解決辦法2:使用JDK動態代理 

   動態代理就不要自己手動生成代理類了,我們去掉 ProxyUser.java 類,增加一個 ObjectInterceptor.java 類

package com.ys.aop.two;

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

import com.ys.aop.one.MyTransaction;

public class ObjectInterceptor implements InvocationHandler{
	//目標類
	private Object target;
	//切面類(這裏指事務類)
	private MyTransaction transaction;
	
	//通過構造器賦值
	public ObjectInterceptor(Object target,MyTransaction transaction){
		this.target = target;
		this.transaction = transaction;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		//開啓事務
		this.transaction.before();
		//調用目標類方法
		method.invoke(this.target, args);
		//提交事務
		this.transaction.after();
		return null;
	}
	
}

  測試:

@Test
	public void testOne(){
		//目標類
		Object target = new UserServiceImpl();
		//事務類
		MyTransaction transaction = new MyTransaction();
		ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction);
		/**
		 * 三個參數的含義:
		 * 1、目標類的類加載器
		 * 2、目標類所有實現的接口
		 * 3、攔截器
		 */
		UserService userService = (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), proxyObject);
		userService.addUser(null);
	}

  結果:

  

  那麼使用動態代理來完成這個需求就很好了,後期在 UserService 中增加業務方法,都不用更改代碼就能自動給我們生成代理對象。而且將 UserService 換成別的類也是可以的。

  也就是做到了代理對象能夠代理多個目標類,多個目標方法。

  注意:我們這裏使用的是 JDK 動態代理,要求是必須要實現接口。與之對應的另外一種動態代理實現模式 Cglib,則不需要,我們這裏就不講解 cglib 的實現方式了。

  不管是哪種方式實現動態代理。本章的主角:AOP 實現原理也是動態代理

   

 5、AOP 關鍵術語 

  1.target:目標類,需要被代理的類。例如:UserService

  2.Joinpoint(連接點):所謂連接點是指那些可能被攔截到的方法。例如:所有的方法

  3.PointCut 切入點:已經被增強的連接點。例如:addUser()

  4.advice 通知/增強,增強代碼。例如:after、before

  5. Weaving(織入):是指把增強advice應用到目標對象target來創建新的代理對象proxy的過程.

  6.proxy 代理類:通知+切入點

  7. Aspect(切面): 是切入點pointcut和通知advice的結合

  具體可以根據下面這張圖來理解:

  

 

6、AOP 的通知類型  

  Spring按照通知Advice在目標類方法的連接點位置,可以分爲5

  • 前置通知 org.springframework.aop.MethodBeforeAdvice
    • 在目標方法執行前實施增強,比如上面例子的 before()方法
  • 後置通知 org.springframework.aop.AfterReturningAdvice
    • 在目標方法執行後實施增強,比如上面例子的 after()方法
  • 環繞通知 org.aopalliance.intercept.MethodInterceptor
    • 在目標方法執行前後實施增強
  • 異常拋出通知 org.springframework.aop.ThrowsAdvice
    • 在方法拋出異常後實施增強
  • 引介通知 org.springframework.aop.IntroductionInterceptor

      在目標類中添加一些新的方法和屬性

 

7、使用 Spring AOP 解決上面的需求

  我們只需要在Spring 的配置文件 applicationContext.xml 進行如下配置:

<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">
	<!--1、 創建目標類 -->
	<bean  class="com.ys.aop.UserServiceImpl"></bean>   
	<!--2、創建切面類(通知)  --> 
	<bean  class="com.ys.aop.one.MyTransaction"></bean>
	
	<!--3、aop編程  
		3.1 導入命名空間
		3.2 使用 <aop:config>進行配置
				proxy-target-class="true" 聲明時使用cglib代理
				如果不聲明,Spring 會自動選擇cglib代理還是JDK動態代理
			<aop:pointcut> 切入點 ,從目標對象獲得具體方法
			<aop:advisor> 特殊的切面,只有一個通知 和 一個切入點
				advice-ref 通知引用
				pointcut-ref 切入點引用
		3.3 切入點表達式
			execution(* com.ys.aop.*.*(..))
			選擇方法         返回值任意   包             類名任意   方法名任意   參數任意
	
	-->
	<aop:config>
		<!-- 切入點表達式 -->
		<aop:pointcut expression="execution(* com.ys.aop.*.*(..))" />
		<aop:aspect ref="transaction">
			<!-- 配置前置通知,注意 method 的值要和 對應切面的類方法名稱相同 -->
			<aop:before method="before" pointcut-ref="myPointCut"></aop:before>
            <aop:after-returning method="after" pointcut-ref="myPointCut"/>
		</aop:aspect>
	</aop:config>
</beans>

  測試:

@Test
	public void testAop(){
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		UserService useService = (UserService) context.getBean("userService");
		useService.addUser(null);
	}

  結果:

  

  上面的配置我們在註釋中寫的很清楚了。這裏我們重點講解一下:

  ①、 切入點表達式,一個完整的方法表示如下:

execution(modifiers-pattern? ref-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
             類修飾符           返回值           方法所在的包                  方法名                     方法拋出的異常

  那麼根據上面的對比,我們就很好理解:

execution(* com.ys.aop.*.*(..))
選擇方法         返回值任意   包             類名任意   方法名任意   參數任意

  ②、springAOP 的具體加載步驟:

  1、當 spring 容器啓動的時候,加載了 spring 的配置文件

  2、爲配置文件中的所有 bean 創建對象

  3、spring 容器會解析 aop:config 的配置

       1、解析切入點表達式,用切入點表達式和納入 spring 容器中的 bean 做匹配

           如果匹配成功,則會爲該 bean 創建代理對象,代理對象的方法=目標方法+通知

           如果匹配不成功,不會創建代理對象

  4、在客戶端利用 context.getBean() 獲取對象時,如果該對象有代理對象,則返回代理對象;如果沒有,則返回目標對象

    說明:如果目標類沒有實現接口,則 spring 容器會採用 cglib 的方式產生代理對象,如果實現了接口,則會採用 jdk 的方式

  

 

發佈了0 篇原創文章 · 獲贊 7 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章