Spring框架getBean()方法返回對象爲什麼只能轉成接口對象,轉換成接口的實例會報錯?

問題的出現是因爲有人問我,爲什麼他在學習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);

 

 

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