1: 介紹
IOC: Inverse of Control(控制反轉),讀作反轉控制更好理解,它不是技術而是一種設計思想自脯,
將原本手動創(chuàng)建對象的控制權楼眷,交給spring框架來管理铲汪。
-
控制什么?:這里的控制就是創(chuàng)建對象的過程罐柳,傳統(tǒng)是我們直接使用new關鍵字讓程序
主動創(chuàng)建對象掌腰,而IOC由一個專門的容器來統(tǒng)一創(chuàng)建對象。 -
反轉张吉?: 我們先理解正控齿梁,更具上面的理解,正控就是我們自己負責對象的創(chuàng)建,那么
反轉就是不需要自己創(chuàng)建勺择,spring中需要依賴所在的容器來創(chuàng)建以及注入依賴的對象创南。 -
反轉了什么?: 反轉了獲取對象的過程,對象的創(chuàng)建和銷毀不再由程序控制省核,而是由
spring容器來控制稿辙。
總結:根據上面的解釋,控制反轉就是當創(chuàng)建一個對象時气忠,程序所依賴的對象由外部
(spring容器)傳遞給他邻储,而不是自己去創(chuàng)建(new)依賴的對象,因此可以說在對象如何獲取
它的依賴這件事上控制權被反轉了。
2:好處
-
實現(xiàn)對象間的解耦
IOC容器可以實現(xiàn)對象間的解耦笔刹,把創(chuàng)建和查找對象的控制權交給容器芥备,由容器進行對象的
組合,所以對象之間是松散耦合舌菜,這樣方便測試萌壳,也方便重復利用。
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:上面已經提到践美,這個注解說明這個類是程序的配置類洗贰,springboot啟動的時候會加載這個類里的配置內容。
@Bean:代表initUser方法返回的Object裝配到容器中陨倡,name定義了這個bean的名稱敛滋,如果沒有配置,則將方法名"initUser"作為bean的名稱裝配到容器玫膀。
獲取bean的實例
public static void main(String[] args) {
//使用springboot中的context加載AppConfig配置類的內容矛缨,
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源碼积暖,可以知道它可以配置多個參數,
//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配置類的內容括授,
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
//通過context獲取user實例
BussinessPerson person = (BussinessPerson) applicationContext.getBean(BussinessPerson.class);
person.service();
}
@Autowired:在spring中最常用的注解之一坞笙,他會根據屬性的類型找到對應的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的注入不會產生歧義性民傻。
4.2.3.1:用實際類名命名需要注入的變量名(這種方法一般不會使用迫悠,局限性大)
在上面程序中如果出現(xiàn)另一個Animal绿贞,就會造成程序運行錯誤,這時可以改變BussinessPerson中Animal變了的名字,spring會根據名字進行注入秒梳,名字為需要注入的類名剪个。
//定義人食店,實現(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來操作,他的參數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:帶參數的構造方法類的裝配
在一些情況下有些類只有帶參數的構造方法蜜暑,于是上述的注入方法就不能使用了,這個情況下我們可以使用@Autowired對構造方法的參數進行注入策肝。
@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的初始化和銷毀之众,例如拙毫,對數據庫進行操作時我們在使用完后希望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有一個參數為lazyInit,它默認是false,這樣會直接進行實例化bean参淹,true則是進行延遲初始化。乏悄、
@ComponentScan(basePackages = {"com.tian.learn.SpringBootLearn.beans"}, lazyInit = true)
5.2.1:Bean的初始化流程
這張圖顯示了整個IOC容器初始化bean的流程浙值,從這張圖中我們可以看到多個接口多個注解,
- BeanNameAware:在調用依賴注入后會調用檩小,
- BeanFactoryAware:在設置Bean工廠時會調用
- ApplicationContextAware:這個接口只有實現(xiàn)了ApplicationContext接口的容器才會調用开呐。
- BeanPostProcessor接口是針對所有的bean的,里面postProcessBeforeInitialization/postProcessAfterInitialization规求,這兩個分別在之前和之后調用筐付,
*@PostConstruct:方法注解。該注解標記的方法為自定義初始化方法阻肿,會在步驟中調用瓦戚。 - InitializingBean: 調用了自定義方法后會調用該接口,方法afterPropertiesSet
- @PreDestory: 自定義銷毀方法丛塌,與@PostConstruct方法注解使用方式一致较解。
- DisposableBean: 系統(tǒng)銷毀Bean的回調。
//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
在一定條件下我們需要參數配置等滿足條件后才進行Bean的初始化臊泌,例如數據庫的鏈接參數全部滿足后才初始化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)境、生產環(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? '老‘:“年輕’}")