1. 程式人生 > >Spring的依賴註入

Spring的依賴註入

Spring 框架 依賴註入 Java開發

所謂依賴註入就是在創建一個對象時,將這個對象所依賴的對象或數據都創建好放進去,例如有一個Student類,它的構造器要求傳遞一個Dog對象,也就是說它依賴這個Dog對象,或者它有一個String類型的屬性,那麽它也就依賴String類型的數據。通過Spring的配置文件,我們可以配置好某個對象的依賴,當該對象被實例化時一並將它的依賴創建好給它,這個過程就是依賴註入。

在Spring的配置文件中,我們通過bean標簽來配置需要被管理的類,配置好後Spring就可以幫我們實例化這個類的對象,我們就只需要從Spring容器中獲取這個對象即可,不用自己手動去new,先來看看如何讓Spring來幫我們創建對象,Student類代碼如下:

package org.zero01.test;

public class Student {

    private String name;
    private int sid;
    private String address;

    public String getName() {
        return name;
    }

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

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

Spring配置文件配置內容如下:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="stu" class="org.zero01.test.Student"></bean>

</beans>

測試代碼:

package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {

        // 傳入配置文件的路徑
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通過配置的id創建對象
        Student student = (Student) app.getBean("stu");
        student.setSid(1);
        student.setName("小明");
        student.setAddress("M78星雲");

        System.out.println(student.getSid());
        System.out.println(student.getName());
        System.out.println(student.getAddress());

        // 默認是單例模式的,所以得到的是同一個對象
        Student student2 = (Student) app.getBean("stu");
        System.out.println(student == student2);
    }
}

運行結果:

1
小明
M78星雲
true

默認情況下,Spring實例化的對象都是單例的,如果不希望是單例的話,將bean標簽中的scope屬性設置為prototype即可:

<bean id="stu" class="org.zero01.test.Student" scope="prototype"></bean>

測試代碼:

package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {

        // 傳入配置文件的路徑
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通過配置的id創建對象
        Student student = (Student) app.getBean("stu");

        // 設置成prototype後,每次創建的都是新對象了
        Student student2 = (Student) app.getBean("stu");
        System.out.println(student == student2);
    }
}

運行結果:

false

接下來介紹一下bean標簽中的一些常用屬性以及內嵌標簽

bean標簽中的init-method屬性,該屬性指定一個方法,這個方法會在容器實例化對象時被調用,例如我在Student類中增加一個init方法:

public void init() {
        this.name = "小明";
        this.sid = 1;
        this.address = "M78星雲";
}

在init-method屬性中指定這個方法:

<bean id="stu" class="org.zero01.test.Student" init-method="init"></bean>

測試代碼:

package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {

        // 傳入配置文件的路徑
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通過配置的id創建對象
        Student student = (Student) app.getBean("stu");

        System.out.println(student.getSid());
        System.out.println(student.getName());
        System.out.println(student.getAddress());
    }
}

運行結果:

1
小明
M78星雲

bean標簽中有一個 property 子標簽,通過 property 標簽我們可以配置該對象的屬性值,例如

    <bean id="stu" class="org.zero01.test.Student">
        <property name="sid" value="1"/>
        <property name="name" value="Jon"/>
        <property name="address" value="北京"/>
    </bean>

測試代碼和之前一樣,略。運行結果如下:

1
Jon
北京

需要註意的是,想要通過 property 標簽去配置對象中某個屬性的值,那麽這個屬性必須具備有setter方法,否則是不能配置的。

property 標簽中有一個ref屬性,這個屬性的值為bean標簽的id屬性的值,所以說當一個對象依賴某個對象時,就可以使用到ref屬性來進行引用,例如Student的屬性裏依賴了一個Dog對象:

    private Dog dog;

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

通過ref屬性引用這個對象即可:

    <bean id="dog" class="org.zero01.test.Dog"/>
    <bean id="stu" class="org.zero01.test.Student">
        <!-- 基本數據類的值都可以自動進行轉換 -->
        <property name="sid" value="1"/>
        <property name="name" value="Jon"/>
        <property name="address" value="北京"/>
        <property name="dog" ref="dog"/>
    </bean>

