代理模式——結合SpringAOP講解

前言

  筆者最近想學學Spring源碼,各類設計模式在Spring的源碼中運用得淋漓盡致,筆者也不得不感嘆原來廣大的開發者一直站在牛人的肩膀上進行編程。談到Spring,面試問得最多的就是Spring的兩大核心,IOC以及AOP。IOC本質上來講就是對bean反射以及依賴注入,管理bean生命週期的容器。而AOP本質上就是動態代理。今天筆者就來說講動態代理。接下來我將從下面幾個方面闡述動態代理:java

  • 靜態代理
  • JDK動態代理
  • CGlib動態代理
  • 談談筆者本身在實際項目使用AOP遇到的坑

靜態代理

  靜態代理很簡單,我們本身在寫代碼的時候都會寫到這種相似靜態代理的代碼。簡單來講,就是把被代理類做爲參數傳給代理類的構造方法,讓代理類替被代理類實現更強大的功能。程序員

 1package com.bingo.designPatterns.proxy;
2
3/**
4 * Description:靜態代理
5 * User: bingo
6 */

7
8public class StaticProxyTest {
9
10    public static void main(String[] args) {
11
12        UserService userService = new UserService();
13
14        LogProxy logProxy = new LogProxy(userService);
15        logProxy.addUser();
16        logProxy.deleteUser();
17    }
18}
19
20interface IUserService{
21    void addUser();
22    void deleteUser();
23}
24
25
26class UserService implements IUserService{
27
28    @Override
29    public void addUser() {
30        System.out.println("添加用戶");
31    }
32
33    @Override
34    public void deleteUser() {
35        System.out.println("刪除用戶");
36    }
37}
38
39//日誌代理
40class LogProxy implements IUserService{
41
42    //目標類
43    private UserService target;
44
45    public LogProxy(UserService target){
46        this.target = target;
47    }
48
49    @Override
50    public void addUser() {
51        System.out.println("記錄日誌開始");
52        target.addUser();
53        System.out.println("記錄日誌結束");
54    }
55
56    @Override
57    public void deleteUser() {
58        System.out.println("記錄日誌開始");
59        target.deleteUser();
60        System.out.println("記錄日誌結束");
61    }
62}
複製代碼

  雖然靜態代理實現比較簡單,可是在實際項目中咱們須要爲每一個類都寫一個代理類,須要寫不少重複冗餘的代碼,不利於代碼的解耦與擴展。可是動態代理便很好的解決了上述問題,真真正正地實現了業務邏輯代碼與加強功能代碼的解耦。面試

動態代理

  在Spring源碼中,用到的動態代理主要有兩種,JDK動態代理以及CGLib動態代理。二者主要區別是:
  • JDK動態代理通常針對實現了接口的類生成代理。(下面講AOP遇到的坑時更能理解這句話的含義)
  • 目標對象沒有實現接口,則默認會採用CGLIB代理。若是目標對象實現了接口,能夠強制使用CGLIB實現代理(添加CGLIB庫)
    其實,上面的區別闡述雖然不夠徹底,但足以區分兩者。
相同點:
  • 兩種動態代理本質上都是:字節碼組裝
AOP動態代理的應用場景:
  • 日誌
  • 事務
  • 權限
  • 緩存
  • 懶加載

JDK動態代理

JDK動態代理的代理類通常須要實現接口spring

 1package com.bingo.designPatterns.proxy;
2
3import java.lang.reflect.InvocationHandler;
4import java.lang.reflect.Method;
5import java.lang.reflect.Proxy;
6
7/**
8 * Description: jdk動態代理
9 * User: bingo
10 */

11public class JdkProxyTest {
12
13    public static void main(String[] args) {
14        IPersonService personService = JdkDynamicProxy.getProxy();
15        personService.addPerson();
16        personService.deletePerson();
17    }
18}
19
20interface IPersonService{
21    void addPerson();
22    void deletePerson();
23}
24
25class PersonService implements IPersonService{
26    @Override
27    public void addPerson() {
28        System.out.println("添加人物");
29    }
30
31    @Override
32    public void deletePerson() {
33        System.out.println("刪除人物");
34    }
35}
36
37
38/**
39 * newProxyInstance方法參數說明:
40 *      ClassLoader loader:指定當前目標對象使用的類加載器,獲取加載器的方法是固定的
41 *      Class<?>[] interfaces:指定目標對象實現的接口的類型,使用泛型方式確認類型
42 *      InvocationHandler:指定動態處理器,執行目標對象的方法時,會觸發事件處理器的方法
43 */

