全注解下的Spring IoC
1.什麼是Bean?
在Spring中把每一個需要管理的對象稱為Spring Bean(簡稱Bean)
2.什麼是IoC容器?
Spring IoC容器是一個管理Bean的容器,它要求所有的IoC容器都需要實作接口BeanFactory。
下面是BeanFactory接口源碼:
package org.springframework.beans.factory;
public interface BeanFactory {
/**
* 用來引用一個執行個體,或把它和工廠産生的Bean區分開,就是說,如果一個FactoryBean的名字為a,那麼,&a會得到那個Factory
*/
String FACTORY_BEAN_PREFIX = "&";
/*
* 四個不同形式的getBean方法,擷取執行個體
*/
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
boolean containsBean(String name); // 是否存在
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 是否為單執行個體
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 是否為原型(多執行個體)
boolean isTypeMatch(String name, Class<?> targetType)
throws NoSuchBeanDefinitionException;// 名稱、類型是否比對
Class<?> getType(String name) throws NoSuchBeanDefinitionException; // 擷取類型
String[] getAliases(String name);// 根據執行個體的名字擷取執行個體的别名
}
預設情況下,Bean都是以單例形式存在,即使用getBean方法傳回的都是同一對象。
3.如何從IoC容器中取出Bean
通過上面的源碼可以看到,getBean方法有按類型(type)擷取Bean的,也有按名稱(name)擷取Bean的,這對于後面介紹的Spring依賴注入很重要。
4.ApplicationContext和BeanFactory的關系
由于BeanFactory的功能不夠強大,Spring在BeanFactory的基礎上,設計了一個ApplicationContext接口,是BeanFactory的子接口,他們的關系如下(圖檔來源于網絡):