以上已經將 property 標簽的屬性介紹完了,因為 property 標簽就只有這個三個屬性,但是它的子標簽卻有不少,例如那三個屬性都可以作為子標簽:

    <bean id="dog" class="org.zero01.test.Dog"/>
    <bean id="stu" class="org.zero01.test.Student">
        <property name="sid">
            <value type="int">1</value>
        </property>
        <property name="name">
            <value type="java.lang.String">Jon</value>
        </property>
        <property name="address" value="北京"/>
        <property name="dog">
            <ref bean="dog"/>
        </property>
    </bean>

property 標簽裏常用的子標簽:

  • value 指定屬性的值
  • ref 引用bean對象
  • bean 創建一個bean對象
  • list 指定List類型的屬性值
  • map 指定map類型的屬性值
  • set 指定set類型的屬性值
  • array 指定數組類型的屬性值
  • null 指定一個空屬性值
  • props 指定Properties類型的屬性值

value和ref標簽已經使用過了,剩下的其他標簽的使用方式如下:

Student類增加以下內容:

    private List list;
    private Set set;
    private Map map;
    private Properties properties;
    private int[] numbers;

    public Properties getProperties() {
        return properties;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public int[] getNumbers() {
        return numbers;
    }

    public void setNumbers(int[] numbers) {
        this.numbers = numbers;
    }

    public List getList() {
        return list;
    }

    public void setList(List list) {
        this.list = list;
    }

    public Set getSet() {
        return set;
    }

    public void setSet(Set set) {
        this.set = set;
    }

    public Map getMap() {
        return map;
    }

    public void setMap(Map map) {
        this.map = map;
    }

配置文件內容:

    <bean id="dog" class="org.zero01.test.Dog"/>
    <bean id="stu" class="org.zero01.test.Student">

        <property name="list">
            <list>
                <value>list01</value>
                <value>list02</value>
                <value>list03</value>
                <bean class="org.zero01.test.Dog" id="dog2"/>
                <ref bean="dog"></ref>
                <null/>
            </list>
        </property>

        <property name="map">
            <map key-type="java.lang.String" value-type="java.lang.Object">
                <entry key="01" value="零一"/>
                <entry key="02" value="零二"/>
                <entry key="03" value="零三"/>
                <entry key="dog" value-ref="dog" />
            </map>
        </property>

        <property name="set">
            <set>
                <value>set01</value>
                <value>set02</value>
                <value>set03</value>
                <null/>
            </set>
        </property>

        <property name="numbers">
            <array>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </array>
        </property>

        <property name="properties">
            <props>
                <prop key="stature">1.75</prop>
                <prop key="avoirdupois">100</prop>
            </props>
        </property>
    </bean>

測試代碼:

package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {

        // 傳入配置文件的路徑
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通過配置的id創建對象
        Student student = (Student) app.getBean("stu");

        System.out.println(student.getList());

        System.out.println(student.getMap());

        System.out.println(student.getSet());

        for (int i : student.getNumbers()) {
            System.out.print(i);
        }

        System.out.println("\n" + student.getProperties());
    }
}

運行結果:

[list01, list02, list03, org.zero01.test.Dog@f6c48ac, org.zero01.test.Dog@13deb50e, null]
{01=零一, 02=零二, 03=零三, dog=org.zero01.test.Dog@13deb50e}
[set01, set02, set03, null]
123
{avoirdupois=100, stature=1.75}

如上,可以看到,Spring配置文件的標簽還是很豐富的,這還只是基本的常用標簽,一些額外的標簽支持需要自己引入。

除了 property 標簽用於配置屬性值外,還有一個 constructor-arg 標簽,這個標簽可以配置構造器的參數值,使用方式和 property 標簽基本一樣,例如Student類裏有這樣一個構造器:

    public Student(String name, int sid, String address, Dog dog) {
        this.name = name;
        this.sid = sid;
        this.address = address;
        this.dog = dog;
    }

