Spring通過DI(依賴注入)實現IOC(控制反轉),常用的注入方式主要有三種:構造方法注入,setter注入,基於註解的注入。
構造方法注入
先簡單看一下測試項目的結構,用maven構建的,四個包:
entity:存儲實體,裏面只有一個User類
dao:數據訪問,一個接口,兩個實現類
service:服務層,一個接口,一個實現類,實現類依賴於IUserDao
test:測試包
在spring的配置文件中註冊UserService,將UserDaoJdbc通過constructor-arg標籤注入到UserService的某個有參數的構造方法
<!-- 註冊userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
<constructor-arg ref="userDaoJdbc"></constructor-arg>
</bean>
<!-- 註冊jdbc實現的dao -->
<bean id="userDaoJdbc" class="com.lyu.spring.dao.impl.UserDaoJdbc"></bean>
如果只有一個有參數的構造方法並且參數類型與注入的bean的類型匹配,那就會注入到該構造方法中。
public class UserService implements IUserService {
private IUserDao userDao;
public UserService(IUserDao userDao) {
this.userDao = userDao;
}
public void loginUser() {
userDao.loginUser();
}
}
@Test
public void testDI() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 獲取bean對象
UserService userService = ac.getBean(UserService.class, "userService");
// 模擬用戶登錄
userService.loginUser();
}
測試打印結果:jdbc-登錄成功
注:模擬用戶登錄的loginUser方法其實只是打印了一條輸出語句,jdbc實現的類輸出的是:jdbc-登錄成功,mybatis實現的類輸出的是:mybatis-登錄成功。
問題一:如果有多個有參數的構造方法並且每個構造方法的參數列表裏面都有要注入的屬性,那userDaoJdbc會注入到哪裏呢?
public class UserService implements IUserService {
private IUserDao userDao;
private User user;
public UserService(IUserDao userDao) {
System.out.println("這是有一個參數的構造方法");
this.userDao = userDao;
}
public UserService(IUserDao userDao, User user) {
System.out.println("這是有兩個參數的構造方法");
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
結果:會注入到只有一個參數的構造方法中,並且經過測試注入哪一個構造方法與構造方法的順序無關
問題二:如果只有一個構造方法,但是有兩個參數,一個是待注入的參數,另一個是其他類型的參數,那麼這次注入可以成功嗎?
public class UserService implements IUserService {
private IUserDao userDao;
private User user;
public UserService(IUserDao userDao, User user) {
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
結果:失敗了,即使在costract-arg標籤裏面通過name屬性指定要注入的參數名userDao也會失敗.
問題三:如果我們想向有多個參數的構造方法中注入值該在配置文件中怎麼寫呢?
public class UserService implements IUserService {
private IUserDao userDao;
private User user;
public UserService(IUserDao userDao, User user) {
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
參考寫法:通過name屬性指定要注入的值,與構造方法參數列表參數的順序無關。
<!-- 註冊userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
<constructor-arg name="userDao" ref="userDaoJdbc"></constructor-arg>
<constructor-arg name="user" ref="user"></constructor-arg>
</bean>
<!-- 註冊實體User類,用於測試 -->
<bean id="user" class="com.lyu.spring.entity.User"></bean>
<!-- 註冊jdbc實現的dao -->
<bean id="userDaoJdbc" class="com.lyu.spring.dao.impl.UserDaoJdbc"></bean>
問題四:如果有多個構造方法,每個構造方法只有參數的順序不同,那通過構造方法注入多個參數會注入到哪一個呢?
public class UserService implements IUserService {
private IUserDao userDao;
private User user;
public UserService(IUserDao userDao, User user) {
System.out.println("這是第二個構造方法");
this.userDao = userDao;
this.user = user;
}
public UserService(User user, IUserDao userDao) {
System.out.println("這是第一個構造方法");
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
結果:哪個構造方法在前就注入哪一個,這種情況下就與構造方法順序有關。
setter注入
配置文件如下:
<!-- 註冊userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
<!-- 寫法一 -->
<!-- <property name="UserDao" ref="userDaoMyBatis"></property> -->
<!-- 寫法二 -->
<property name="userDao" ref="userDaoMyBatis"></property>
</bean>
<!-- 註冊mybatis實現的dao -->
<bean id="userDaoMyBatis" class="com.lyu.spring.dao.impl.UserDaoMyBatis"></bean>
注:上面這兩種寫法都可以,spring會將name值的每個單詞首字母轉換成大寫,然後再在前面拼接上”set”構成一個方法名,然後去對應的類中查找該方法,通過反射調用,實現注入。
切記:name屬性值與類中的成員變量名以及set方法的參數名都無關,只與對應的set方法名有關,下面的這種寫法是可以運行成功的
public class UserService implements IUserService {
private IUserDao userDao1;
public void setUserDao(IUserDao userDao1) {
this.userDao1 = userDao1;
}
public void loginUser() {
userDao1.loginUser();
}
}
還有一點需要注意:如果通過set方法注入屬性,那麼spring會通過默認的空參構造方法來實例化對象,所以如果在類中寫了一個帶有參數的構造方法,一定要把空參數的構造方法寫上,否則spring沒有辦法實例化對象,導致報錯。
基於註解的注入
在介紹註解注入的方式前,先簡單瞭解bean的一個屬性autowire,autowire主要有三個屬性值:constructor,byName,byType。
- constructor:通過構造方法進行自動注入,spring會匹配與構造方法參數類型一致的bean進行注入,如果有一個多參數的構造方法,一個只有一個參數的構造方法,在容器中查找到多個匹配多參數構造方法的bean,那麼spring會優先將bean注入到多參數的構造方法中。
- byName:被注入bean的id名必須與set方法後半截匹配,並且id名稱的第一個單詞首字母必須小寫,這一點與手動set注入有點不同。
- byType:查找所有的set方法,將符合符合參數類型的bean注入。
下面進入正題:註解方式註冊bean,注入依賴
主要有四種註解可以註冊bean,每種註解可以任意使用,只是語義上有所差異:
- @Component:可以用於註冊所有bean
- @Repository:主要用於註冊dao層的bean
- @Controller:主要用於註冊控制層的bean
- @Service:主要用於註冊服務層的bean
描述依賴關係主要有兩種:
- @Resource:java的註解,默認以byName的方式去匹配與屬性名相同的bean的id,如果沒有找到就會以byType的方式查找,如果byType查找到多個的話,使用@Qualifier註解(spring註解)指定某個具體名稱的bean。
@Resource
@Qualifier("userDaoMyBatis")
private IUserDao userDao;
public UserService(){
}
- @Autowired:spring註解,默認也是以byName的方式去匹配與屬性名相同的bean的id,如果沒有找到,就通過byType的方式去查找,如果查找到多個,用@Qualifier註解限定具體使用哪個。
@Autowired
@Qualifier("userDaoJdbc")
private IUserDao userDao;
寫在最後:雖然有這麼多的注入方式,但是實際上開發的時候自己編寫的類一般用註解的方式註冊類,用@Autowired描述依賴進行注入,一般實現類也只有一種(jdbc or hibernate or mybatis),除非項目有大的變動,所以@Qualifier標籤用的也較少;但是在使用其他組件的API的時候用的是通過xml配置文件來註冊類,描述依賴,因爲你不能去改人家源碼嘛。
========================== 分割線 ================================
看到文章底下的評論說 @Autowired 註解默認是以 ByType 方式進行注入的我也是呵呵了。網上很多人評論說 Spring 的 @Autowired 註解是以 ByType 方式進行注入的,你們就不假思索的跟風?你們寫代碼驗證過嗎?
爲了避免更多的評論誤人子弟,現貼上我的代碼,僅爲證明 Spring 的 @Autowired 註解默認是以 ByName 方式注入的:
項目結構如下:
一個 dao,一個 service,一個 test 用於測試
dao 有兩個實現類
service 有一個實現類
在 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">
<context:component-scan base-package="com.qjl.*">
</context:component-scan>
<!-- 註冊dao接口的mybatis實現類 -->
<bean id="userDaoMyBatis" class="com.qjl.dao.impl.UserDaoMyBatisImpl">
</bean>
<!-- 註冊dao接口的hibernate實現類 -->
<bean id="userDaoHibernate" class="com.qjl.dao.impl.UserDaoHibernateImpl">
</bean>
<!-- 註冊IUserService唯一的實現類 -->
<bean id="userService" class="com.qjl.service.impl.UserServiceImpl">
</bean>
</beans>
MyBatis 的 dao 實現類:
package com.qjl.dao.impl;
import com.qjl.dao.IUserDAO;
/**
* 類名稱:以MyBatis方式實現的UserDao
* 全限定性類名: com.qjl.dao.impl.UserDaoMyBatisImpl
* @author 曲健磊
* @date 2018年7月20日上午10:02:22
* @version V1.0
*/
public class UserDaoMyBatisImpl implements IUserDAO {
/**
* 測試dao方法
*/
@Override
public void testDao() {
System.out.println("mybatis實現的dao層");
}
}
Hibernate 的 dao 實現類:
package com.qjl.dao.impl;
import com.qjl.dao.IUserDAO;
/**
* 類名稱:以Hibernate的方式實現的UserDao
* 全限定性類名: com.qjl.dao.impl.UserDaoHibernateImpl
* @author 曲健磊
* @date 2018年7月20日上午10:01:57
* @version V1.0
*/
public class UserDaoHibernateImpl implements IUserDAO {
/**
* 測試dao方法
*/
@Override
public void testDao() {
System.out.println("Hibernate實現的dao");
}
}
service 實現類如下(重點,噴子自己看):
package com.qjl.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import com.qjl.dao.IUserDAO;
import com.qjl.service.IUserService;
/**
* 類名稱:服務層的實現類
* 全限定性類名: com.qjl.service.impl.UserServiceImpl
* @author 曲健磊
* @date 2018年7月20日上午10:06:30
* @version V1.0
*/
public class UserServiceImpl implements IUserService {
// 如果默認是以ByType方式注入的話,因爲我配置了多個IUserDao的實現類
// 後臺是會報錯的,但是並沒有報錯,說明了什麼?
// 證明了它默認不是以ByType方式注入的。
// 因爲如果默認以ByType方式注入的話
// 在匹配到有多個實現類的情況下,沒有使用Quilifer註解指明的情況下會報錯。
// 說明Spring優先去查找與被注入的屬性名相同的bean的id來進行注入,
// 也就是通過名稱匹配的方式(ByName)來進行的自動注入
@Autowired
private IUserDAO userDaoHibernate;
/**
* 測試服務層方法
*/
@Override
public void testService() {
userDaoHibernate.testDao();
}
}
測試類如下:
package com.qjl.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.qjl.service.IUserService;
/**
* 類名稱:測試autowired到底默認是以ByName方式注入還是以ByType方式注入
* 全限定性類名: com.qjl.test.AutowiredTest
* @author 曲健磊
* @date 2018年7月20日上午10:13:21
* @version V1.0
*/
public class AutowiredTest {
private ClassPathXmlApplicationContext app;
@Before
public void init() {
app = new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Test
public void testAutowired() {
IUserService userService = (IUserService) app.getBean("userService");
userService.testService();
}
}
實驗結果:
如果 Spring 默認是以 ByType 的方式進行 @Autowired 的自動注入,當匹配到多個dao 實現類的時候,而且還沒有使用 @Quilifier 註解指明具體的那個實現類的時候爲爲什麼不報錯?
我就想問一下:你們不親自寫代碼驗證,有什麼資格在這誤人子弟?
====================================分割線 =====================================
又看到底下的槓精評論說:”你的項目又用註解又用配置文件,真實的項目都是全註解,你這種寫法不會影響性能嗎?”,我也是呵呵了,我不知道項目真實開發是用的全註解?我放在網盤中的項目的目的很明確,把上面的話拷過來:”現貼上我的代碼,僅爲證明 Spring 的 @Autowired 註解默認是以 ByName 方式注入的。”,又在這影響性能,又在這跟我說真實項目全註解?你是聽不懂人話?
修改成全註解的代碼也放到這裏:
package com.qjl.dao.impl;
import org.springframework.stereotype.Repository;
import com.qjl.dao.IUserDAO;
/**
* 類名稱:以Hibernate的方式實現的UserDao
* 全限定性類名: com.qjl.dao.impl.UserDaoHibernateImpl
* @author 曲健磊
* @date 2018年7月21日上午8:43:10
* @version V1.0
*/
@Repository("userDaoHibernate")
public class UserDaoHibernateImpl implements IUserDAO {
/**
* 測試dao方法
*/
@Override
public void testDao() {
System.out.println("Hibernate實現的dao");
}
}
package com.qjl.dao.impl;
import org.springframework.stereotype.Repository;
import com.qjl.dao.IUserDAO;
/**
* 類名稱:以MyBatis方式實現的UserDao
* 全限定性類名: com.qjl.dao.impl.UserDaoMyBatisImpl
* @author 曲健磊
* @date 2018年7月21日上午8:43:25
* @version V1.0
*/
@Repository("userDaoMyBatis")
public class UserDaoMyBatisImpl implements IUserDAO {
/**
* 測試dao方法
*/
@Override
public void testDao() {
System.out.println("mybatis實現的dao層");
}
}
package com.qjl.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.qjl.dao.IUserDAO;
import com.qjl.service.IUserService;
/**
* 類名稱:服務層的實現類
* 全限定性類名: com.qjl.service.impl.UserServiceImpl
* @author 曲健磊
* @date 2018年7月20日上午10:06:30
* @version V1.0
*/
@Service("userService")
public class UserServiceImpl implements IUserService {
// 如果默認是以ByType方式注入的話,因爲我配置了多個IUserDao的實現類
// 後臺是會報錯的,但是並沒有報錯,說明了什麼?
// 證明了它默認不是以ByType方式注入的。
// 因爲如果默認以ByType方式注入的話
// 在匹配到有多個實現類的情況下,沒有使用Quilifer註解指明的情況下會報錯。
// 說明Spring優先去查找與被注入的屬性名相同的bean的id來進行注入,
// 也就是通過名稱匹配的方式(ByType)來進行的自動注入
@Autowired
private IUserDAO userDaoHibernate;
/**
* 測試服務層方法
*/
@Override
public void testService() {
userDaoHibernate.testDao();
}
}
配置文件如下:
<?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">
<context:component-scan base-package="com.qjl.*">
</context:component-scan>
</beans>
運行結果:
全註解項目的網盤鏈接如下:
https://pan.baidu.com/s/1GrRlT5cLAI3SMu17TA6l6Q
在編輯文章的時候又看到了評論中剛纔那位先生的回覆:”spring書上講就是按類型匹配…”,書上的就一定是對的?Thinking in java 中有 n 多錯誤,你知道不?只看書上的代碼,不動手實踐就可以學會編程?書上寫啥,你就不假思索的拿過來照搬,還上別人的博客底下來”炫技”?
紙上得來終覺淺,絕知此事要躬行。