上篇講述了[Spring的IOC](http://blog.csdn.net/lovesummerforever/article/details/22500339)[原理](http://blog.csdn.net/lovesummerforever/article/details/22500339)和[使用](http://blog.csdn.net/lovesummerforever/article/details/22646793),本篇講述Spring對AOP的支持。首先回顧一下Spring IOC容器,用一種通俗的方式理解Spring的IOC,也就是家里要安裝燈泡,去網上買,我們只需要去下訂單就(ApplicationContext.xml)可以了,無需關心工廠是如何加工的,你想要燈泡發紅的光就直接在選擇的時候選擇紅光,如果想要發黃色光的就直接選擇發黃色光的燈牌,之后生成訂單后會有派件人員直接派送到你的家門口,不需要你自己創建燈泡工廠去生產(new)燈泡。
### 那什么是Spring的AOP呢?
我們可以理解為你想要給燈安裝一個燈罩,可以直接把燈罩起來,而這個燈罩相對于燈本身來說沒有任何的關系,是獨立存在的,你只要加上去就可以。對于這個燈罩來說,就是從AOP的角度去分析了。
### 那究竟什么是Spring的IOC容器呢?
在軟件業,AOP是AspectOriented Programming的縮寫,意為:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。也可以意為面向行為編程,是函數式編程的一種衍生范型。
### 我們以一個例子來說明什么是AOP。
例如我們在用戶增刪改查上添加一個安全性檢查,這樣無論是任何操作之前都要進行安全性檢查。
UserManager.java接口代碼如下所示。
~~~
public interface UserManager {
public void addUser(String name,String password);
public void delUser(int userId);
public String findUserById(int userId);
public void modifyUser(int userId,String username,String password);
}
~~~
UserManagerImpl.java實現接口代碼如下所示。
~~~
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String name, String password) {
checkSecurity();
System.out.println("------------UserManagerImpl.add()----");
}
@Override
public void delUser(int userId) {
checkSecurity();
System.out.println("------------UserManagerImpl.del()----");
}
@Override
public String findUserById(int userId) {
checkSecurity();
System.out.println("------------UserManagerImpl.findUserById()----");
return "張三";
}
@Override
public void modifyUser(int userId, String username, String password) {
checkSecurity();
System.out.println("------------UserManagerImpl.ModifyUser()----");
}
//檢查安全性.
//當安全性不需要時就要成百上千給去改動,
//我們就可以用代理模式,讓代理去做,讓代理去控制目標,代理做某件事情.
private void checkSecurity()
{
System.out.println("------------UserManagerImpl.checkSecurity()----");
}
}
~~~
這樣的話如果我們想要更改的話就要在這個類上對安全性檢查進行更改,添加的話也需要修改這個類的代碼進行添加,這不符合我們設計原則,開閉原則,對擴展開放,對修改關閉。那怎么辦呢?我們從中可以發現,檢查安全性來說對增刪改查的操作沒有影響,是一項獨立存在的方法或獨立存在的服務,這樣我們可以把檢查安全性這以服務抽取出來放到UserManagerImpl類的代理類中,這樣去分開這個獨立的服務。讓代理去實現相同的接口,調用UserManagerImpl中的方法,把服務單獨的放到代理類中。這樣單獨的管理這項服務。
### 使用靜態代理解決問題
代理類代碼如下所示。UserManagerImplProxy.java,加上代理類之后我們就可以去掉UsermanagerImpl.java中的安全性檢查方法。
~~~
/**
* 代理類和目標做的事情是一樣的,所以實現相同的接口.
* @author Summer
*
*/
public class UserManagerImplProxy implements UserManager {
//目標引用.
private UserManager userManager;
//通過構造方法傳遞過來.
public UserManagerImplProxy(UserManager userManager)
{
this.userManager = userManager;
}
@Override
public void addUser(String username, String password) {
checkSecurity();
userManager.addUser(username, password);
}
@Override
public void delUser(int userId) {
// TODO Auto-generated method stub
checkSecurity();
userManager.delUser(userId);
}
@Override
public String findUserById(int userId) {
checkSecurity();
return userManager.findUserById(userId);
}
@Override
public void modifyUser(int userId, String username, String password) {
checkSecurity();
userManager.modifyUser(userId, username, password);
}
private void checkSecurity()
{
System.out.println("------------UserManagerImpl.checkSecurity()----");
}
}
~~~
但問題是要去修改的這個安全性檢查的方法的話,就要去修改代理類了,如果很多方法,就要修改很多次,會引來同樣的開閉問題。
我們應該剝離出來,讓這個方法在運行的時候動態的加入進去,方法只寫一次,從AOP的角度來說,這種遍布在系統中的獨立的服務,稱為橫切性的關注點。縱向的是方法一調用方法二,方法二調用方法三,而這個服務是切入到各個方法上的,所以認為是橫向的。我們可以使用動態代理來解決這個問題。
### 使用動態代理解決問題
動態代理,我們要實現一個系統的處理器InvocationHandler,它可以復用,不需要每個方法一個代理,并且是在運行時聲明出來的,可以為各個接口服務。把橫切性的問題拿出來放到一個單獨的類中,上述例子中,我們刪除我們自己手動寫的代理類,新建SecurityHandler類,實現InvocationHandler這個接口。
代碼如下所示。
~~~
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class SecurityHandler implements InvocationHandler {
private Object targetObject;
public Object createProxyInstance(Object targetobject)
{
this.targetObject = targetobject;
//根據目標生成代理.
//代理是對接口來說的,是拿到目標的接口.拿到這個接口,實現這個接口.
//targetobject這個目標類實現了哪個接口.
//返回代理.
return Proxy.newProxyInstance(targetobject.getClass().getClassLoader(),
targetobject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//檢查安全性.
checkSecurity();
//當前方法,調用目標對象的方法.
//調用目標方法.
Object ret = method.invoke(targetObject, args);
return ret;
}
//檢查安全性.
private void checkSecurity()
{
System.out.println("------------UserManagerImpl.checkSecurity()----");
}
}
~~~
這個代理類是運行時創建的,是動態的,而編譯時創建的是靜態的。TargetObject目標,建立目標實例createProxyInstance(),根據目標生成代理Proxy.newProxyInstance(),在invoke()方法中設置允不允許調用真正的對象,在invoke()中進行檢查安全性控制,在invoke()中統一做控制,就像Filter中的doFilter方法。
客戶端調用,代碼如下所示。
~~~
<pre name="code" class="java">public class Client {
/**
* @param args
*/
public static void main(String[] args) {
InvocationHandler hander = new SecurityHandler();
//返回的是一個代理類,不是真正的UserManager.而是指向代理類.在內存中.
UserManager userManager = (UserManager)hander.createProxyInstance(new UserManagerImpl());
//使用的是代理.
//共同的接口,不同的實現.
userManager.addUser("張三", "123");
}
}
~~~
~~~
//返回的是一個代理類,不是真正的UserManager.而是指向代理類.在內存中.UserManager userManager = (UserManager)hander.createProxyInstance(new UserManagerImpl());//使用的是代理.//共同的接口,不同的實現.userManager.addUser("張三", "123");}}
~~~
接下來讓我們走入Spring的AOP,他是如何實現的呢?如下圖所示。

### AOP中的基本概念和術語。
### 1、切面(Aspect)
切面就是一個抽象出來的功能模塊的實現,例如上述安全性檢查,可以把這個功能從系統中抽象出來,用一個獨立的模塊描述,這個獨立的模塊就是切面,我們的SecutiryHinder類。
### 2、連接點(JoinPoint)
連接點即程序運行過程中的某個階段點,可以是方法調用、異常拋出等,在這個階段點可以引入切面,從而給系統增加新的服務功能。
### 3、通知(Advice)
通知即在某個連接點所采用的處理邏輯,即切面功能的實際實現。通知的類型包括Before、After、Around以及Throw四種,下篇文章將會展示通知的使用方法。
### 4、切入點(PointCut)
接入點即一系列連接點的集合,它指明通知將在何時被觸發。可以指定能類名,也可以是匹配類名和方法名的正則表達式。當匹配的類或者方法被調用的時候,就會在這個匹配的地方應用通知。
### 5、目標對象(Target)
目標對象就是被通知的對象,在AOP中,目標對象可以專心實現自身的業務邏輯,通知功能可以在程序運行期間自動引入。
### 6、代理(Proxy)
代理是目標對象中使用通知以后創建的對象,這個對象既擁有目標對象的全部功能,而且擁有通知提供的附加功能。
理解上述術語還是先用比較通俗的方式來解釋,對于切面,我們在中學中學習過,切線,切面,切點,對于數學中學習的我們一定都不陌生。

如果仍然還是抽象的話,我們知道嫦娥一號與月球對接,嫦娥一號所要完成的任務或功能就是我們的切面(Aspect),帶探究月球生命的使命;連接點(JoinPoint)就是總臺發出命令,來觸發嫦娥一號升空,這個發出的命令觸發點就是JoinPoint;通知(Advice)就是具體的去執行總臺的命令,包括集中類型是在總臺發出命令之前(Before)、之后(After)、Around(大約多長時間)、或系統出現錯誤(Throw),去執行具體的操作。切入點(PointCut)就是總臺發出取出月球特殊植物,嫦娥一號進行匹配特殊的植物,并通知是否找到成功或失敗。那嫦娥一號這個對象本身就是我們的代理(Proxy),而真正的目標對象是總臺(大概理解一下,不到之處,望原諒)。
我們這里的橫切性關注點(Coreconcerns)是指安全性服務,對于安全性的檢查可能分不到各個模塊中。而切面(Aspect)是一個關注點的模塊化,從軟件角度來說是指應用程序在不同模塊的某一個領域或方面。連接點(Joinpoint)程序執行過程中某個特殊的點,比如方法調用或處理異常的時候,上述我們再調用增刪改查方法的時候。切入點(Pointcuts)是連接點的集合。通知(Advice)是實際的邏輯實現,即實際實現安全服務的那個方法。
通知有四種類型,前置通知(Beforeadvice):在切入點匹配方法之前使用。返回后通知(Afterreturning advice):在切入點匹配方法返回的時候執行。拋出后通知(Afterthrowing advice):在切入點匹配的方法執行時拋出異常的時候運行。后通知(After(finally)advice):無論切入點匹配方法是否正常結束,還是拋出異常結束的,在它結束(finally)后通知(afteradvice)。環繞通知(AroundAdvice):環繞通知既在切入點匹配的方法執行之前又再執行之后運行。并且可以決定這個方法在什么時候執行,如何執行,設置是是否執行。
晦澀難懂的東西還是用實例來說明,見下一篇SpringAOP實例。
- 前言
- Struts旅程(一)Struts簡介和原理
- struts旅程(二)Struts登錄示例
- Struts旅程(三)Struts表單處理器ActionForm(靜態動態)
- Struts旅程(四)MVC向struts MVC框架演變過程
- Struts旅程(五)struts控制器DispatchAction
- Struts旅程(六)Struts頁面轉發控制ActionForward和ActionMapping
- Hibernate旅程(一)Hibernate架構概述
- Hibernate旅程(二)Hibernate實例
- Hibernate旅程(三)Hibernate持久化對象的三個狀態
- Hibernate旅程(四)Hibernate對數據庫刪除、查找、更新操作
- Hibernate旅程(五)Hibernate映射--基本類映射和對象關系映射
- Hibernate旅程(六)Hibernate映射--繼承映射
- Hibernate旅程(七)Hibernate緩存機制--一級緩存
- Hibernate旅程(八)Hibernate緩存機制--二級緩存
- Hibernate旅程(九)Hibernate緩存機制--查詢緩存
- Spring旅程(一)為什么使用Spring
- Spring旅程(二)非Spring向Spring過渡-- Spring IOC容器的使用
- Spring旅程(三) AOP--Spring AOP容器基礎
- Spring旅程(四) AOP--Spring AOP實例
- SSH旅程(五)Spring運用到Hibernate中
- SSH旅程(六)Spring和struts結合(方案一)