則配置內容如下:

    <bean id="stu" class="org.zero01.test.Student">
        <constructor-arg name="dog" ref="dog"/>
        <constructor-arg name="sid" value="1"/>
        <constructor-arg name="name" value="Mick"/>
        <constructor-arg name="address" value="上海"/>
    </bean>

測試代碼:

package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {

        // 傳入配置文件的路徑
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通過配置的id創建對象
        Student student = (Student) app.getBean("stu");
        System.out.println(student.getSid());
        System.out.println(student.getName());
        System.out.println(student.getAddress());
        System.out.println(student.getDog());
    }
}

運行結果:

1
Mick
上海
org.zero01.test.Dog@7cd62f43

constructor-arg 標簽除了以上兩個使用到的屬性之外還有一個index屬性以及type屬性,index屬性是用於指定給哪個位置的參數賦值,而type屬性則是用於指定該值的類型,這兩個屬性一般用不到。constructor-arg 標簽也有子標簽,它的子標簽和 property 標簽的子標簽一樣,這裏就不再贅述了。

我們在使用 property 標簽的時候,可能會感到一絲蛋疼,要寫那麽多的屬性或標簽,所以Spring就提供了一個屬性標記,讓我們可以通過這個屬性標記來簡化一些配置 property 的操作,要使用這個屬性標記首先需要在 beans 引入屬性標記地址:

xmlns:p="http://www.springframework.org/schema/p"

然後就可以使用這個屬性標記了:

    <bean id="dog" class="org.zero01.test.Dog"/>
    <bean id="list" class="java.util.ArrayList"/>
    <bean id="stu" class="org.zero01.test.Student" p:sid="1" p:name="Alisa" p:address="湖南" p:dog-ref="dog" p:list-ref="list"/>

從配置內容可以看到,在bean標簽上就可以直接完成屬性的配置了,能讓我們少寫不少標簽。但是有一個小缺點就是不能夠給集合這種對象填充元素,從以上的配置內容中也可以看到只能給一個不包含任何元素的實例對象。所以這個屬性標記一般用於配置基本數據類型的屬性值多些,遇到集合對象需要填充元素的情況就只能使用 property 標簽了。

測試代碼與之前的差不多,略。運行結果如下:

1
Alisa
湖南
org.zero01.test.Dog@271053e1
[]

在實際開發中,一般用xml配置依賴對象的情況比較少,基本大部分情況都是使用註解去進行配置,因為註解要比xml方便和簡單。但是有一些對象則必須要在xml裏配置,例如用於連接數據庫的數據源對象,因為這種對象的配置信息多變動,使用註解來配置就不合適了,所以這種類型的對象就十分適合使用xml來進行配置,例如配置個 c3p0 連接池:

    <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource"
          p:driverClass="com.mysql.jdbc.Driver"
          p:jdbcUrl="jdbc:mysql///mysql"
          p:user="root"
          p:password="your_password"
          p:maxPoolSize="10"
          p:minPoolSize="3"
          p:loginTimeout="2000"
    />

測試代碼:

package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.sql.DataSource;

public class Test {

    public static void main(String[] args) {

        // 傳入配置文件的路徑
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通過配置的id創建對象
        DataSource dataSource = (DataSource) app.getBean("c3p0");
        System.out.println(dataSource);
    }
}

運行結果:

com.mchange.v2.c3p0.ComboPooledDataSource[ identityToken -> 1hge0yy9t1mjbsw5vgbf8b|30b8a058, dataSourceName -> 1hge0yy9t1mjbsw5vgbf8b|30b8a058 ]

