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注解,默認是以byType的方式去匹配與屬性名相同的bean的id,如果沒有找到,就通過byName的方式去查找,
@Autowired @Qualifier("userDaoJdbc") private IUserDao userDao;
寫在最后:雖然有這么多的注入方式,但是實際上開發的時候自己編寫的類一般用注解的方式注冊類,用@Autowired描述依賴進行注入,一般實現類也只有一種(jdbc or hibernate or mybatis),除非項目有大的變動,所以@Qualifier標簽用的也較少;但是在使用其他組件的API的時候用的是通過xml配置文件來注冊類,描述依賴,因為你不能去改人家源碼嘛。