(三):全注解下的spring IOC

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

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


123.png

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? '老‘:“年輕’}")
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容