bean 標簽裏有一個 abstract 屬性,該屬性可以將一個節點聲明為抽象節點,抽象節點可以被子節點繼承,與Java中的繼承概念是一樣的,子節點繼承父節點後可以擁有父節點的所有配置信息。例如我們可以通過配置數據源對象的來演示這種繼承關系:

    <!-- 通過 abstract 屬性聲明為抽象節點後才可以被繼承 -->
    <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource" abstract="true"
          p:driverClass="com.mysql.jdbc.Driver"
          p:jdbcUrl="jdbc:mysql///mysql"
          p:user="root"
          p:password="your_password"
    />

    <!-- 通過 parent 屬性來繼承一個父節點,該節點會擁有父節點的所有配置信息 -->
    <bean id="dataSource" parent="c3p0"
          p:maxPoolSize="20"
          p:minPoolSize="5"
          p:loginTimeout="1500"
    />

    <bean id="dataSource2" parent="c3p0"
          p:maxPoolSize="15"
          p:minPoolSize="3"
          p:loginTimeout="1000"
    />

如上,子節點繼承父節點後可以擁有父節點的所有配置信息,所以我們可以把一些較為穩定的,不易改變的配置信息寫在父節點上。然後在父節點的配置信息的基礎上,子節點可以新增一些配置信息,這樣我們在獲得數據源對象的時候就有多個配置方案可以選擇。

測試代碼將之前的c3p0改成dataSource或dataSource2即可,運行結果如下:

com.mchange.v2.c3p0.ComboPooledDataSource[ identityToken -> 1hge0yy9t1mjprn91mvr8fv|30b8a058, dataSourceName -> 1hge0yy9t1mjprn91mvr8fv|30b8a058 ]

註:一旦某個節點聲明為抽象節點後就不可以被實例化了,只能實例化繼承它的子節點。


配置Spring的註解支持

以上也提到了使用註解來配置依賴對象會方便簡單一些,所以以下簡單介紹一下如何配置Spring的註解,讓Spring能過夠通過註解的方式來對類進行管理。

Spring配置文件內容修改如下:

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

    <!-- 讓Spring支持註解 -->
    <context:annotation-config/>
    <!-- 指定哪些包下的類支持使用註解的方式管理 -->
    <context:component-scan base-package="org.zero01.test"/>

</beans>

在Student類上加上 @Component 註解,讓該類受Spring管理:

package org.zero01.test;

import org.springframework.stereotype.Component;

// 這裏註解配置的值相當於xml中bean標簽的id屬性的值
@Component("stu")
public class Student {
        ...

測試代碼:

package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {

        // 傳入配置文件的路徑
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通過配置的id創建對象
        Student student = (Student) app.getBean("stu");
        Student student2 = (Student) app.getBean("stu");

        // 同樣默認是單例的
        System.out.println(student == student2);
    }
}

運行結果:

true

在屬性的setter方法上,我們可以通過 @Value 註解來配置屬性的值,這就相當於xml中的 property 標簽的作用了:

package org.zero01.test;

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

@Component("stu")
public class Student {

    private String name;
    private int sid;
    private String address;

    public String getName() {
        return name;
    }

    @Value("小明")
    public void setName(String name) {
        this.name = name;
    }

    public int getSid() {
        return sid;
    }

    @Value("1")
    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getAddress() {
        return address;
    }

    @Value("湖南")
    public void setAddress(String address) {
        this.address = address;
    }
}

測試代碼:

package org.zero01.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    public static void main(String[] args) {

        // 傳入配置文件的路徑
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 通過配置的id創建對象
        Student student = (Student) app.getBean("stu");
        System.out.println(student.getSid());
        System.out.println(student.getName());
        System.out.println(student.getAddress());
    }
}

運行結果:

1
小明
湖南

這個 @Value 註解除了可以寫在屬性的setter方法上,還可以直接寫在屬性上,作用是一樣的:

package org.zero01.test;

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

@Component("stu")
public class Student {

    @Value("小明")
    private String name;
    @Value("1")
    private int sid;
    @Value("湖南")
    private String address;
    ...

測試代碼和之前一樣,運行結果也是一樣,略。

Spring的依賴註入