1. 程式人生 > >Spring基礎:依賴注入(DI)

Spring基礎:依賴注入(DI)

一. Spring的概述

Spring是Java生態中最為成功的框架,他的核心思想是控制反轉(Inverse of Control)和麵向切面程式設計(Aspect Oriented Programming)。Spring的優勢:

  • Spring可以通過配置(XML或註解)來擴充套件POJO的功能,通過依賴注入實現系統解耦
  • 使用AOP技術消除了系統中事務的try…catch…finally模板程式碼,通過宣告式事務技術使得開發人員可以專注於業務邏輯
  • Spring提供了很對模板類來整合其他優秀的開源框架,統一模板簡化開發,例如HibernateTemplate,RedisTemplate等

控制反轉(IoC)

的核心思想就是將程式中物件建立的主動權由程式本身轉為交由第三方建立(IoC容器)! 例如程式中的Controller,Service,Repository以及各種第三方框架的元件(@Component / @Bean)的初始化交由Spring的IoC容器,開發人員只需要通過描述的方式(XML or 註解)將這些元件註冊到spring容器中即可。Spring容器在初始化的時候會建立容器中註冊的元件,並且通過解析元件的依賴關係(@Autowired)完成依賴物件的注入。所以當程式從容器中獲取指定元件的時候,容器確保元件已經完成初始化,並且已經注入所依賴的元件,達到開箱即用。

二. 在Spring IoC 容器中裝配Bean

1. 2種依賴注入的方式

Spring中依賴注入主要分為:構造方法注入和屬性注入。構造方法注入 需要在Bean中提供有參的構造方法,元件通過引數注入。

public class UserController {
    
    private UserService userService;
    
    public UserService getUserService() {
        return userService;
    }

    // 屬性的setter方法
    public void setUserService(UserService userService)
{ this.userService = userService; } // 無參構造器 public UserController(){} // 通過構造方法的引數注入元件物件 public UserController(UserService userService){ this.userService = userService; } }

在applicationContext.xml配置檔案中為UserController元件注入UserService物件:

<?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">

    <!-- 通過XML完成元件的註冊 -->
    <bean id="userController" class="com.znker.controller.UserController">
        <!-- 通過構造方法完成物件的注入,ref標籤通過id引用容器中的Bean -->
        <constructor-arg index="0" ref="userService"/>
    </bean>

    <bean id="userService" class="com.znker.service.UserService" />

</beans>

這是比較原始的注入方法,constructor-arg元素用於定義類構造方法的引數,index指定入參順序,value引用為屬性注入的值。

屬性注入 是通過屬性對應的setter方法注入。

<?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">

    <!-- 通過XML完成元件的註冊 -->
    <bean id="userController" class="com.znker.controller.UserController">
        <!-- 使用setter方法注入,因為使用反射技術,Bean需要提供一個無參構造器
             property元素:指定需要注入的屬性
             value元素:引用容器中Bean物件完成注入,根據Bean名稱注入
         -->
        <property name="userService" ref="userService"/>
    </bean>

    <bean id="userService" class="com.znker.service.UserService" />

</beans>

2. 裝配容器中的Bean

Spring將自己開發的Bean和第三方類庫Bean註冊到容器中主要有三種方法:

  • 在XML檔案顯示配置(主要是第三方類庫,例如DataSource,RedisTempalte…)
  • 使用註解,Bean的掃描和註冊以及自動裝配機制(@Bean / @Component,@Autowired)
  • 使用配置java配置類配置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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <!-- 向Bean中顯式注入簡單值 -->
        <property name="username" value="root"/>
        <property name="password" value="1234"/>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 為屬性注入容器中的Bean物件 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 注入簡單值 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

    <!-- 掃描Mapper介面,將通過動態代理生成的Bean註冊到容器中去 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入簡單值,指明掃描的包 -->
        <property name="basePackage" value="com.znker.mapper"/>
        <!-- 注入簡單值,從容器中尋找id為sqlSessionFactory的Bean注入 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!-- 掃描所有帶有@Repository註解的代理類,將其作為Bean註冊到容器中去 -->
        <property name="annotationClass" value="org.springframework.stereotype.Repository"/>
    </bean>
    
</beans>

除了注入簡單值和引用物件之外,還能給Bean注入集合和對映物件。

除了XML外還可以使用註解裝配Bean,並且註解更加流行(springboot推薦全註解)。註解開發提供了自動裝配的功能。使用註解開發需要解決兩個問題:1. 讓容器發現並註冊Bean到容器中(註解掃描),2. 完成容器中Bean的依賴關係的自動裝配(依賴注入)。

Spring中的那些註解:

註解@Component告知Spring將這個類掃描生成為Bean註冊到容器中,value元素表示該Bean在容器中的id,預設是類首字母的小寫。

@Component	// Spring掃描到該類的時候會將它註冊為容器中的Bean
public class UserService {