在Spring Boot中,主要通過注解方式裝配Bean到Spring IoC容器中(也支援XML).
5.裝配Bean注入IoC容器(通過兩個執行個體了解)
(1)@Bean
首先定義一個Java簡單對象,檔案User.java,代碼如下:
package com.springboot.chapter3.pojo;
import java.util.LongSummaryStatistics;
/**
* Created by lizeyang on 2019/5/7.
*/
public class User {
private Long id;
private String userName;
private String note;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
然後再定義一個Java配置檔案AppConfig.java,代碼如下:
package com.springboot.chapter3.config;
import com.springboot.chapter3.pojo.User;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by lizeyang on 2019/5/7.
*/
@Configuration
public class AppConfig {
@Bean(name="user")
public User initUser(){
User user = new User();
user.setId(1L);
user.setUserName("user_name_1");
user.setNote("note_1");
return user;
}
}
其中@Configuration代表這是一個Java配置檔案,Spring容器會根據它來生成IoC容器裝配Bean。@Bean代表将initUser方法傳回的POJO傳回到IoC容器中,屬性name定義這個Bean的名稱,若不配置,方法名即為Bean名稱。
最後,使用AnnotationConfigApplicationContext建構IoC容器,代碼如下:
package com.springboot.chapter3.config;
import com.springboot.chapter3.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* Created by lizeyang on 2019/5/7.
*/
public class IoCTest {
public static void main(String[] args){
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User user = ctx.getBean(User.class);
System.out.println(user.getId());
}
}
運作IoCTest結果如下:
顯然,配置在配置檔案中名稱為user的Bean已經裝配到IoC容器中,并且可以通過getBean方法擷取對應的Bean,并将Bean的屬性輸出出來。
(2)@ComponentScan
除了@Bean,還可以使用@Component和@ComponentScan,@Component是标明哪個類被掃描進入Spring IoC容器,@ComponentScan是标明采用何種政策掃描裝配Bean。
首先,在包com.springboot.chapter3.config内建立UserCpoy.java檔案,代碼如下:
package com.springboot.chapter3.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* Created by lizeyang on 2019/5/7.
*/
@Component("UserCopy")
public class UserCopy {
@Value("1")
private Long id;
@Value("user_name_1")
private String userName;
@Value("note_1")
private String note;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
修改配置檔案為:AppConfigCopy,代碼為:
package com.springboot.chapter3.config;
import com.springboot.chapter3.pojo.User;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* Created by lizeyang on 2019/5/7.
*/
@Configuration
@ComponentScan
public class AppConfigCopy {
// public User initUser(){
// User user = new User();
// user.setId(1L);
// user.setUserName("user_name_1");
// user.setNote("note_1");
// return user;
// }
}
運作IoCTest,結果與上面相同。
6.什麼是依賴注入?
依賴注入就是Bean之間的依賴,最常用的是@Autoeired注解,它會根據屬性的類型(type)找到對應的Bean進行注入(還記得BeanFactory裡面getBean方法的兩種方式嗎?)
另外。可以通過@Primary修改多個不同類型的Bean的優先權,或者将@Autowired和@Qualifier結合使用,通過類型和名稱一起找到Bean。
7.Spring IoC初始化和銷毀Bean過程——Bean生命周期
分為Bean定義、Bean初始化、Bean生存期和Bean的銷毀4個部分。
下圖為Spring Bean的初始化流程:
下面是Spring Bean的生命周期:
8.屬性檔案使用——application.properties
在Spring Boot中,我們一般先在Maven配置檔案中加載依賴,Spring Boot将建立讀取屬性檔案的上下文。如:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
有了依賴,就可以直接使用application.properties檔案,通過其機制讀取到上下文中,之後便可引用它。如:
spring.datasource.url=jdbc:mysql://localhost:3306/toutiao?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=nowcoder
mybatis.config-location=classpath:mybatis-config.xml
#logging.level.root=DEBUG
引用有兩種方法,一種是Spring表達式,如通過@Value注解,使用${...}占位符讀取配置在屬性檔案中的内容,如:
@Component
public class DataBaseProperties{
@Value("${database.drivename}")
private String driveName = null;
}
另一種方法就是使用@ConfigurationProperties注解,如:
@Component
@ConfigurationProperties("database")
public class DataBaseProperties{
......
}
9.單例(isSingleton)和原型(isPrototype)的差別
我們通過一段代碼來進行比較兩者差別:
@Component
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ScopeBean{
...
}
//測試作用域
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
ScopeBean scopeBean1 = ctx.getBean(ScopeBean.class);
ScopeBean scopeBean2 = ctx.getBean(ScopeBean.class);
System.out.println(scopeBean1 == scopeBean2);
在第二行代碼注釋之後,測試結果為true,說明scopeBean1和scopeBean2變量都指向同一個執行個體,即為單例。
若将注釋消去,則設定Bean的作用域為prototype,讓IoC容器每次擷取Bean時,都建立一個Bean的執行個體傳回給調用者。
10.XML配置Bean
盡管Sping Boot建議使用注解和掃描配置Bean,但也不拒絕XML配置Bean.
通過一個例子來了解XML配置Bean:
先建立一個松鼠實作類,代碼如下:
package com.springboot.other.pojo;
import com.springboot.chapter3.pojo.definition.Animal;
public class Squirrel implements Animal {
@Override
public void use() {
System.out.println("松鼠可以采集松果");
}
}
這個檔案所在的包不在@ComponentScan定義的掃描包com.springboot.chapter3.*之内,也沒有标注@Component,是以不會被掃描機制所裝配,我們使用XML檔案來裝配,代碼如下:
<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="squirrel" class="com.springboot.other.pojo.Squirrel"/>
</beans>
最後,裝配XML定義的Bean,代碼如下:
@Configuration
@ComponentScan(basePackages = "com.springboot.chapter3.*")
@ImportResource(value = {"classpath:spring-other.xml"})
public class AppConfig {
......
}
這樣就可以引入對應的XML,進而将XML定義的Bean裝配到IoC容器中。
11.使用Spring EL(了解即可)
Spring EL是一種表達式語言,通過Spring EL可以擁有更強大的運算規則來裝配Bean,最常用的是讀取屬性檔案的值,如:
@Value("#{3.14}")
private float pi;
@Value("#{1+2}")
private int run;
總結
Spring Boot關于IoC的部分基本就是這些,本節代碼已上傳github:
https://github.com/lizeyang18/SpringBoot-2.x/tree/master/Chapter3