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="foo@bar.com"/>
</bean>
<!-- p-namespace 聲明 -->
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="foo@bar.com"/>
</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="foo@bar.com"/>
</bean>
<!-- c-namespace 聲明 -->
<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
</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的配置,類型安全且易懂。
