Spring(2)之 (AOP 面向切面程式設計)(AOP目錄)
Spring Core:
Spring的核心功能即IOC容器,解決物件的建立及物件之間的依賴關係
Spring WEB:
Spring對WEB模組的支援
Spring AOP: 面向切面程式設計
- AOP的概述
- AOP的底層實現
- Spring AOP開發
(網址: https://blog.csdn.net/qq_41029923/article/details/84230164 ) - 使用 AspectJ實現 AOP
(網址: https://blog.csdn.net/qq_41029923/article/details/84250237 ) - Spring JDBC Template的使用
(網址: https://blog.csdn.net/qq_41029923/article/details/84291336 )
1. AOP程式設計基本概念:
AOP: Aspect Object Programming 面向切面程式設計
功能:讓關注點程式碼與業務程式碼分離
關注點: 重複的程式碼(方法)
切面: 關注點形成的類
面向切面程式設計就是指對很多功能重複的程式碼的抽象,再在執行時向業務方法中動態的植入“切面類程式碼”;
切入點: 通過切入點表示式,指定攔截哪些類的哪些方法,給指定的類在執行時植入切面類程式碼
2. 代理模式:
代理模式是一種設計模式,提供了對目標物件的另外的訪問方式即通過代理物件訪問目標物件;
好處: 可以在目標物件實現的基礎上(即不改變目標物件的程式碼),增加額外的功能——擴充套件目標物件的功能
- 靜態代理
eg:儲存資料到資料庫,Dao直接儲存; 新增事務;代理物件要實現與目標物件一樣的介面;
優點: 可以做到在不修改目標物件功能的前提下(即目標物件程式碼不做更改),對目標物件進行擴充套件;
缺點: 代理物件要實現與目標物件一樣的介面,會有很多代理類,一旦介面增加方法,目標物件和代理物件都需要維護(即因為兩者都實現了此介面,所以程式碼都要做更改)
解決方式: 使用代理工廠、動態代理; - 動態代理(也叫JDK代理、介面代理)
目標物件一定要實現介面, 否則不能使用動態代理;
代理物件不需要實現介面;
代理物件的生成使用到 JDK API,動態的在記憶體中建立物件 (即 class $Proxy0 implements XXX);
API中:
Proxy
public static Object newProxyInstance(
ClassLoader loader, //指定當前目標物件的使用類的載入器
Class<?> [] interfaces,//目標物件實現的介面型別
InvocationHandler h //事件處理器
) - Cglib代理(子類代理)
子類代理,在記憶體中構建一個子類物件從而實現對目標物件的擴充套件,Cglib代理許多AOP框架使用;
步驟:
應用 .jar檔案—Spring Core包中包含;
在記憶體中動態建立目標物件的子類;
目標物件不能是 final;
目標物件方法時 final、static,則不會攔截;
在 Spring AOP 程式設計中:
動態代理:目標物件要實現介面,代理物件不需要實現介面(直接在代理物件中新增程式碼);
Cglib代理:目標物件不需要實現介面(代理物件實現了 MethodInterceptor 介面),(直接在代理物件中新增程式碼);
如果加入容器中的目標物件實現了介面,會自動使用JDK代理;
如果目標物件 沒有 實現介面(且目標物件不是 final),會自動使用 Cglib代理;
eg:1. 靜態代理
(① 介面IUserDao.java中有一個方法; ② UserDao.java是目標物件:實現IUserDao介面,實現裡面的方法;想新增事務,若直接在目標物件中新增, 這樣不好,所以要新增一個代理物件UserDaoProxy; ③ UserDaoProxy.java是代理物件,要實現與目標物件 UserDao一樣的介面 IUserDao。代理物件中新增目標物件,新增介面,因為目標物件實現了介面,這樣可以通過新增介面來新增實現類,來呼叫目標物件中的方法,在呼叫方法前後新增事務即可;)
1.1 IUserDao.java(介面)
public interface IUserDao{
public void save() throws Exception;
}
1.2 UserDao.java(目標物件)
(UserDao.java中實現IUserDao介面,實現裡面的方法;想新增事務,若直接在目標物件中新增, 這樣不好)
public class UserDao implements IUserDao{
public void save() throws Exception{
//System.out.println("----開始事務----");
System.out.println("----資料已儲存----");
//System.out.println("----提交事務----");
}
}
1.3 UserDaoProxy.java(代理物件_靜態代理)
(代理物件(UserDaoProxy)要實現與目標物件(UserDao)一樣的介面(IUserDao)。代理物件中新增目標物件,新增介面,因為目標物件實現了介面,這樣可以呼叫目標物件中的方法,在呼叫方法前後新增事務即可;)
public class UserDaoProxy implements IUserDao{
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save() throws Exception{
System.out.println("----開始事務----");
target.save();
System.out.println("----提交事務----");
}
}
1.4 Test.java
(通過介面 IUserDao 生成一個目標物件 target,生成一個代理物件 proxy將 target傳入,代理物件呼叫方法)
public class Test{
public static void main(String[] args) {
IUserDao target=new UserDao();//目標物件
UserDaoProxy proxy=new UserDaoProxy(target);//代理物件
try{
proxy.save();
}catch(Exception e){
e.printStackTrace();
}
}
}
執行結果:
eg:2. 動態代理
(若介面中增加方法,目標物件實現了介面所以也要增加方法,但代理物件中不需要再增加方法;直接在測試類中呼叫增加的方法即可)
2.1 IUserDao.java(介面)
public interface IUserDao{
public void save() throws Exception;
public void update() throws Exception;
}
2.2 UserDao.java(目標物件)
(UserDao.java中實現IUserDao介面,實現裡面的方法;想新增事務,若直接在目標物件中新增, 這樣不好)
public class UserDao implements IUserDao{
public void save() throws Exception{
//System.out.println("----開始事務----");
System.out.println("----資料已儲存----");
//System.out.println("----提交事務----");
}
public void update() throws Exception{
System.out.println("----資料已修改----");
}
}
2.3 ProxyFactory.java
(代理工廠 ProxyFactory中有目標物件,private IUserDao target,因為目標物件所實現的介面不止有IUserDao,IUserDao換成Object會更好;目標物件要實現介面,代理物件不需要實現介面(直接在代理物件中新增程式碼));
public class ProxyFactory{
private Object target;//目標物件
public ProxyFactory(Object target){
this.target=target;
}
public Object getProxyInstance(){//生成代理物件
Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler(){
public Object invoke(Object proxy,Method method,Object[] args) throws Exception{
System.out.println("開始事務");
Object result=method.invoke(target,args);//呼叫目標物件方法
System.out.println("提交事務");
return result;
}
}
);
}
}
2.4 Test.java
public class Test{
public static void main(String[] args) {
//目標物件
UserDao target=new UserDao();
//代理物件
IUserDao proxy=(IUserDao)new ProxyFactory(target).getProxyInstance();
//執行
try{
proxy.save();
proxy.update();
}catch(Exception e){
e.printStackTrace();
}
System.out.println(proxy.getClass());
}
}
執行結果:
介面中新增方法後執行結果:
eg:3. Cglib 代理
3.1 IUserDao.java(介面)
public interface IUserDao{
public void save() throws Exception;
public void update() throws Exception;
}
3.2 UserDao.java(目標物件)
(UserDao.java不需要實現IUserDao介面)
public class UserDao{ //不能final(public final class UserDao)
public void save() throws Exception{ //不能是final、static(public final、static void save())
System.out.println("----資料已儲存----");
}
public void update() throws Exception{
System.out.println("----資料已修改----");
}
}
3.3 ProxyFactory.java
(代理工廠 ProxyFactory中有目標物件,private IUserDao target,因為目標物件所實現的介面不止有IUserDao,IUserDao換成Object會更好;)
/*Cglib子類代理工廠(對UserDao在記憶體中建立一個代理)*/
public class ProxyFactory implements MethodInterceptor{
private Object target;//目標物件
public ProxyFactory(Object target){
this.target=target;
}
//代理物件
public Object getProxyInstance(){
//1.工具類
Enhancer en=new Enhancer();
//2.設定父類
en.setSuperclass(target.getClass());
//3.設定回撥方法(呼叫下方的intercept方法)
en.setCallback(this);
//4.建立代理物件
return en.create();
}
public Object intercept(Object arg0,Method arg1,Object[] arg2,MethodProxy arg3) throws Throwable{
System.out.println("開啟事務------");
//執行目標物件的方法
Object resultValue=method.invoke(target,arg2);
System.out.println("結束事務");
return resultValue;
}
}
3.4 Test.java
public class Test{
public static void main(String[] args) {
//目標物件
UserDao target=new UserDao();
//代理物件
IUserDao proxy=(IUserDao)new ProxyFactory(target).getProxyInstance();
//執行
try{
proxy.save();
}catch(Exception e){
e.printStackTrace();
}
System.out.println(proxy.getClass());
}
}
執行結果:
目標物件 UserDao 是final的執行結果:
(目標物件不能是 final)
會報錯
目標物件中的方法 save 是final、static的執行結果:
(目標物件的方法如果是 final、static,則不會攔截(沒有“開始事務、結束事務”))