0.引言
大家可能不知道什么是IoC,但(dan)只要(yao)你(ni)接(jie)觸過Java,多多少(shao)少(shao)都會接(jie)觸到(dao)Spring框架,比如用于(yu)web開(kai)發(fa)的Spring MVC、持久層開(kai)發(fa)的Spring Data、微服務開(kai)發(fa)Spring Cloud,以及經典的“輪(lun)子(zi)的輪(lun)子(zi),框架的框架”——Spring Boot。我(wo)們把(ba)這一大坨東(dong)西就(jiu)叫(jiao)做Spring全家桶。
今天給大家介紹的,就是(shi)spring全家桶中相當基礎的概(gai)念:IoC
本文我(wo)們簡單的介紹下概念以及實(shi)現方(fang)法,后(hou)面有機會來分(fen)(fen)享高階應用以及源碼分(fen)(fen)析(xi)
1 什么是IoC?為什么要用IoC?
IoC,即控制(zhi)反轉——是Spring全家(jia)桶各個功能模塊的基礎(chu)
每個功能模塊都依賴于對象而發揮作用,IoC的本質就是幫助創建對象。
↑↑↑ 這句(ju)話聽起來真的迷惑,創建對(dui)象還要(yao)幫助(zhu)?那我不是要(yao)多(duo)少就new多(duo)少?
能(neng)有這個(ge)疑惑的(de)讀者,可能(neng)跟我一樣年輕,沒咋改(gai)過那種(zhong)耦合度很高的(de)代碼。尤其(qi)是現在利用(yong)spring開(kai)發的(de)絕大(da)部分大(da)型工(gong)程,耦合度都很高。
舉個(ge)簡單的(de)例(li)子:在業務層,我(wo)定義了一(yi)(yi)個(ge)類,比如說是用來計(ji)算(suan)某個(ge)幾何圖形的(de)屬性,里(li)面(mian)可能有計(ji)算(suan)面(mian)積啊(a)(a)、計(ji)算(suan)周(zhou)長啊(a)(a)這(zhe)些(xie)方法。同時肯定會有一(yi)(yi)個(ge)實例(li)化的(de)圖形類對象,我(wo)們(men)就叫他(ta)diagram。那你寫出(chu)來可能就是下面(mian)這(zhe)樣:
private Diagram diagram = new Diagram( );
這個(ge)業務層的類(lei)(lei)比如定(ding)義為DiagramService0,那(nei)定(ding)義Diagram這個(ge)類(lei)(lei)的數據層就(jiu)被(bei)DiagramService0調(diao)用了(le)。一些大型(xing)的工程里,我們(men)極有可能寫了(le)Diagram這個(ge)類(lei)(lei),運用到(dao)很多地方,可能不僅(jin)僅(jin)是(shi)DiagramService0。
也許換個(ge)(ge)使用(yong)場(chang)景,比如我又需(xu)(xu)要(yao)算圖(tu)形有幾個(ge)(ge)內角,但是這個(ge)(ge)需(xu)(xu)求又只在某個(ge)(ge)場(chang)景用(yong)得(de)上。那我是不是沒必要(yao)專(zhuan)門(men)把這個(ge)(ge)功能集(ji)成到DiagramService0,更好的辦法顯然(ran)是寫一個(ge)(ge)繼(ji)承(cheng)類,比如說是DiagramService1。
如果有一天,Diagram這個(ge)類要(yao)(yao)升級要(yao)(yao)迭代(dai)(就比(bi)如最簡單的改名,改成DiagramNew),我們就需要(yao)(yao)去維(wei)護(hu)DiagramService0、DiagramService1。這只是(shi)一個(ge)簡單的例子,事(shi)實上你(ni)需要(yao)(yao)維(wei)護(hu)的東(dong)西在工程的方(fang)方(fang)面(mian)面(mian),非常的不方(fang)便。
那怎么解決呢?
——是不是我們可(ke)(ke)以考慮,不由(you)自己主動(dong)去new對(dui)象(xiang),交(jiao)給外部來(lai)創(chuang)建(jian)對(dui)象(xiang)。不管我的(de)類怎么(me)迭代,它(ta)只(zhi)要按照我寫的(de)這個類去對(dui)應的(de)地(di)方創(chuang)建(jian)對(dui)象(xiang)就可(ke)(ke)以了(le)
這就是控制反轉的核心,我們把對象創建的權利,從我們的程序轉移到外部,從而解(jie)決我們在開發中(zhong)修(xiu)改(gai)一(yi)處(chu)代(dai)碼(ma),往往要連帶著修(xiu)改(gai)很多關聯代(dai)碼(ma)的問題。這個(ge)外部,就是我們的IoC容器(對,IoC的本(ben)質就是一(yi)個(ge)容器)
學術一(yi)點的(de)說(shuo):IOC容器(qi)負責(ze)對象(xiang)的(de)創建、初始化等一(yi)系(xi)列工作,被(bei)(bei)創建或(huo)被(bei)(bei)管理的(de)對象(xiang)在IOC容器(qi)中統稱為Bean
2 如何實現IoC
其實上實現IoC的辦法并不少,比較常用的就有:1.基于注解(jie)(jie) 2.基于xml文(wen)件 3.掃包+注解(jie)(jie)
package wy.springboot01.domain;
import lombok.Data;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
/**
* springboot項目啟動的時候,自動將application.yml內容加載到實體對象中
*/
@Data
//將實體類交給spring管理,自動掃描
@Component
public class User {
private Integer uid;
private String uname;
private String password;
private ArrayList<String> addrs;
}
package wy.springboot01.IoC;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import wy.springboot01.domain.User;
public class IoCTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("wy.springboot01.domain");
//context.getBean(User.class)利用IoC生成對象
System.out.println(context.getBean(User.class));
}
}
3 對IoC創建對象的參數進行賦值
當然,如果你將這(zhe)(zhe)個(ge)(ge)IoC創建的User對象保存下來,就(jiu)可(ke)以用set對這(zhe)(zhe)些參(can)數進行賦值(zhi),但是我們往(wang)往(wang)喜歡給他(ta)們來上一個(ge)(ge)默(mo)認值(zhi),怎么做呢?
方法一:在參(can)數(shu)上加@Value注解(jie)
這種方(fang)法非常簡單,如下:
@Data
//將實體類交給spring管理,自動掃描
@Component
public class User {
@Value("132456")
private Integer uid;
@Value("wy")
private String uname;
@Value("asd")
private String password;
@Value("Beijing, Sichuan, Nanchang")
private List<String> addrs;
public User() {
}
public User(Integer uid, String uname, String password, ArrayList<String> addrs) {
this.uid = uid;
this.uname = uname;
this.password = password;
this.addrs = addrs;
}
}
方(fang)法(fa)二:導入(ru)一個配置類
通(tong)常我們還是推(tui)薦這(zhe)個方法,如(ru)下更改代(dai)碼(ma):
package wy.springboot01.domain;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
/**
* springboot項目啟動的時候,自動將application.yml內容加載到實體對象中
*/
@Data
//將實體類交給spring管理,自動掃描
@Component
//加載配置內容,設定配置前綴,注意:prefix參數不支持小駝峰原則,必須全部小寫
@ConfigurationProperties(prefix = "user")
public class User {
private Integer uid;
private String uname;
private String password;
private ArrayList<String> addrs;
public User() {
}
public User(Integer uid, String uname, String password, ArrayList<String> addrs) {
this.uid = uid;
this.uname = uname;
this.password = password;
this.addrs = addrs;
}
}
顯然我們通過@ConfigurationProperties這個注解去加載了配置類,我們只需要在與java這個路徑平行的resources路徑下新增一個yml文件當做配置即可,比如我們就叫做application.yml(通常這個路徑會有一個默認的application.properties,但我習慣了用yml做配置這里就不寫application.properties做配置的方法了,其實也很簡單感興趣的可以自行去搜索一下),我們這么來配置它(一定要注意冒號后面的空格):
#配置屬性并加載到User實體中
user:
uid: 1546879
uname: wuyu
password: wuyu1999
addrs:
- Beijing
- Sichuan
- Nanchang
可以看到我們的前綴叫做user,所以我們在User中使用@ConfigurationProperties注解時,參數要填 prefix="user"。這么一來,這些默認值就會在運行的時候被加載進去,此時如果你再次運行IoCTest,就會輸出有上述值的對象(需要注意的是,很多時候IoC創建對象這種方式有可能不生效,還是建議使用@Value)
還有一種特殊情況:如果一個類的參數包含了另一個類的對象,這個時候我們就要用到依賴注入@Autowired:
@Data
//將實體類交給spring管理,自動掃描
@Component
public class SuperUser {
@Value(“777”)
private String info1;
@Value(“666”)
private String info2;
@Autowired
private User user;
}
當(dang)時(shi)我試到這一(yi)步的時(shi)候(hou)產生了(le)一(yi)個疑問(wen),有的時(shi)候(hou)我們為(wei)了(le)好區(qu)分或者說為(wei)了(le)好給讀代碼的人(ren)信(xin)息,可能我這個User類在注入Bean的時(shi)候(hou)(即@Component這一(yi)步)我會加一(yi)些參(can)數,打個比方我會寫成(cheng)@Component("ctyunUser"),那這個User類注入到bean里面的名(ming)稱(cheng)就叫ctyunUser。
這個時候我的SuperUser的user參數還找不找得到User這個類呢?大家可以自己去試一下,實際上是找的到的,因為@Autowired是默認使用參數的類型去找,在這(zhe)個(ge)例子(zi)里他是默認去找Bean中有沒有User這(zhe)個(ge)類,實際上是有的(de)只不過它的(de)別名(ming)叫做ctyunUser。
如果我們要用名稱去找,就需要在@Autowired下面再加一個@Qualifier(“ctyunUser”),這樣他就會根據名稱去找,如果這個時候User的@Component的參數沒有寫或寫的不是ctyunUser,創建對象就會報錯。
這(zhe)一(yi)塊大家可以自己去試一(yi)試,還是挺有意思的
以上就(jiu)是(shi)IoC非常初(chu)級的(de)一個用(yong)法,下一篇我們會(hui)介紹一些進(jin)階的(de)用(yong)法和更多有趣的(de)案例