問題的出現是因爲有人問我,爲什麼他在學習Spring框架的時候,他在xml文件當中定義了一個Bean,最後在調用getBean()方法獲取這個Bean的時候,必須轉換成這個Bean對應的接口,而不能轉換成這個接口的實現類。
我在網上一查,發現也有對應的問題,但是感覺對應的答案都不夠正確,或者不夠解答我的疑惑
現在我們開始重現這個問題
第一步:創建接口和對應的實現類
//對應接口
public interface ICar {
void move();
}
//對應的實現類
@Transactional
public class MyBenz implements ICar {
@Override
public void move() {
System.out.println("my car");
}
}
第二步:編寫對應的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:myname="http://www.example.org/schema/user" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.example.org/schema/user http://www.example.org/schema/user.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置HikariDataSource數據源 -->
<bean id = "dataSource" class = "com.zaxxer.hikari.HikariDataSource">
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3307/my_oracle?serverTimezone=GMT"></property>
</bean>
<!-- 定義事務 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" /><!-- ref:引入數據源 -->
</bean>
<!-- 配置 Annotation 驅動,掃描@Transactional註解的類定義事務啓動事物註解 transaction-manager的值必須和上面這個bean的id一樣-->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false" />
<!--配置bean,class屬性我們設置爲實現類-->
<bean id="myBenz" class="com.fsl.springbootjunit.spring.test3.MyBenz"></bean>
</beans>
第三步:進行測試
public static void main(String[] args) {
ApplicationContext bf = new ClassPathXmlApplicationContext("spring/applicationContext-beans.xml");
MyBenz myBenz = (MyBenz)bf.getBean("myBenz");
myBenz.move();
}
結果如下,會出現java.lang.ClassCastException:
我們在配置文件當中配置的Bean的class屬性明明就是com.fsl.springbootjunit.spring.test3.MyBenz,但是爲什麼我們getBean()的時候,轉成對應的類型會報類型轉換錯誤呢?
問題解答
首先我們要知道的是,Spring的AOP的實現底層是使用的代理模式,它就是通過創建對應的代理對象,通過對代理對象的方法運行前後進行處理來達到目的。我們使用的事務也是如此,給創建一個代理對象,在這個方法運行之前,Spring幫助開啓事務,方法運行之後,Spring幫助我們提交事務或者回滾事務。
Spring創建代理對象的代碼如下:
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
//在創建代理對象之前進行一系列判斷,來決定是使用JDK代理還是CGLIB代理
//isOptimize:使用cglib代理是否使用激進的優化策略
//isProxyTargetClass:是否對目標本身進行代理,而不是對目標類的接口進行代理
//hasNoUserSuppliedProxyInterfaces:是否不存在代理接口
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
在創建對象開始之前,會進行判斷來決定使用JDK動態代理還是CGLIB動態代理,因爲這兩種代理模式對代理對象的要求不同,比如JDK動態代理就要求被代理的對象實現了某個接口。
其中有一個屬性很重要,isProxyTargetClass,這個就用來判斷,是否對目標本身進行代理,而這個屬性,默認是false
現在我們就能夠知道,原來代理對象在生成的過程當中,是去找到被代理對象的接口,依據那個接口來生成的對象,而不是依據目標本身生成的。
ApplicationContext bf = new ClassPathXmlApplicationContext("spring/applicationContext-beans.xml");
Object myBenz = bf.getBean("myBenz");
//這裏返回true,因爲是依據這個接口生成的代理對象
System.out.println(myBenz instanceof ICar);
//這裏返回false,因爲這個是代理目標類,但是Spring默認的不以這個去生成代理對象
System.out.println(myBenz instanceof MyBenz);
那假如我們就想以目標類去生成代理對象呢?
通過上面的分析,大家肯定也能夠回答出來,我把proxyTargetClass屬性設置爲true就可以了。
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
在執行下面的這個代碼,我們就能夠看到對應的效果
ApplicationContext bf = new ClassPathXmlApplicationContext("spring/applicationContext-beans.xml");
Object myBenz = bf.getBean("myBenz");
//返回true
System.out.println(myBenz instanceof ICar);
//返回true
System.out.println(myBenz instanceof MyBenz);