    @Autowired
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public UserDao getUserDao() {
        return userDao;
    }
}

@Component註解還有三個派生註解:@Controller@ServiceRepository這三個註解和也可能將pojo標識為容器中的Bean,在分層開發的的結構中,pojo的作用更加清晰。

除了給pojo加註解之外,還要告知spring去哪裡掃描對應的pojo,在使用java配置類開發的時候,可以使用@ComponentScan註解標識需要掃描的包

package com.znker.dao;

import org.springframework.context.annotation.ComponentScan;
// 該註解告知spring掃描指定包極其子包下所有的類,將帶有@Component註解的pojo註冊為容器中的Bean
// 如果該註解不指定basePackages元素,這預設掃描當前配置類所在的包,basePackages可以指定多個包
@ComponentScan(basePackages = {"com.znker.dao"})
public class DaoConfig {

}

註解@Autowired實現了容器中Bean之間依賴關係的自動裝配。Spring容器在初始化的時候完成容器中的Bean的定義和生成,然後尋找Bean所需的資源,完成依賴注入。@Autowired註解預設是按容器中Bean的型別完成依賴項查詢和注入的(byType)

@Controller
public class UserController {
	// 從容器中查詢UserService型別的Bean注入
    @Autowired
    private UserService userService;

}

按型別注入可能會存在Bean不唯一的情況,例如容器有多個UserService介面的實現類,此時spring不知道該注入哪個實現類,需要其它輔助方法來消除歧義。@Primary註解標註在其中某個實現類上,告知當出現歧義的使用,優先使用該Bean注入,而@Qualifier標註在需要注入資源屬性上,此時按照容器中Bean的id來注入所需資源(預設是實現類的類名的首字母小寫),從而消除歧義。

註解@Bean@Component一樣都可以標註pojo為容器中的Bean,但是@Bean有一個特殊的用法,它可以將方法返回的物件作為Bean存放到容器中。

實際開發中,XML和註解是混合使用的,一般專案中自己開發的pojo優先使用註解配置(Controller,Service,Repository…),而第三方元件(DataSource,RedisTemplate…)則用XML配置更加方便。如果是基於java配置的專案中,使用@ImportResource可以匯入XML配置檔案,將XML中定義的Bean註冊到spring容器中去。

package com.znker.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;

// 該註解匯入了xml配置檔案中定義的Bean,配置項是個陣列,可用引入多個xml配置檔案,用逗號隔開
@ImportResource({"classpath:spring-dao.xml"})
@ComponentScan("com.znker.*")
public class ApplicationConfig {

}

在分模組開發的時候,專案中可能有多個XML配置檔案,此時可以在主配置檔案中匯入各個模組的配置檔案。此時主配置檔案中就有所有Bean的註冊資訊,容器使用主配置檔案初始化的時候就能註冊工程中的所有Bean

<import resource="classpath:spring-dao.xml"/>

在基於xml配置檔案初始化容器的時候,需要指定容器需要掃描的包:

<!-- 掃描指定包極其子包下所有類,將標註了@Component、@Bean等註解的pojo註冊為容器中的Bean -->
<context:component-scan base-package="com.znker.*"/>

載入屬性檔案:

開發過程中屬性配置經常寫到properties屬性檔案中,這樣更改屬性的時就無需修改專案原始碼了,方便環境切換。註解@PropertySource用於將屬性配置檔案引入java配置類中。database.properties屬性檔案:

jdbc.database.driver=com.mysql.jdbc.Driver
jdbc.database.url=jdbc:mysql://localhost:3306/demo
jdbc.database.username=root
jdbc.database.password=1234
// 載入屬性檔案
@PropertySource(value = {"classpath:database.properties"})
public class ApplicationConfig {

}

使用xml的方式載入屬性檔案:

<!-- 載入屬性檔案,多個屬性檔案之間使用逗號隔開 -->
<conontext:property-placeholder location="classpath:database.properties"/>

註解@Value和佔位符可以引用屬性檔案中的配置@Value("${jdbc.database.driver}")將屬性值注入到pojo的屬性中。

Bean的作用域:

在預設情況下,spring容器的Bean只有一個例項

  • 單例(singleton):預設選項,整個應用只生成一個例項,容器中元件共用一個例項
  • 原型(prototype):多例模式,每次注入或者從容器中獲取Bean例項的時候spring容器都會建立一個新的例項
  • 會話(session):在web應用中,在一個會話過程中只建立一個例項
  • 請求(request):在web應用中,在一個請求過程中會建立一個例項