AOP

         如果說面向對象編程是關注將需求功能劃分爲不同的並且相對獨立,封裝良好的類,並讓它們有着屬於自己的行爲,依靠繼承和多態等來定義彼此的關係的話;那麼面向切面編程則是希望能夠將通用需求功能從不相關的類當中分離出來,能夠使得很多類共享一個行爲,一旦發生變化,不必修改很多類,而只需要修改這個行爲即可。

JDK的動態代理是利用JAVA動態代理:使用java.lang.reflect包中的Proxy類與InvocationHandler接口完成的;

API中Proxy和InvocationHandler的使用和原理解析:http://rejoy.iteye.com/blog/1627405


代理模式

爲其他對象提供一種代理以控制對這個對象的訪問。代理對象在客戶端和目標對象之間起到中介的作用。


總結:
靜態代理在使用時,需要定義接口或是父類,被代理對象與代理對象一起實現相同的接口或者是繼承相同父類。

靜態代理存在一個問題:當我們在被代理的類中增加了一個方法,代理類中也要增加相應方法。

JDK動態代理

// 做日誌的JDK動態代理對象
public class LogJdkProxy implements InvocationHandler {
    private Object target; // 代理的目標對象
    public LogJdkProxy(Object target) {
       this.target = target;
    }
    /** 創建代理對象 */
    public Object createProxyInstance() {
// 第1個參數設置代碼使用的類裝載器,一般採用跟目標類相同的類裝載器
       // 第2個參數設置代理類實現的接口
       // 第3個參數設置回調對象,當代理對象的方法被調用時,會調用該參數指定對象的invoke方法
       return Proxy.newProxyInstance(//
              getClass().getClassLoader(), // 第1個參數
              target.getClass().getInterfaces(), // 第2個參數
              this); // 第3個參數
    }
    /**
     * @param proxy  目標對象的代理類實例
     * @param method 對應於在代理實例上調用接口方法的Method實例
     * @param args   傳入到代理實例上方法參數值的對象數組
     * @return 方法的返回值,沒有返回值(void)時是null
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       System.out.println("== Log:開始執行操作,方法名:"+method.getName()+" ==");
       Object result = method.invoke(target, args); // 執行原方法,把方法調用委派給目標對象
       System.out.println("== Log:操作執行完畢 ==");
       return result;
    }
}
JDK動態代理總結:
a) JAVA動態代理是使用java.lang.reflect包中的Proxy類與InvocationHandler接口完成的;
b) 要使用JDK動態代理,必須要定義接口(因爲動態代理生成的代理類繼承了Proxy類,java的單繼承模式和代理類和被代理類必須實現同一接口的規定,所以必須定義接口);
c) JDK動態代理將會攔截所有public的方法(因爲只能調用接口中定義的方法,在代理類和被代理類共有的接口中定義相應的被代理的函數),這樣即使在接口中增加了新的方法,不用修改代碼也會被攔截;
d) 如果只想攔截一部分方法,可以在invoke方法中對要執行的方法名進行判斷。

JDK的動態代理有一個限制,就是使用動態代理的對象必須實現一個或多個接口。如果想代理沒有實現接口的類,就可以使用CGLIB實現。

因爲代理類和被代理類必須實現相同的接口,代理類本身繼承了Proxy類,所以必須使用接口。

原對象
// 此類不能是final的,否則不能有子類,CGLIB也就不能工作了
public class UserServiceImpl {
    // 這是final的方法,不能被子類重寫,所以CGLIB不會攔截這個方法
    public final void foo1() {
       System.out.println(">> final的方法 <<");
    }
    // 這是static的方法,CGLIB不會攔截這個方法
    public static void foo2() {
       System.out.println(">> static的方法 <<");
    }
    // 這是private的方法,CGLIB不會攔截這個方法
    private void foo3() {
       System.out.println(">> private的方法 <<");
    }
    // CGLIB會攔截這個方法,可以是public, protected, default的修飾符
    public void deleteUser() {
       System.out.println(">> 刪除一個User <<");
    }
}

CGLIB代理類

// CGLIB代理類
public class LogCglibProxy implements MethodInterceptor {
    private Object target; // 代理的目標對象
    /**
     * 創建代理對象
     * @param 
     * @param target 目標對象
     */
    public  T createProxyInstance(T target) {
       this.target = target;
       Enhancer enhancer = new Enhancer(); // 該類用於生成代理對象
       enhancer.setSuperclass(target.getClass()); // 設置父類
       enhancer.setCallback(this); // 設置回調對象爲自己這個對象
       return (T) enhancer.create(); // 創建代理對象並返回
    }
    /**
     * @param proxy       目標對象代理類的實例
     * @param method     代理類實例上調用父類方法的Method實例
     * @param args           傳入到方法參數值的對象數組
     * @param methodProxy    使用它調用父類的方法
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
       System.out.println(proxy.getClass());
       System.out.println("== Log:開始執行操作,方法名:" + method.getName() + " ==");
       Object result = methodProxy.invoke(target, args); // 執行原方法
       System.out.println("== Log:操作執行完畢 ==");
       return result;
    }
}
CGLIB代理總結:
1. CGLIB可以生成目標類的子類,並重寫父類非final修飾符的方法。
2. 要求類不能是final的,要攔截的方法要是非final、非static、非private的。

一個典型的動態代理創建對象過程可分爲以下四個步驟:
1、通過實現InvocationHandler接口創建自己的調用處理器 InvocationHandler handler = new InvocationHandlerImpl(...);
2、通過爲Proxy類指定ClassLoader對象和一組interface創建動態代理類

Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});

3、通過反射機制獲取動態代理類的構造函數,其參數類型是調用處理器接口類型

Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

4、通過構造函數創建代理類實例,此時需將調用處理器對象作爲參數被傳入

Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));

爲了簡化對象創建過程,Proxy類中的newProxyInstance方法封裝了2~4,只需兩步即可完成代理對象的創建。

public static Object newProxyInstance(ClassLoader loader,

                                      Class<?>[] interfaces,

                                      InvocationHandler h)

                               throws IllegalArgumentException

生成的ProxySubject繼承Proxy類實現Subject接口,實現的Subject的方法實際調用處理器的invoke方法,而invoke方法利用反射調用的是被代理對象的的方法(Object result=method.invoke(proxied,args))


代理總結:

spring在運行期創建代理,不需要特殊的編譯器。spring有兩種代理方式:
a) 若目標對象實現了若干接口,spring就會使用JDK動態代理;
b) 若目標對象沒有實現任何接口,spring就使用CGLIB庫生成目標對象的子類。

對接口創建代理優於對類創建代理,因爲會產生更加鬆耦合的系統。

切面

切面=切入點+通知
切入點指定要攔截哪些方法
通知(Advice接口的實現類)分爲前置通知、後置通知、異常通知、最終通知、環繞通知
切入點表達式格式
格式:
execution(
    修飾符?
    返回值類型
    類的全限定名?
    方法名(參數)
)


前置通知    before
後置通知    afterReturning
異常通知    afterThrowing
最終通知    after
環繞通知(前、後)

以上所有通知都配置上時,執行結果如下:
1,不拋異常:
    == before ==
    == 環繞通知(前) ==
    >> 刪除一個User <<
    == after ==
    == afterReturning ==
    == 環繞通知(後) ==
2,拋異常   
    == before ==
    == 環繞通知(前) ==
    >> 查詢所有User <<
    == after ==
    == afterThrows ==

// 定義通知類
public class MyAdvice {
    public void before() {
       System.out.println("== 前置通知 ==");
    }
    public void afterReturning() {
       System.out.println("== 後置通知 ==");
    }
    public void throwsException() {
       System.out.println("== 異常通知 ==");
    }
    public void after() {
       System.out.println("== 最終通知 ==");
    }
    // 參數ProceedingJoinPoint與返回值都不是強制性的,但是要寫上才能實完整的功能。
    public Object around(ProceedingJoinPoint point) throws Throwable {
       System.out.println("== 環繞通知(前) ==");
       Object result = point.proceed();
       System.out.println("== 環繞通知(後) ==");
       return result;
    }
}

XML文檔配置




b)	基於註解



// 定義通知類
@Aspect
public class MyAdvice {
    // 在@AspectJ註解風格的AOP中 一個切入點簽名通過一個普通的方法來定義
    // 1,作爲切入點簽名的方法必須不能有參數
// 2,一般使用private void修飾,方法體爲空
    @Pointcut("execution(* cn.itcast..*(..))")
    private void myPointcut() {
    }
    // Before 前置通知 在方法調用前執行
    // "myPointcut()" 表示前面定義的切入點
    // 也可以寫爲@Before("execution(* cn.itcast..*(..))")
    @Before("myPointcut()")
    public void before() {
       System.out.println("== 前置通知 ==");
    }
    // AfterReturning,後置通知 在一個匹配的方法返回的時候執行
    // pointcut 使用的切入點
    // returning 表示方法的返回值,方法無返回值時,返回值爲null
    @AfterReturning(pointcut = "myPointcut()", returning = "returnValue")
    public void afterReturning(Object returnValue) {
       System.out.println("== 後置通知(returnValue=" + returnValue + ") ==");
    }
    // AfterThrowing 異常通知,在一個方法拋出異常後執行
    // pointcut 使用的切入點
    // throwing 表示拋出的異常
    @AfterThrowing(pointcut = "myPointcut()", throwing = "ex")
    public void throwsException(Exception ex) {
       System.out.println("== 異常通知(ex=" + ex + ") ==");
    }
    // @After 最終通知 不論一個方法如何結束,最終通知都會執行
    // 最終通知必須準備處理正常和異常兩種情況,通常用它來釋放資源
    @After("myPointcut()")
    public void after() {
       System.out.println("== 最終通知 ==");
    }
    // @Around 環繞通知 可以控制方法的執行
    // 通知的第一個參數必須是 ProceedingJoinPoint類型
    // 調用ProceedingJoinPoint的proceed()方法會導致 後臺的連接點方法執行
    @Around("myPointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
       System.out.println("== 環繞通知(前) ==");
       Object result = point.proceed();
       System.out.println("== 環繞通知(後) ==");
       return result;
    }
}

事務

事務:一組操作的執行單元,對於數據庫來說,事務管理是一組SQL指令,比如增加、刪除、修改等。事務的一致性要求這個事務內的操作必須全部執行成功,如果在此過程中出現了差錯,那麼這一組操作都將全部回滾。

僅用四個詞解釋事務(ACID)
        atomic(原子性):要麼都發生,要麼都不發生。
        consistent(一致性):數據應該不被破壞。
        Isolate(隔離性):用戶間操作不相混淆。
        Durable(持久性):永久保存,例如保存到數據庫中等。


Spring提供了兩種事務管理方式:
1) 編程式事務管理
編寫程序式的事務管理可以清楚的定義事務的邊界,可以實現細粒度的事務控制,比如你可以通過程序代碼來控制你的事務何時開始,何時結束等,與後面介紹的聲明式事務管理相比,它可以實現細粒度的事務控制。
2) 聲明式事務管理
如果你並不需要細粒度的事務控制,你可以使用聲明式事務,在Spring中,你只需要在Spring配置文件中做一些配置,即可將操作納入到事務管理中,解除了和代碼的耦合, 這是對應用代碼影響最小的選擇。當你不需要事務管理的時候,可以直接從Spring配置文件中移除該設置。


聲明式事務管理-基於XML配置:
1、在spring配置文件中引入tx命名空間
2、在spring配置文件中做關於事務管理的配置
聲明式事務管理-基於XML配置:
1、在spring配置文件中引入tx命名空間
2、在spring配置文件中做關於事務管理的配置



聲明式事務管理-基於註解配置
@Transaction:方法的事務設置將被優先執行;     
@Transactional註解可以被繼承,即:在父類上聲明瞭這個註解,則子類中的所有public方法也都是會開事務的。


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