Spring裝配Bean的三種方式+導入和混合配置
- 2020 年 4 月 7 日
- 筆記
Spring IoC與bean
A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application.
bean是由Spring IoC容器實例化、裝配和管理的對象,否則,bean只是應用程序中的眾多對象之一。
bean及其之間的依賴關係反映在容器使用的配置元數據中。
我們已經了解,Spring IoC容器能夠幫我們操作bean,但是前提是我們需要配置元數據以告知Spring容器,它才能夠通過讀取這些配置,來實例化,裝配和管理bean對象。
而配置元數據的方式,就是我們今天要總結的三種,分別是XML,Java註解以及Java代碼。我們通過這幾種方式,向Spring容器傳達這些對象之間豐富的相互依賴關係。
該圖是Spring如何工作的高級視圖。可以看到,應用程序類與配置元數據相結合,在創建並初始化ApplicationContext之後,就可以獲得一個完全配置和可執行的系統或應用程序。
基於XML的顯式裝配
xml配置的基本結構
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="..." class="..."> <!-- collaborators and configuration for this bean go here --> </bean> <bean id="..." class="..."> <!-- collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions go here --> </beans>
id屬性表示bean的唯一標識。
class屬性定義bean的類型並使用完全限定的類名。
bean實例的三種創建方式
<!-- 一、使用默認構造函數創建,如果沒有該默認構造函數,則創建失敗。 --> <bean id="userService" class="com.smday.service.impl.UserServiceImpl"></bean> <!-- 二、使用普通公章中的方法創建對象(使用某個類中的方法創建對象,並存入spring容器 --> <bean id="instanceFactory" class="com.smday.factory.InstanceFactory"></bean> <bean id="userService" factory-bean="instanceFactory" factory-method="getUserService"></bean> <!-- 三、使用工廠中的靜態方法創建對象 --> <bean id="userService" class="com.smday.factory.StaticFactory" factory-method="getUserService"></bean>
依賴注入的兩種方式
構造器注入方式
在<bean>
標籤的內部定義<constructor-arg>
標籤。
public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public ExampleBean( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { this.beanOne = anotherBean; this.beanTwo = yetAnotherBean; this.i = i; } }
<bean id="exampleBean" class="examples.ExampleBean"> <!-- constructor injection using the nested ref element --> <constructor-arg> <ref bean="anotherExampleBean"/> </constructor-arg> <!-- constructor injection using the neater ref attribute --> <constructor-arg ref="yetAnotherBean"/> <constructor-arg type="int" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
value:用於提供基本類型和String類型的數據。
ref:用於提供其他的bean類型數據,在spring的ioc核心容器中出現過的bean對象。
在創建對象時,如果沒有提供構造器中的這些參數,將無法創建該對象。
setter方法注入方式
在<bean>
標籤的內部定義<property>
標籤。
public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; } }
<bean id="exampleBean" class="examples.ExampleBean"> <!-- setter injection using the nested ref element --> <property name="beanOne"> <ref bean="anotherExampleBean"/> </property> <!-- setter injection using the neater ref attribute --> <property name="beanTwo" ref="yetAnotherBean"/> <property name="integerProperty" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
name:指定注入時調用的set方法的屬性名稱。
value:提供基本類型和String類型的數據。
ref:提供其他的bean類型數據,在spring的ioc核心容器中出現過的bean對象。
如果某個成員必須有值,但並沒有提供相應的setter方法,將會出錯。
【集合類型的注入】:分為list和map兩類結構
<bean id="userService" class="com.smday.service.impl.UserServiceImpl"> <property name="myStrs"> <array> <value>AAA</value> <value>BBB</value> <value>BBB</value> </array> </property> <property name="myList"> <list> <value>AAA</value> <value>BBB</value> <value>BBB</value> </list> </property> <property name="mySet"> <set> <value>AAA</value> <value>BBB</value> <value>BBB</value> </set> </property> <property name="myMap"> <map> <entry key="testA" value="AAA"></entry> <entry key="testB" > <value>BBB</value> </entry> </map> </property> <property name="myProp"> <props> <prop key="testC">CCC</prop> <prop key="testD">DDD</prop> </props> </property> </bean>
list結構可以使用list、array和set標籤。
map結構可以使用map和props標籤。
利用命名空間簡化xml
一、p-namespace使用bean元素的屬性來提供屬性值和協作bean,而不是使用嵌套的<property/>
元素,下面兩段bean的配置效果相同。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 傳統的xml聲明 --> <bean name="classic" class="com.example.ExampleBean"> <property name="email" value="[email protected]"/> </bean> <!-- p-namespace 聲明 --> <bean name="p-namespace" class="com.example.ExampleBean" p:email="[email protected]"/> </beans>
二、Spring 3.1中新引入的c-namespace允許使用內聯屬性來配置構造函數參數,而不是使用嵌套的<constructor-arg>
。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> <!-- 傳統的xml聲明 --> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> <constructor-arg value="[email protected]"/> </bean> <!-- c-namespace 聲明 --> <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="[email protected]"/> </beans>
基於Java的顯式裝配
@Bean 和 @Configuration
這兩個註解類是Spring’s new java-configuration的核心構件。
@Bean註解用於指示方法實例化、配置和初始化要由Spring IoC容器管理的新對象,@Bean註解的作用與<bean/>
標籤相同。簡單的理解就是這個註解可以告知spring,這個方法上面未來希望註冊一個應用上下文的bean對象,因此用@Bean註解的方法需要利用Java代碼,定義返回一個bean實例的邏輯。
@Configuration註解一個類表明這個類的主要目的是作為bean定義的源,@Configuration類允許通過簡單地調用同一類中的其他@Bean方法來定義bean之間的依賴關係。簡單的理解就是一個配置類,自此之後,你可以在該配置類中完成在xml中完成的事,但形式會有所不同。
下面這個例子是一個最簡單的配置類的定義:
@Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } }
它的作用和下面這段xml配置的方式等價:
<beans> <bean id="myService" class="com.acme.services.MyServiceImpl"/> </beans>
Bean的依賴
一個@Bean注釋的方法可以有任意數量的參數來描述構建該bean所需的依賴關係。例如,如果我們的TransferService需要一個AccountRepository,我們可以通過一個方法參數來實現這個依賴:
@Configuration public class AppConfig { @Bean public TransferService transferService(AccountRepository accountRepository) { return new TransferServiceImpl(accountRepository); } }
當spring調用transferService方法創建bean時,會自動裝配accountRepository到配置方法中,再次印證了那句話,帶有@Bean註解的方法可以編寫任何必要的Java代碼來產生Bean的實例,例如構造器,setter方法,以及任何可以產生實例的方法。
初始化Spring容器
AnnotationConfigApplicationContext是Spring 3.0中新增的。它不僅可以接受@Configuration配置類作為輸入,還可以接受普通的@Component類和使用JSR-330元數據注釋的類。
初始化spring容器,獲取Myservice對象,調用對象的方法。
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
定製bean的命名
默認情況下,配置類將會使用@Bean註解的方法的名稱作為bean的名稱,這一點可以通過name屬性修改。
@Configuration public class AppConfig { @Bean(name = "myFoo") public Foo foo() { return new Foo(); } }
如上:如果沒有指定name屬性,該bean的名稱為foo,如果指定了name屬性,這裡的名稱就是myFoo。
基於註解的自動裝配
Spring從以下兩個角度實現自動裝配:
- 組件掃描:Spring自動發現應用上下文中所創建的bean。
- 自動裝配:Spring自動滿足bean之間的依賴。
首先還是來看一段簡單的例子:
//定義一個UserService接口 public interface UserService { void add(); }
//定義實現類,注意加上@Component註解,告知spring創建這個bean @Component public class NormalUserServiceImpl implements UserService { @Override public void add() { System.out.println("添加用戶"); } }
//controller層,注意@Autowired註解,自動按類型注入Userservice @Component public class UserController { @Autowired private UserService userservice; public void add(){ userservice.add(); } }
//定義配置類,注意@ComponentScan("com.my.demo")註解開啟組件掃描 @Configuration @ComponentScan("com.my.demo") public class Appconfig { }
//整合junit測試類進行測試 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = Appconfig.class) public class UserServiceTest { @Autowired private UserService userservice; @Test public void testMethod(){ userservice.add(); } }
以上就是一套可以正常運行的簡單案例,當然其中有不少可能出現的問題或者是其他可以實現相同功能的方案,我們都暫且不提。其中出現了許多自動化裝配bean的註解,我們一一來看:
自動裝配的常用註解
【@Component】
- 作用:將當前類對象存入spring容器中。
- 屬性:value,用於指定bean的id,不指定value時,默認值為當前類名首字母小寫。
值得一提的是,在三層架構中,Spring框架提供了明確的三層注釋,作用與@Component相同,但語義更加清晰明了,分別是:
Controller:表現層、Service:業務層、Respository:持久層
【@Autowired】
- 作用:自動按照類型注入,只要容器中有唯一的一個bean對象類型和要注入的變量類型匹配,就可以注入成功。
- 如果ioc容器中沒有任何bean類型和要注入的變量類型匹配,則報錯(解決方法是,設置required屬性的值為false,如果沒找到對應類型的bean,則會出於未裝配狀態),如果ioc容器中有多個類型匹配時,出現歧義性,也會報錯。
- 出現位置:既可以是構造器,也可以是setter方法,甚至任何其他的方法,Spring都會嘗試滿足方法參數上聲明的依賴。
- 細節:在使用註解注入時,set方法就不是必須的了。
當出現歧義性時,滿足類型要求的bean不是唯一時,可以考慮使用@Qualifier和@Resource註解,參考:Spring解決自動裝配歧義性的幾種方案
【@Configuration】
- 作用:指定當前類是一個配置類
- 細節:當配置類作為AnnotationConfigApplicationContext對象創建的參數時,該註解可以不寫。
【@ComponentScan】
- 作用:開啟組件掃描,用於通過註解指定spring在創建容器時要掃描的包。
- 屬性:value,和basePackages的作用相同,指定創建容器時要掃描的包。
- 如果不指定value或者basePackages的值,將會默認掃描與配置類相同的包。
設置spring組件掃描的基礎包的幾種方案:
-
@ComponentScan("com.my.demo")
-
@ComponentScan(basePackages = {"com.my.demo.web","com.my.demo.service"})
-
@ComponentScan(basePackageClasses = {UserController.class, UserService.class, UserDao.class})
,相較於第二種,較為安全。
需要注意的是,組件掃描默認是不開啟的,我們需要通過該註解顯式通知Spring,告訴它去尋找帶有@Component註解的類,去創建該類的bean對象。
開啟組件掃描的xml方式:
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--配置掃描,相當於@ComponentScan("com.my.demo")--> <context:component-scan base-package="com.my.demo"/> </beans>
既然使用xml方式開啟組件掃描,那麼測試的時候需要謹慎,要讀取該xml文件:@ContextConfiguration("classpath:applicationContext.xml")
。
導入和混合配置
直接以例子呈現:
#jdbcConfig.properties jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring jdbc.username=root jdbc.password=123456
/** * @author Summerday * <p> * 和spring連接數據庫相關的配置類 */ public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 創建queryRunner對象 * * @param dataSource * @return */ @Bean(name = "runner") @Scope("prototype") public QueryRunner createQueryRunner(DataSource dataSource) { return new QueryRunner(dataSource); } /** * 創建數據源對象 * * @return */ @Bean(name = "dataSource") public DataSource createDataSource() { try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds; } catch (Exception e) { throw new RuntimeException(e); } } }
【@Value】
- 作用:用於基本類型和string類型的數據。
- 屬性:value,指定數據的值,可以使用spring中的SpEL,spring的el表達式。
- SpEL的寫法:${表達式}。
/** * 主配置類 */ @Configuration @ComponentScan(basePackages = "com.smday") @Import(JdbcConfig.class) @PropertySource("classpath:JdbcConfig.properties") public class SpringConfiguration { }
【@Import】
- 作用:用於導入其他的配置類。
- 屬性:value,指定其他配置類的位元組碼,使用Import註解後,有該註解的類為父配置類,導入的都是子配置類。
【@PropertySource】
- 作用:作用於指定properties文件的位置。
- 屬性:value,指定文件的名稱和路徑,關鍵字classpath表示類路徑下。
最後的最後,引用Spring in Action中作者的話:自動化配置、基於Java的顯式配置以及基於xml的顯式配置都描述了Spring應用中組件以及這些組件之間的關係。作者建議儘可能使用自動化的配置,其次如果需要顯式配置,希望優先選擇基於Java的配置,類型安全且易懂。