1: 介紹
IOC: Inverse of Control(控制反轉(zhuǎn)),讀作反轉(zhuǎn)控制更好理解,它不是技術(shù)而是一種設(shè)計思想,
將原本手動創(chuàng)建對象的控制權(quán),交給spring框架來管理。
-
控制什么?:這里的控制就是創(chuàng)建對象的過程,傳統(tǒng)是我們直接使用new關(guān)鍵字讓程序
主動創(chuàng)建對象,而IOC由一個專門的容器來統(tǒng)一創(chuàng)建對象。 -
反轉(zhuǎn)?: 我們先理解正控,更具上面的理解,正控就是我們自己負責(zé)對象的創(chuàng)建,那么
反轉(zhuǎn)就是不需要自己創(chuàng)建,spring中需要依賴所在的容器來創(chuàng)建以及注入依賴的對象。 -
反轉(zhuǎn)了什么?: 反轉(zhuǎn)了獲取對象的過程,對象的創(chuàng)建和銷毀不再由程序控制,而是由
spring容器來控制。
總結(jié):根據(jù)上面的解釋,控制反轉(zhuǎn)就是當創(chuàng)建一個對象時,程序所依賴的對象由外部
(spring容器)傳遞給他,而不是自己去創(chuàng)建(new)依賴的對象,因此可以說在對象如何獲取
它的依賴這件事上控制權(quán)被反轉(zhuǎn)了。
2:好處
-
實現(xiàn)對象間的解耦
IOC容器可以實現(xiàn)對象間的解耦,把創(chuàng)建和查找對象的控制權(quán)交給容器,由容器進行對象的
組合,所以對象之間是松散耦合,這樣方便測試,也方便重復(fù)利用。
spring IOC容器:
spring IOC容器是一個管理bean的容器,在spring的定義中,它要求所有的IOC容器都需要實現(xiàn)接口
BeanFactory,它是一個接口,為我們提供了bean的獲取,判斷類型等方法。spring體系中BeanFactory
和ApplicationContext為最重要的接口射擊,ApplicationContext不僅實現(xiàn)了BeanFactory還實現(xiàn)了很
多別的接口,使其功能更強大,所以在我們使用spring IOC容器中,大部分使用的是ApplicationContext
接口的實現(xiàn)類。
3:Bean裝配方式
spring boot在啟動完成之前會加載配置,這些配置之中就有spring中的bean的配置,我們就通過幾種方式
來進行bean的配置。
3.1: @Configuration和@Bean一起使用,使用Java類來進行項目裝配。該方法一般用于加載第三方的bean類。
@Configuration
public class AppConfig {
@Bean(name = "user")
public User initUser(){
User user = new User();
user.setLastName("tian");
user.setAge(12);
return user;
}
}
@Configuration:上面已經(jīng)提到,這個注解說明這個類是程序的配置類,springboot啟動的時候會加載這個類里的配置內(nèi)容。
@Bean:代表initUser方法返回的Object裝配到容器中,name定義了這個bean的名稱,如果沒有配置,則將方法名"initUser"作為bean的名稱裝配到容器。
獲取bean的實例
public static void main(String[] args) {
//使用springboot中的context加載AppConfig配置類的內(nèi)容,
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
//通過context獲取user實例
User user = (User) applicationContext.getBean("user");
log(user.toString());
}
3.2:掃描裝配
springboot常用的裝配是掃描裝配,使用注解@Component和@ComponentScan。
3.2.1:
@Component("user")
public class User {
//使用@Value
@Value("song")
private String lastName;
@Value("12")
private Integer age;
@Value("false")
private Boolean boss;
//set get方法。
}
@Component:指定該User類作為bean裝配到容器,“user"表示裝配時這個類的名字,如果沒有”user"springboot會將該類的類名首字母小寫作為該類在容器中的名稱。
@Value:為User類的屬性賦值。
3.2.2:
@Configuration
@ComponentScan
public class AppConfig {
}
首先要先添加@Configuration注解,標記這個類是配置類,這樣才會在程序啟動過程中加載該類。
@ComponentScan::用這個類標記說明springboot會進行掃描,但springboot只會掃描AppConfig類所
在的包及其子包。查看@ComponentScan源碼,可以知道它可以配置多個參數(shù),
//basepackage指定掃描的包及其子包
@ComponentScan(basePackages = {"com.tian.learn.SpringBootLearn.beans"})
//basePackageClasses定義掃描的類
@ComponentScan(basePackageClasses = {User.class, Dog.class})
//excludeFilter排除滿足條件的bean,使用@Filter注解,使?jié)M足條件的bean不被裝配。
@ComponentScan(basePackages = {"com.tian.learn.SpringBootLearn.beans"},
excludeFilters = {@ComponentScan.Filter(classes = {Dog.class})})
4:依賴注入
前面講了springboot Bean的裝配,現(xiàn)在要解決的是類之間的依賴,這個依賴在spring IOC中稱為依賴注入。
4.1:@Autowired初探
eg:這是一個人使用動物的例子
//定義動物的接口
public interface Animal {
void use();
}
//定義一個dog,使用注解讓其稱為裝配的Bean
@Component
public class Dog implements Animal {
@Override
public void use() {
System.out.println("dog wang wang wang!!!");
}
}
//定義人的接口
public interface Person {
void service();
void setAnimal(Animal animal);
}
//定義人,實現(xiàn)人接口。
@Component("bussinessperson")
public class BussinessPerson implements Person {
@Autowired //springboot常用的依賴注入注解
private Animal animal = null;
@Override
public void service() {
animal.use();
}
@Override
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
//驗證
public static void main(String[] args) {
//使用springboot中的context加載AppConfig配置類的內(nèi)容,
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
//通過context獲取user實例
BussinessPerson person = (BussinessPerson) applicationContext.getBean(BussinessPerson.class);
person.service();
}
@Autowired:在spring中最常用的注解之一,他會根據(jù)屬性的類型找到對應(yīng)的Bean進行注入。這里Dog類是動物的一種,所以spring會自動講Dog類注入給BussinessPerson類。
4.2: @Autowired詳解
4.2.1:使用
@Autowired可以使用在變量上也可以使用在方法上
//定義人,實現(xiàn)人接口。
@Component("bussinessperson")
public class BussinessPerson implements Person {
/****/
@Override
@Autowired
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
4.2.2: 可為NULL標注
使用@Autowired默認必須找到Bean進行注入,如果標注的屬性可以為NULL,需要使用@Autowired的required屬性標注,false為可以為null, 默認為true
//定義人,實現(xiàn)人接口。
@Component("bussinessperson")
public class BussinessPerson implements Person {
@Autowired(required=false) //springboot常用的依賴注入注解
private Animal animal = null;
/***...**/
}
}
4.2.3:歧義性
歧義性:因為@Autowried自動注入的規(guī)則,當多個相同類型(type,eg:上面程序除了Dog或者可能還有Cat)的類同時存在與spring IOC中時,運行程序就會報錯,因為spring不知道該注入哪一個。上面程序因為只有一個Dog,所以對Animal的注入不會產(chǎn)生歧義性。
4.2.3.1:用實際類名命名需要注入的變量名(這種方法一般不會使用,局限性大)
在上面程序中如果出現(xiàn)另一個Animal,就會造成程序運行錯誤,這時可以改變BussinessPerson中Animal變了的名字,spring會根據(jù)名字進行注入,名字為需要注入的類名。
//定義人,實現(xiàn)人借口。
@Component("bussinessperson")
public class BussinessPerson implements Person {
@Autowired //springboot常用的依賴注入注解
private Animal dog = null; //將Dog類名寫到這里,dog小寫
}
4.2.3.2: @Primary和@Qualifier
@Primary:優(yōu)先,告訴spring容器當發(fā)現(xiàn)同類型的Bean時,優(yōu)先使用我進行注入。
@Component
@Primary
public class Cat implements Animal {
@Override
public void use() {
System.out.println("cat zhua laoshu");
}
}
@Qualifier::有時候@Primary也會存在在多個相同類型的Bean上,這時會又造成歧義性,我們可以使用@Quefilier加上@Autowired來操作,他的參數(shù)value需要一個字符串去定義,這個字符串為Bean的名稱,Bean的名稱在spring IOC中是唯一的,這樣就沒有了歧義性。
//定義人,實現(xiàn)人借口。
@Component("bussinessperson")
public class BussinessPerson implements Person {
@Autowired //springboot常用的依賴注入注解
@Qualifier("dog")
private Animal animal = null; //將Dog類名寫到這里,dog小寫
}
4.2.4:帶參數(shù)的構(gòu)造方法類的裝配
在一些情況下有些類只有帶參數(shù)的構(gòu)造方法,于是上述的注入方法就不能使用了,這個情況下我們可以使用@Autowired對構(gòu)造方法的參數(shù)進行注入。
@Component("bussinessperson")
public class BussinessPerson implements Person {
private Animal animal = null;
//使用兩個注解和上面的方式一樣消除歧義性。
public BussinessPerson(@Autowired @Qualifier("dog") Animal animal){
this.animal = animal;
}
@Override
public void service() {
animal.use();
}
@Override
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
5:Bean的生命周期
在一些情況下,我們需要自己掌控spring中bean的初始化和銷毀,例如,對數(shù)據(jù)庫進行操作時我們在使用完后希望close它自己。這就需要我們詳細了解一下spring中bean的生命周期,spring中bean的生命周期可大致分為,bean的定義、bean的初始化、bean的生存周期、bean的銷毀4個部分
5.1:Bean的定義
- spring通過配置的@ComponentScan和@Component進行資源定位。
- 找到資源進行解析,并將這些信息保存起來。這時還沒有初始化bean,沒有bean的實例,只是保存了bean的信息。
- 然后將bean的信息發(fā)布到spring IOC容器中,此時IOC容器中也只有Bean的定義,沒有bean的實例。
5.2:Bean的初始化
在默認的情況下進行完5.1的3個步驟后直接就會執(zhí)行Bean的初始化過程,創(chuàng)建bean的實例,并完成依賴注入,在這里我們可以控制bean讓其不進行實例化,之后使用的時候在進行初始化。
@ComponentScan有一個參數(shù)為lazyInit,它默認是false,這樣會直接進行實例化bean,true則是進行延遲初始化。、
@ComponentScan(basePackages = {"com.tian.learn.SpringBootLearn.beans"}, lazyInit = true)
5.2.1:Bean的初始化流程

這張圖顯示了整個IOC容器初始化bean的流程,從這張圖中我們可以看到多個接口多個注解,
- BeanNameAware:在調(diào)用依賴注入后會調(diào)用,
- BeanFactoryAware:在設(shè)置Bean工廠時會調(diào)用
- ApplicationContextAware:這個接口只有實現(xiàn)了ApplicationContext接口的容器才會調(diào)用。
- BeanPostProcessor接口是針對所有的bean的,里面postProcessBeforeInitialization/postProcessAfterInitialization,這兩個分別在之前和之后調(diào)用,
*@PostConstruct:方法注解。該注解標記的方法為自定義初始化方法,會在步驟中調(diào)用。 - InitializingBean: 調(diào)用了自定義方法后會調(diào)用該接口,方法afterPropertiesSet
- @PreDestory: 自定義銷毀方法,與@PostConstruct方法注解使用方式一致。
- DisposableBean: 系統(tǒng)銷毀Bean的回調(diào)。
//eg
@Component("bussinessperson")
public class BussinessPerson implements Person, BeanNameAware, BeanFactoryAware,
ApplicationContextAware, InitializingBean, DisposableBean {
private Animal animal = null;
public BussinessPerson(){
}
@Override
public void service() {
animal.use();
}
@Override @Autowired @Qualifier("dog")
public void setAnimal(Animal animal) {
this.animal = animal;
SpringBootLearnApplication.log("延遲初始化");
}
@PostConstruct
public void init(){
System.out.println(this.getClass().getSimpleName() + " init ");
}
@PreDestroy
public void destory1(){
System.out.println(this.getClass().getSimpleName() + " destory1");
}
@Override
public void setBeanName(String name) {
System.out.println(this.getClass().getSimpleName() + " setBeanName: " + name );
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println(this.getClass().getSimpleName() +
" setBeanFactory: " + beanFactory.toString());
}
@Override
public void destroy() throws Exception {
System.out.println(this.getClass().getSimpleName() + " destory");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println(this.getClass().getSimpleName() + " afterPropertiesSet");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println(this.getClass().getSimpleName() + " setApplicationContext: " +
applicationContext.getApplicationName());
}
}
注意:當我們使用第三方的Bean的時候,可以使用@Bean定義自定義的初始化和銷毀方法
@Bean(initMethod="init", destoryMethod="destory1")
6:使用屬性文件
spring之前一致使用的是配置文件有properties、xml等,spring boot也同樣可以使用,這里我們直接使用
application.properties文件
driver.name=com.mysql.jdbc.Driver
driver.url=jdbc:mysql://localhost:8080/
driver.username=tian
driver.password=123
在bean中使用配置文件,使用@Value注解和${}占位符進行屬性文件的讀取
@Component
public class MySqlDataSource implements Condition {
@Value("${driver.name}")
private String name = null;
@Value("${driver.url}")
private String url = null;
@Value("${driver.password}")
private String password = null;
}
@ConfigurationProperties:這個注解可以減少我們讀取配置文件時的配置,同樣是上述代碼,使用該注解為
@Component
@ConfigurationProperties("driver")
public class MySqlDataSource implements Condition {
private String name = null;
private String url = null;
private String password = null;
}
該注解中的字符串driver會與bean的屬性名組成全限定名從配置文件中查找。
@PropertySource:該注解可以讓我們的springboot加載其他的配置文件.
@SpringBootApplication
@PropertySource(value = {"classpath:jdbc.properties"}, ignoreResourceNotFound = true)
public class SpringBootLearnApplication {}
classpath:代表從類文件路徑下找屬性文件jdbc.properties,
ignoreResourceNotFound:從名字上可以看出,這個是忽略屬性文件找不到的問題。
7:條件裝配Bean
在一定條件下我們需要參數(shù)配置等滿足條件后才進行Bean的初始化,例如數(shù)據(jù)庫的鏈接參數(shù)全部滿足后才初始化bean,這里spring給我們提供了@Conditional注解配合接口Condition來完成。
condition接口來完成對比,查看是否能創(chuàng)建
public class ConditionalOne implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
使用兩個注解@Bean和@Conditional來完成,這樣就能更具matches的返回值判斷時候初始化這個Bean
@Bean
@Conditional(ConditionalOne.class)
public MySqlDataSource getDataSource(){
MySqlDataSource mySqlDataSource = new MySqlDataSource();
mySqlDataSource.setName("tian");
mySqlDataSource.setPassword("123");
mySqlDataSource.setUrl("localhost");
return mySqlDataSource;
}
7:Bean的作用域
在IOC中默認Bean都是單利的,springboot還使用在web中,所以bean的作用于如圖

8:@Profile注解
在項目中我們面臨不同的環(huán)境,開發(fā)環(huán)境、測試環(huán)境、生產(chǎn)環(huán)境等,不同的環(huán)境配置不同,這就需要不同的配置文件,@Profile就是解決這個問題, 這里不在做詳細描述。
9:引入XML配置Bean
之前的spring開發(fā)大部分都是使用xml配置bean,現(xiàn)在還有一些框架還在使用xml,這樣我們在開發(fā)過程中使用該框架需要使用xml方式來實現(xiàn)。
@importSource:引入xml來配置Bean
@Configuration
@ComponentScan(basePackages = {"com.tian.learn.SpringBootLearn.beans"} )
@importSource(value={"classpath:spring-other.xml"})
public class AppConfig {}
10:spring EL
spring El可以功能更強大的為spring bean的屬性賦值
- 賦值字符串 @Value("#{'tian'}")
- 占位符: @Value("${driver.name}), 他會讀取上下文屬性值來裝配到bean中。
- 獲取其他bean的屬性 @Value("#{PersionBean.name}")
- 運算 @Value("#{1+2}")
- 三元運算 @Value("#{age>50? '老‘:“年輕’}")