天天看點

spring 依賴注入_【Spring基礎】JavaBean的配置與依賴注入

spring 依賴注入_【Spring基礎】JavaBean的配置與依賴注入

0 前言

Spring的核心就是提供了一個IoC(Inversion of Control)容器,它可以管理所有輕量級的JavaBean元件,提供的底層服務包括元件的生命周期管理、配置群組裝服務、AOP支援,以及建立在AOP基礎上的聲明式事務服務等。

本本主要展示IoC容器對JavaBean裝配,以及依賴的注入的幾種方式。

看本文之前請務必學習JAVA基礎。

1 傳統的實作方式

先來看下面這個例子:

class Person {
    private String name;

    public Person(){};
    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

class Dance {
    public void do_dance(Person person){
        System.out.println(person.getName()+" is dancing!");
    }
}

class Sing {
    public void do_sing(Person person){
        System.out.println(person.getName()+" is sing!");
    }
}

public class Test {
    public static void main(String[] args) {
        Person person=new Person("Ming");
        new Sing().do_sing(person);
        new Dance().do_dance(person);
    }
}
           

Dance與Sing元件的執行個體化都依賴于Person元件的執行個體化,通過主動建構的方式實作元件的執行個體化以及依賴關系的建構。在大型項目中,這種寫法有以下缺點:

  1. 一個元件如果要使用另一個元件,必須先知道如何正确地建立它,管理起來麻煩。
  2. 随着更多的元件被引入,元件的依賴層級變深,組織起來太複雜。
  3. 有些元件需要銷毀以便釋放資源,但如果該元件被多個元件共享,如何確定它的使用方都已經全部被銷毀?
  4. 元件互相依賴,測試困難。
如果一個系統有大量的元件,其生命周期和互相之間的依賴關系如果由元件自身來維護,不但大大增加了系統的複雜度,而且會導緻元件之間極為緊密的耦合,繼而給測試和維護帶來了極大的困難。

為解決這個問題,提出了IoC模式,在IoC模式下,控制權發生了反轉,即從應用程式轉移到了IoC容器,所有元件不再由應用程式自己建立和配置,而是由IoC容器負責,這樣,應用程式隻需要直接使用已經建立好并且配置好的元件。為了能讓元件在IoC容器中被“裝配”出來,需要某種“注入”機制,根據操作的不同可以分為下文的幾種。

2 基于XML配置的依賴注入

在XML中定義Bean并交代元件之間的依賴關系。

Pom配置如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itranswarp.learnjava</groupId>
    <artifactId>spring-ioc-appcontext</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <java.version>11</java.version>

        <spring.version>5.2.3.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>
</project>
           

代碼結構如下:

spring 依賴注入_【Spring基礎】JavaBean的配置與依賴注入

對應的代碼如下:

Person

package model;

public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
           

Dance

package model;

public class Dance {
    Person person;

    public void setPerson(Person person){
        this.person=person;
    }

    public void do_dance(){
        System.out.println(person.getName()+" is dancing!");
    }
}
           

Sing

package model;

public class Sing {
    Person person;

    public void setPerson(Person person) {
        this.person = person;
    }

    public void do_sing(){
        System.out.println(person.getName()+" is sing!");
    }
}
           

Dance、Sing

自己并不會建立

Person

,而是等待外部通過

set

方法來注入一個

Person

Bean的定義交給XML

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="dance" class="model.Dance">
        <property name="person" ref="person"/>
    </bean>

    <bean id="sing" class="model.Sing">
        <property name="person" ref="person"/>
    </bean>

    <bean id="person" class="model.Person">
        <property name="name" value="Ming"/>
    </bean>
</beans>
           

Main中通過context來通路XML檔案

import model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("Application.xml");

        Dance dance=context.getBean(Dance.class);
        dance.do_dance();

        Sing sing=context.getBean(Sing.class);
        sing.do_sing();

        Person person=context.getBean(Person.class);
        person.setName("Tom");

        Dance dance_=context.getBean(Dance.class);
        dance_.do_dance();

        Sing sing_=context.getBean(Sing.class);
        sing_.do_sing();
    }
}
           

運作結果

Ming is dancing!
Ming is sing!
Tom is dancing!
Tom is sing!
           

使用XML配置的優點是所有的Bean都能一目了然地列出來,并通過配置注入能直覺地看到每個Bean的依賴。它的缺點是寫起來非常繁瑣,每增加一個元件,就必須把新的Bean配置到XML中。

3 基于Java配置的依賴注入

檔案結構如下:

spring 依賴注入_【Spring基礎】JavaBean的配置與依賴注入

基于Java的配置檔案(AppConfig),替代了XML

Person

package model;

