springIOC的那些事
???springIOC動態代理的那些事兒
1.發現問題
今天在使用spring的IOC容器時發現了這樣的一個問題:
首先有一個接口定義如下:
它的實現類如下:
package cn.ccsu.service.impl;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import cn.ccsu.dao.BookMapper; import cn.ccsu.dao.StockMapper; import cn.ccsu.dao.UserMapper; import cn.ccsu.exception.BookStockException; import cn.ccsu.exception.UserAccountException; import cn.ccsu.service.BookShopService;@Service("bookShopServiceImpl") public class BookShopServiceImpl implements BookShopService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate BookMapper bookMapper;@Autowiredprivate StockMapper stockMapper;public BookShopServiceImpl() {}@Transactional@Overridepublic void purchase(String userName, Integer id) throws Exception {// 1. 獲取書的單價Integer price = bookMapper.queryPrice(id);// 2. 更新書的庫存if (stockMapper.queryStock(id) == 0) {throw new BookStockException("庫存不足!");}System.out.println("更新書的庫存:" + stockMapper.updateStock(id));// 3. 更新用戶余額if (userMapper.queryBalance(userName) < price) {throw new UserAccountException("余額不足!");}System.out.println("\n更新用戶余額:" + userMapper.updateBalance(userName, price));}}這個類主要是完成對圖書的銷售工作,這個不是重點。接著我創建spring應用上下文,視圖從中獲取這個類的實例,這個時候出錯了。代碼如下:
ClassPathXmlApplicationContext ctxt = new ClassPathXmlApplicationContext("applicationContext.xml");BookShopService service = (BookShopService) ctxt.getBean("bookShopServiceImpl");報錯信息如下:
com.sun.proxy.$Proxy21 cannot be cast to BookShopServiceImpl怎么樣?是不是懵逼了?且聽我細細道來。
2.動態代理
??看到com.sun.proxy.$Proxy21沒?這就是突破口:proxy--->代理,這說明spring創建了一個代理對象。為什么是代理對象而不是BookShopServiceImpl類的對象呢?這個后面再說,先看看下面的。
不知道你有沒有聽說過java的動態代理(不知道的請自行谷歌),java有2種動態代理機制:JDK動態代理和cglib動態代理。前者是基于接口實現的,而后者是基于類實現的。聽不懂?行,我簡單說下吧!!
比如我剛剛的這個例子,BookShopServiceImpl類實現了BookShopService接口,此時就可以用JDK代理,JDK會創建一個代理對象,暫且叫它$Proxy21吧。$Proxy21和BookShopServiceImpl類沒有任何繼承關系,但是$Proxy21是BookShopService接口的實現類的對象。也就是說JDK代理創建的是該類的父接口的一個實現對象。
接下來說說cglib代理,cglib代理是基于類的代理。比如有一個基類A,B繼承了這個基類A。如果此時創建一個代理對象,該代理對象是可以用B指向的。因為該對象是B的一個實現類的對象。也就是說cglib代理會創建原來的類的一個子類,也就是代理類是原有類的一個子類。
綜上所述:JDK代理會創建原有接口的一個實現類,而cglib代理會創建原有類的一個子類。
3.解開謎團
這下明白沒?再來看看報錯信息: com.sun.proxy.$Proxy21 cannot be cast to BookShopServiceImpl這里使用了代理,而且還是JDK代理-->即基于接口的代理,所以不能將該代理對象強轉為BookShopServiceImpl類型,因為該代理對象是BookShopService接口的子類型。這就完了嗎?還早著呢,繼續往下看。
我之前說過:為什么spring?IOC容器創建代理對象而不是創建BookShopServiceImpl類的對象呢?仔細看這個類的purchase方法:
這里用了事務。在spring中如果使用事務或者AOP,都會創建代理對象,讓這個代理對象去完成。而spring默認的代理機制是JDK代理,所以這里使用了JDK代理,創建的對象是BookShopService的子類型,和BookShopServiceImpl 沒有半點關系,所以不能強轉為BookShopServiceImpl。還有一種是cglib代理,之前說了,它是基于類的方式。你可以在配置文件中修改代理方式,如下:
<tx:annotation-driven transaction-manager="transactionManager" mode="aspectj" />修改代理方式為cglib代理。(注:aspectj代理方式即cglib代理)
4.驗證
接著我寫了一個小demo,驗證了下,代碼如下: A.java: package cn.ccsu;public abstract class A {public A() {}} B.java: package cn.ccsu;import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional;@Component public class B extends A {public B() {}@Transactionalpublic void testAnno() {} } 測試: ClassPathXmlApplicationContext ctxt = new ClassPathXmlApplicationContext("applicationContext.xml"); B b = (B) ctxt.getBean("b"); b.testAnno();雖然我在在這里用了事務,但是因為沒有牽涉到接口,所以會使用cglib代理,也就是創建B類的一個子類型的對象。即代理類是B類的子類。所以在這里無論使用A指向還是B指向都沒問題。
接著又寫了一個接口以及它的一個實現類,代碼如下:
此時會報錯:
java.lang.ClassCastException: com.sun.proxy.$Proxy8 cannot be cast to cn.ccsu.service.impl.IUserServiceImpl如果我去掉@Transactional注解,程序可以正常后運行。當你使用事務(或者AOP)時,spring會自動創建一個代理對象,讓這個代理對象去完成。但如果沒有使用事務,spring的IOC容器會正常創建該類的一個對象,所以程序可以正常跑起來。
5.總結
敲黑板ing..劃重點啦!!劃重點啦!!
1.JDK代理是基于接口的,它會創建被代理類的父接口的一個子類型;cglib代理是基于類的,它會創建被代理類的一個子類型。
2.spring有2中代理機制:JDK代理和cglib代理,默認使用前者。
3.當使用AOP或者事務時會自動創建一個代理對象,讓它來完成需要處理的事。
這個問題也讓我想起了之前的一個bug,同樣的問題,只不過是在使用AOP時遇到的,鏈接如下:
AOP bug
轉載于:https://www.cnblogs.com/yansiju/p/7859931.html
總結
以上是生活随笔為你收集整理的springIOC的那些事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【BZOJ4545】DQS的trie 后
- 下一篇: 深度学习demo