Spring基礎:依賴注入(DI)
一. Spring的概述
Spring是Java生態中最為成功的框架,他的核心思想是控制反轉(Inverse of Control)和麵向切面程式設計(Aspect Oriented Programming)。Spring的優勢:
- Spring可以通過配置(XML或註解)來擴充套件POJO的功能,通過依賴注入實現系統解耦
- 使用AOP技術消除了系統中事務的try…catch…finally模板程式碼,通過宣告式事務技術使得開發人員可以專注於業務邏輯
- Spring提供了很對模板類來整合其他優秀的開源框架,統一模板簡化開發,例如HibernateTemplate,RedisTemplate等
控制反轉(IoC)
二. 在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
,@Service
,Repository
這三個註解和也可能將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應用中,在一個請求過程中會建立一個例項