public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
           

Dance

package model;

public class Dance {
    Person person;

    public Dance(Person person) {
        this.person=person;
    }

    public void do_dance(){
        System.out.println(person.getName()+" is dancing!");
    }
}
           

Sing

package model;

public class Sing {
    Person person=new Person();

    public void setPerson(Person person){
        this.person=person;
    }

    public void do_sing(){
        System.out.println(person.getName()+" is sing!");
    }
}
           

APPConfig

import model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class APPConfig {
    @Bean
    public Person person(){
        Person person=new Person();
        person.setName("Ming");
        return person;
    }

    @Bean   //通過構造器的注入依賴
    public Dance dance(){
        return new Dance(person());
    }

    @Bean   //通過set注入依賴
    public Sing sing(){
        Sing sing= new Sing();
        sing.setPerson(person());
        return sing;
    }
}
           

這裡的APPConfig相當于XML,定義了每個bean,以及各個bean之間的依賴關系。

  • @Bean

    用于标記JavaBean對象
  • @Configuration

    用于标記這個類是個配置類,主函數中的

    AnnotationConfigApplicationContext

    必須傳入一個标注了

    @Configuration

    的類名
注意: 代碼中同時展示了兩種依賴注入的方式,一種是set方法,一種是帶參構造器。

Main方法

import model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(APPConfig.class);
        Dance dance=context.getBean(Dance.class);
        dance.do_dance();

        Sing sing=context.getBean(Sing.class);
        sing.do_sing();

        Person person=context.getBean(Person.class);
        person.setName("Tom");

        Dance dance_=context.getBean(Dance.class);
        dance_.do_dance();

        Sing sing_=context.getBean(Sing.class);
        sing_.do_sing();
    }
}
           

運作結果

Ming is dancing!
Ming is sing!
Tom is dancing!
Tom is sing!
           

從運作結果可以看出,大家使用的是同一個Person對象。

4 基于注解搜尋的依賴注入

不再需要單獨的XML或者APPConfig來告訴容器哪些是Bean以及他們之間的關系,可以通過注解在程式中定義,讓程式自動搜尋識别。

Person

package model;

import org.springframework.stereotype.Component;

@Component
public class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
           

Dance

package model;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Dance {
    @Autowired      //替代了set
    Person person;

    public void do_dance(){
        System.out.println(person.getName()+" is dancing!");
    }
}
           

Sing

package model;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Sing {
    @Autowired
    Person person;

    public void do_sing(){
        System.out.println(person.getName()+" is sing!");
    }
}
           
  • @Component

    注解就相當于定義了一個Bean,它有一個可選的名稱,預設是

    mailService

    ,即小寫開頭的類名。
  • @Autowired

    就相當于把指定類型的Bean注入到指定的字段中。

    @Autowired

    大幅簡化了注入,因為它不但可以寫在

    set()

    方法上,還可以直接寫在字段上,甚至可以寫在構造方法中

Main的代碼如下:

import model.Dance;
import model.Person;
import model.Sing;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value = "model")
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);

        Dance dance=context.getBean(Dance.class);
        dance.do_dance();

        Sing sing=context.getBean(Sing.class);
        sing.do_sing();

        Person person=context.getBean(Person.class);
        person.setName("Tom");

        Dance dance_=context.getBean(Dance.class);
        dance_.do_dance();

        Sing sing_=context.getBean(Sing.class);
        sing_.do_sing();
    }
}
           

運作結果:

null is dancing!
null is sing!
Tom is dancing!
Tom is sing!
           
注意:由于是自動裝配,person的name為null。

AppConfig

标注了

@ComponentScan

,它告訴容器,自動搜尋目前類所在的包以及子包,把所有标注為

@Component

的Bean自動建立出來,并根據

@Autowired

進行裝配。直接在Main中通過調用

@ComponentScan(value = "model")

來實作bean的自動搜尋與裝配,避免了配置檔案的書寫。

使用Annotation配合自動掃描能大幅簡化Spring的配置,我們隻需要保證:

  • 每個Bean被标注為

    @Component

    并正确使用

    @Autowired

    注入;
  • 配置類被标注為

    @Configuration

    @ComponentScan

  • 所有Bean均在指定包以及子包内。

這個方法雖然友善,但不适用于第三方包加入時。

比如當需要調用某個第三方包裡的某個方法,該方法在第三方包中沒有用@Component`注解,如何告訴IoC容器建立并配置?

或者換個說法,如何建立并配置一個第三方Bean?

待續。

5 參考文獻

[1]廖雪峰的官方網站

Java教程​www.liaoxuefeng.com

spring 依賴注入_【Spring基礎】JavaBean的配置與依賴注入