Spring 中全部的 Bean 都是經過容器來進行管理的。每一個 POJO 均可以是一個 Spring Bean。容器會管理 Bean 的依賴關係,這種依賴關係有多是 Bean 之間的,也有多是 Bean 對配置數據的依賴。在使用 Spring 的時候,開發者須要作的就是讓 Spring 容器知道這些依賴關係,而後剩下的事情交給 Spring 容器就好了。java
Spring 容器在初始化的時候會作兩件事,將配置中的全部 POJO 加載進容器生成 Bean 而且注入 Bean 之間的依賴關係。配置 Bean 之間的依賴關係就是咱們所說的依賴注入,須要注意的是這個生成 Bean 和依賴注入之間並不存在嚴格的前後關係,具體下面再說。spring
若是咱們想讓 Spring 來管理 Bean,第一步就是要將這些 Bean 裝入容器中。把 Bean 裝入容器中的方式有三種:微信
這三種方式完成的效果都同樣,並且這三種方式能夠混合使用。在下面我會主要使用 xml 的方式來做爲例子來進行演示,由於 xml 配置文件相對比較直觀,也會提供註解版本和 Java 代碼版本的例子。app
public class Gun implements Weapon{
@Override
public boolean attack() {
return false;
}
}
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="gun" name="gun1,gun2" class="cn.rayjun.springdemo.springcontainer.Gun">
</bean>
</beans>
複製代碼
在上面的配置文件中,咱們建立一個 xml 配置文件,其中 xml 根元素是 beans
,而後裏面有一個 bean
元素,這就是一個 Spring Bean。上面配置文件的意思就是告訴容器,我有一個名字叫作 Gun
的 Java 類,如今交給你管理了,就這麼簡單。ide
上面 XML Bean 的配置中有 id,name,class 等幾個屬性。其中 class 很簡單,就是類的全限定名稱,這個至關於告訴 Spring 容器要建立哪一個類的實例。id 和 name 都是用來標識一個 Bean 的惟一性。每一個 Bean 的 id 在容器中只能是惟一的,而 name 則能夠有多個,每一個 name 使用 ,
、;
或者空格來隔開,id 的命名須要符合 XML 的命名規範,也就是不能使用特殊字符,但 name 則可使用特殊字符來進行命名,若是沒有定義 id,則會把 name 的第一個值定爲 id,若是 id 和 name 都沒有定義,則使用類全名加上數字編號來做爲 id。獲取 bean 的時候,能夠經過以下的方式獲取:函數
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Gun gun1 = (Gun) context.getBean("gun1");
Gun gun = (Gun) context.getBean("gun");
System.out.println(gun == gun1); // true
複製代碼
在實際使用 Spring 的過程當中是不會使用上面的方式來使用 Bean,都會經過依賴注入的方式來獲取。post
使用註解如何配置呢,使用註解的配置以下:this
@Component
public class Gun implements Weapon{
@Override
public boolean attack() {
return false;
}
}
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<context:component-scan base-package="cn.rayjun.springdemo"/>
</beans>
複製代碼
上面的代碼與徹底使用 xml 配置的效果相同,你可能會想,這樣更麻煩了,其實不是,在 xml 中添加 context:component-scan
以後,cn.rayjun.springdemo
包下全部被 @Component 註解的類都會自動添加到容器中。也就是 xml 中就再也不須要 這個標籤了,若是想把 xml 從代碼中徹底剔除掉上面的代碼能夠寫成這樣:spa
@Component
public class Gun implements Weapon{
@Override
public boolean attack() {
return false;
}
}
@Configuration
@ComponentScan(basePackages = "cn.rayjun.springdemo")
public class SoldierConfig {
}
複製代碼
除了 @Component 註解以外,還有 @Repository, @Service, @Controller 等註解,@Repository 主要用來標識數據層,@Service 主要用來標識 Service 層,@Controller 主要用來標識 Controller 層。這些註解其實並無什麼不一樣,這麼作只是爲了讓代碼的分層更加清晰,這些註解均可以使用 @Componenet 替代。@Repository註解的實現以下:code
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
複製代碼
實際上 @Repository 等註解就是使用 @Component 註解實現的。Spring 號稱是無侵入性的,可是上面的代碼卻在 Gun 類上添加了 @Component 的註解,雖然不影響代碼的功能,但仍是稍微有點侵入性的,下面是終極的方案:
public class Gun implements Weapon{
@Override
public boolean attack() {
return false;
}
}
@Configuration
public class SoldierConfig {
@Bean
public Gun gun() {
return new Gun();
}
}
複製代碼
這裏使用帶 @Configuration 的配置類徹底替代 XML,這樣一來,代碼中沒有 XML,經過也作到了對代碼真正的無侵入性。
上面是將代碼放入到容器中的三種方法,這三種方法不是獨立存在的,而是能夠混用的。這三種配置各有各的好處,XML 讓 Bean 之間的依賴很清晰,並且不用修改源碼;註解能夠基本消除掉配置文件,可是這樣代碼中就會充斥各類註解;Java 配置則能夠徹底替代 XML。
在上面咱們說生成 Bean 和依賴注入不存在嚴格的前後關係,這是由於 DI有兩種方式:構造方法參數注入 和 Setter 方法注入。若是是構造參數注入,那麼在 Bean 生成對象的時候就須要將依賴注入,若是是 Setter 方法注入,則是在 Bean 對象生成以後纔會注入。
下面來看看如何進行依賴注入,先來看 XML 版本:
// 構造方法注入
public class Soldier {
private Weapon weapon;
public Soldier(Weapon weapon) {
this.weapon = weapon;
}
}
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="gun" class="cn.rayjun.springdemo.springcontainer.Gun">
</bean>
<bean id="solider" class="cn.rayjun.springdemo.springcontainer.Soldier">
<constructor-arg name="weapon" ref="gun"/>
</bean>
</beans>
複製代碼
使用構造方法注入時,須要在構造方法的參數上聲明所須要的依賴,而後在 XML 配置中經過 <constructor-arg/>
將依賴注入。再來看看 Setter 方法注入:
// setter 方法注入
public class Soldier {
private Weapon weapon;
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
}
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="gun" class="cn.rayjun.springdemo.springcontainer.Gun">
</bean>
<bean id="solider" class="cn.rayjun.springdemo.springcontainer.Soldier">
<property name="weapon" ref="gun"/>
</bean>
</beans>
複製代碼
首先 Solider 中須要有待注入依賴的 Setter 方法,而後在 XML 配置中經過 <property/>
來進行注入。那麼構造參數注入和 Setter 方法注入怎麼選呢?官方的推薦的作法是:若是依賴關係是強依賴時,使用構造參數注入,若是是可選依賴,則使用 setter 進行注入。強依賴能夠理解爲當前這個 Bean 正常運行所必須的依賴。
下面再來看看註解的是如何來進行依賴注入的,先看下面這段代碼,下面的這段代碼使用的是構造函數參數注入的方式:
@Component
public class Soldier {
private Weapon weapon;
@Autowired
public Soldier(Weapon weapon) {
this.weapon = weapon;
}
}
@Component
public class Knife implements Weapon{
public boolean attack() {
return false;
}
}
@Component
public class Gun implements Weapon{
public boolean attack() {
return false;
}
}
複製代碼
除了使用構造函數參數注入以外,還可使用 Setter 方法注入和成員變量注入,這些注入方式的效果都同樣:
// Setter 方法注入
@Component
public class Soldier {
private Weapon weapon;
@Autowired
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
}
// 私有成員變量注入
@Component
public class Soldier {
@Autowired
private Weapon weapon;
}
複製代碼
在上面的代碼中,咱們使用的是 @Autowired 來進行注入,也可使用 @Inject 來進行依賴注入。這兩個註解是等價的,能夠在代碼中互相替換,可是推薦在整個項目中保持統一。若是項目的依賴比較複雜,那麼代碼中就會充斥着這些註解,這也是使用註解配置的問題,大量的這樣的註解會讓代碼很差管。註解使用是最便利的,但也是最難管理的。
還有最後一種注入方式,使用 Java 代碼進行注入:
@Configuration
public class SoldierConfig {
@Bean
public Gun newGun() {
return new Gun();
}
@Bean
public Knife newKnife() {
return new Knife();
}
@Bean
public Soldier newSoldier() {
// 使用構造方法參數注入
return new Soldier(newGun());
}
@Bean
public Soldier newSoldier() {
// 使用 Setter方法參數注入
Soldier s = new Soldier();
s.setWeapon(newGun());
return s;
}
}
複製代碼
@ComponentScan 註解用來配置須要掃描的包的範圍,被 @Bean 註解的方法表示會獲得一個返回類型的 Bean,而後能夠直接經過調用相應方法爲 Bean 注入依賴。使用 Java 配置的好處是便可以不使用 XML 配置文件,又不會對代碼有侵入。上面的代碼中都是類之間的依賴,若是依賴的是普通的字面量或者一些容器類型,也能夠進行注入,注入的方式以下:
<bean id="soldier" class="cn.rayjun.springdemo.container.Soldier" >
<property name="name" value="Tom"/>
<property name="age" value="12" />
</bean>
複製代碼
配置的 value 會根據 Bean 中成員的類型自動進行轉換。還能夠注入容器類型的值:
<bean id="soldier" class="cn.rayjun.springdemo.container.Soldier" >
<property name="name" value="Tom"/>
<property name="age" value="12" />
<property name="foods">
<list>
<value>apple</value>
<value>orange</value>
<value>banana</value>
</list>
</property>
</bean>
複製代碼
Soldier 中須要注入一個 Weapon 類型的 Bean,如今 Gun 和 Knife 都實現了 Weapon,採用上面的方式注入時,就會報 UnsatisfiedDependencyException
異常,由於容器沒法決定要注入哪個。解決的方法有三種:
被 @Primary 註解的 Bean 會被優先注入,這樣就不會報錯:
@Component
public class Soldier {
private Weapon weapon;
@Autowired
public Soldier(Weapon weapon) {
this.weapon = weapon;
}
}
@Component
public class Knife implements Weapon{
public boolean attack() {
return false;
}
}
@Component
@Primary
public class Gun implements Weapon{
public boolean attack() {
return false;
}
}
複製代碼
還可使用 @Qualifier 來明確告訴容器使用的是哪一個 Bean。
@Component
public class Soldier {
private Weapon weapon;
@Autowired
public Soldier(@Qualifier("gun") Weapon weapon) {
this.weapon = weapon;
}
}
@Component
@Qualifier("knife")
public class Knife implements Weapon{
public boolean attack() {
return false;
}
}
@Component
@Qualifier("gun")
public class Gun implements Weapon{
public boolean attack() {
return false;
}
}
複製代碼
自定義註解稍微複雜點,後續再寫文章來講明。
在一些狀況下,會出現循環依賴,雖然這種狀況比較少,但仍是有可能會出現.
public class ClassA {
private ClassB classB;
public ClassA(ClassB classB) {
this.classB = classB;
}
}
public class ClassB {
private ClassA classA;
public ClassB(ClassA classA) {
this.classA = classA;
}
}
<bean id="classB" class="cn.rayjun.springdemo.springcontainer.cycle.ClassB">
<constructor-arg name="classA" ref="classA"/>
</bean>
<bean id="classA" class="cn.rayjun.springdemo.springcontainer.cycle.ClassA">
<constructor-arg name="classB" ref="classB"/>
</bean>
複製代碼
若是按照上面的配置,啓動的時候會報 UnsatisfiedDependencyException 異常,這就是循環依賴,解決的辦法也很簡單,就是把構造方法注入改爲 Setter 方法注入。
public class ClassA {
private ClassB classB;
public void setClassB(ClassB classB) {
this.classB = classB;
}
}
public class ClassB {
private ClassA classA;
public void setClassA(ClassA classA) {
this.classA = classA;
}
}
<bean id="classB" class="cn.rayjun.springdemo.springcontainer.cycle.ClassB">
<property name="classA" ref="classA"/>
</bean>
<bean id="classA" class="cn.rayjun.springdemo.springcontainer.cycle.ClassA">
<property name="classB" ref="classB"/>
</bean>
複製代碼
改爲 Setter 方法以後就能夠注入成功了,緣由就是構造函數注入和 Setter 方法注入的時機不一樣,構造函數須要在生成對象的時候就注入,這是 ClassA 和 ClassB 就產生了雞生蛋仍是蛋生雞的問題。而 Setter 方法注入這是在生成對象以後,那就能夠成功注入了。
Spring 容器的內容不少,上面僅僅介紹了 Spring 最核心的部分,這是 Spring 容器構建的基礎,下一篇會詳細介紹 Spring 的另外一大特性 AOP。關於容器的一些語法細節能夠去查詢官方文檔。
相關文章
關注微信公衆號,聊點其餘的