Java進階代理模式結合SpringAOP源碼分析

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

  • 靜態代理
  • 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動態代理的代理類一般需要實現接口

 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<annotation-driven>
3<bean>
4 <property>
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<annotation-driven>

上面配置默認啟用JDK動態代理,JDK只能代理接口不能代理類。而我的項目中用的是這個配置,卻因為沒有定義Service接口導致項目啟動報錯。

如果需要使用CGLIB動態代理:配置如下:

1<annotation-driven> 

關注、轉發、評論頭條號每天分享java 知識,私信回覆“555”贈送一些Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式資料


分享到:


相關文章: