(三):全注解下的spring IOC

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的初始化流程
12.png

這張圖顯示了整個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的作用于如圖


123.png

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? '老‘:“年輕’}")
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市童太,隨后出現(xiàn)的幾起案子米辐,更是在濱河造成了極大的恐慌,老刑警劉巖书释,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翘贮,死亡現(xiàn)場離奇詭異,居然都是意外死亡爆惧,警方通過查閱死者的電腦和手機狸页,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來检激,“玉大人肴捉,你說我怎么就攤上這事∈迨眨” “怎么了齿穗?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長饺律。 經常有香客問我窃页,道長,這世上最難降的妖魔是什么复濒? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任脖卖,我火速辦了婚禮,結果婚禮上巧颈,老公的妹妹穿的比我還像新娘畦木。我一直安慰自己,他們只是感情好砸泛,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布十籍。 她就那樣靜靜地躺著蛆封,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勾栗。 梳的紋絲不亂的頭發(fā)上惨篱,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天,我揣著相機與錄音围俘,去河邊找鬼砸讳。 笑死,一個胖子當著我的面吹牛界牡,可吹牛的內容都是我干的簿寂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼欢揖,長吁一口氣:“原來是場噩夢啊……” “哼陶耍!你這毒婦竟也來了?” 一聲冷哼從身側響起她混,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泊碑,沒想到半個月后坤按,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡馒过,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年臭脓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腹忽。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡来累,死狀恐怖,靈堂內的尸體忽然破棺而出窘奏,到底是詐尸還是另有隱情嘹锁,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布着裹,位于F島的核電站领猾,受9級特大地震影響,放射性物質發(fā)生泄漏骇扇。R本人自食惡果不足惜摔竿,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望少孝。 院中可真熱鬧继低,春花似錦、人聲如沸稍走。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至梦裂,卻和暖如春似枕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背年柠。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工凿歼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冗恨。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓答憔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親掀抹。 傳聞我的和親對象是個殘疾皇子虐拓,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內容