44class JdkDynamicProxy{
45
46    public static IPersonService getProxy(){
47
48        IPersonService personService = new PersonService();
49
50        IPersonService proxy = (IPersonService) Proxy.newProxyInstance(IPersonService.class.getClassLoader(), new Class<?>[]{IPersonService.class}, new InvocationHandler() {
51
52            @Override
53            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
54                System.out.println("記錄日誌開始");
55                Object obj = method.invoke(personService, args);
56                System.out.println("記錄日誌結束");
57                return obj;
58            }
59        });
60
61        return proxy;
62    }
63}
複製代碼

CGLib動態代理

  須要導入cglib.jar,asm.jar包才能使用此代理編程

 1package com.bingo.designPatterns.proxy;
2
3import net.sf.cglib.proxy.Enhancer;
4import net.sf.cglib.proxy.MethodInterceptor;
5import net.sf.cglib.proxy.MethodProxy;
6
7import java.lang.reflect.Method;
8
9/**
10 * Description: cglib動態代理
11 * User: bingo
12 */

13
14public class CglibProxyTest {
15    public static void main(String[] args) {
16
17        CglibProxy proxy = new CglibProxy();
18        Train t = (Train)proxy.getProxy(Train.class);
19        t.move();
20    }
21}
22
23class Train {
24
25    public void move(){
26        System.out.println("火車行駛中...");
27    }
28}
29
30class CglibProxy implements MethodInterceptor {
31
32    private Enhancer enhancer = new Enhancer();
33
34    public Object getProxy(Class clazz){
35        //設置建立子類的類
36        enhancer.setSuperclass(clazz);
37        enhancer.setCallback(this);
38
39        return enhancer.create();
40    }
41
42    /**
43     * 攔截全部目標類方法的調用
44     * obj  目標類的實例
45     * m   目標方法的反射對象
46     * args  方法的參數
47     * proxy代理類的實例
48     */

49    @Override
50    public Object intercept(Object obj, Method m, Object[] args,
51                            MethodProxy proxy)
 throws Throwable 
{
52        System.out.println("日誌開始...");
53        //代理類調用父類的方法
54        proxy.invokeSuper(obj, args);
55        System.out.println("日誌結束...");
56        return null;
57    }
58}
複製代碼

項目中使用AOP遇到的坑

  筆者以前在開發小程序服務接口時遇到的問題:
1.在Spring配置文件中配置了事務管理器,以下:小程序

1<!--事務管理器配置,單數據源事務-->
2<tx:annotation-driven transaction-manager="transactionManager" />  
3<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
4    <property name="dataSource" ref="dataSource" />
5</bean>
複製代碼

2.配置了事務管理器後,加入了@Transactional註解設計模式

1@Service
2@Transactional
3public class AccountService{
4    //to do something
5}
複製代碼

  乍一看,這個配置與使用都沒什麼毛病,對吧,可是項目一放到Tomcat服務上就報錯,具體報什麼錯,筆者已經記不得了,可是筆者很記得出錯的緣由就是transaction註解的問題,因而沒辦法,翻看原來公司的項目代碼,對比本身的代碼,發現各類配置也不差,不知道問題出在哪,可是原來的項目啓動正常,個人項目卻報錯。細看發現,公司原來的項目中Service都定義了一個接口做爲規範,@Transactional註解都是加在接口實現類上的。因而乎,我半信半疑的爲每一個Service的類定義了一個接口規範,實現類加上@Transactional註解,以下:緩存

1@Service
2@Transactional
3public class AccountService implements IAccountService {
4    //to do something
5}
複製代碼

配置沒變,只是簡單地爲Service層定義了接口,並實現接口,項目就運行正常了。微信

問題根源出在哪呢?就在這裏:
1<tx:annotation-driven transaction-manager="txManager"/>
複製代碼

上面配置默認啓用JDK動態代理,JDK只能代理接口不能代理類。而個人項目中用的是這個配置,卻由於沒有定義Service接口致使項目啓動報錯。app

若是須要使用CGLIB動態代理:配置以下:

1<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/> 
複製代碼

  不知道有沒有讀者遇到過跟我一樣的坑,若是不瞭解動態代理,就有可能出現和我相同的問題。可是寫程序規範很重要,MVC三層結構中,除了Controller層,Service、Dao層都須要定義接口,這是企業開發的規範。那段時間仍是個剛畢業不久的實習生,不免有時候會寫出這種不規範的代碼,可是但願之後可以愈來愈規範,多看看《阿里巴巴Java開發手冊》,多看看源碼,看看大牛們寫的代碼,相信不久以後本身也能夠寫出如此優雅的代碼,讓編程變成一種藝術。

  我是廣州的java程序員小彬,剛畢業半年,一直致力於java的學習,對個人文章感興趣能夠關注個人微信公衆號(J2彬彬),感謝支持!

相關文章
相關標籤/搜索