2. spring 應用之IOC
- 2019 年 10 月 18 日
- 筆記
本文是作者原創,版權歸作者所有.若要轉載,請註明出處
我們知道Spring Framework 最重要的功能就是IoC (Inversion of Control ),也叫DI(dependency injection),這不是我說的,是官網這麼說的,截圖如下
spring官網說IoC,也叫DI,是同一個意思.
首先複習一下spring的應用
1.用xml方式將對象交給spring管理
首先是測試類
public class UserService { private String userName; public String getUserName() { return userName; } public void setUserName(String userName1) { this.userName = userName1; } }
然後是applicationContext.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 https://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="no"> <bean class="com.lusaisai.service.UserService" id="userService" > <!--此處name的值與set方法要一致--> <property name="userName" value="lusai"></property> </bean> </beans>
最後是測試
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService");
System.out.println(userService.getUserName());
}
可以看到注入成功
2.依賴注入
首先是UserService依賴的對象UserDao
public interface UserDao { void test(); }
然後是它的實現類
public class UserDaoImpl implements UserDao { @Override public void test() { System.out.println("UserDaoImpl"); } }
在UserService增加依賴的對象UserDao和set方法,注意,這裡需要UserService的空構造
public class UserService { private String userName; private UserDao userDao; public String getUserName() { return userName; } public void setUserName(String userName1) { this.userName = userName1; } public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void serviceTest(){ userDao.test(); } }
然後是xml配置
<!--必須有空構造--> <bean class="com.lusaisai.service.UserService" id="userService" > <property name="userName" value="lusai"></property> <!--這裡的ref指定下方bean標籤配置對象的id--> <!--此處name的值與UserService的屬性userDao的set方法名要一致--> <property name="userDao" ref="userDao"></property> </bean> <bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean>
然後是測試
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); System.out.println(userService.getUserName()); userService.serviceTest();
看下結果
好了,注入成功
3.我們發現一個問題,我們已經在程式碼里寫了UserDao,還有它的set和get方法,為啥還要在xml里用配置告訴spring應該如何注入呢,其實spring提供了自動裝配的功能,如下圖
我們可以看到default-autowire有5個可供選擇
單獨的bean標籤里也有5個供選擇,這裡截個官網的圖
下面我們來測試一下,首先是byName,我們把手動裝配注釋掉,添加上default-autowire=”byName”
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byName" >
<!--必須有空構造-->
<bean class="com.lusaisai.service.UserService" id="userService" >
<property name="userName" value="lusai"></property>
<!--這裡的ref指定下方bean標籤配置對象的id-->
<!--此處name的值與UserService的屬性userDao的set方法名要一致-->
<!--<property name="userDao" ref="userDao"></property>-->
</bean>
<bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean>
</beans>
UserService中的程式碼沒有改動,測試
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); System.out.println(userService.getUserName()); UserDao userDao = userService.getUserDao(); System.out.println(userDao);
看下結果
好的,成功了,我們改一下set方法的名字
public void setUserDao1(UserDao userDao) { this.userDao = userDao; }
重新運行一次
可以看到,userDao沒有注入進來, 我們把方法名復原再次修改屬性名試一下
private String userName; private UserDao userDao2; public String getUserName() { return userName; } public void setUserName(String userName1) { this.userName = userName1; } public UserDao getUserDao() { return userDao2; } public void setUserDao(UserDao userDao) { this.userDao2 = userDao; }
重新運行
可以看到,注入成功,因此byName的set方法名要和依賴對象的bean標籤的id相同
接下來看下byType,我們將default-autowire=”byType” 再加上一個同一個類不同名的bean標籤
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byType" > <!--必須有空構造--> <bean class="com.lusaisai.service.UserService" id="userService" > <property name="userName" value="lusai"></property> <!--這裡的ref指定下方bean標籤配置對象的id--> <!--此處name的值與UserService的屬性userDao的set方法名要一致--> <!--<property name="userDao" ref="userDao"></property>--> </bean> <bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean> <bean class="com.lusaisai.dao.UserDaoImpl" id="userDao2" ></bean> </beans>
userService程式碼
private String userName; private UserDao userDao; public String getUserName() { return userName; } public void setUserName(String userName1) { this.userName = userName1; } public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; }
再運行一下結果
十月 17, 2019 11:53:15 下午 org.springframework.context.support.AbstractApplicationContext refresh 警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService' defined in class path resource [applicationContext.xml]: Unsatisfied dependency expressed through bean property 'userDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDao,userDao2 Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService' defined in class path resource [applicationContext.xml]: Unsatisfied dependency expressed through bean property 'userDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDao,userDao2 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1499) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1379) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:849) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85) at com.lusaisai.test.Test.main(Test.java:16) Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDao,userDao2 at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:221) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1225) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1484) ... 13 more
報錯資訊,顯示只要一個,但找到2個,我們注釋掉一個
<!--<bean class="com.lusaisai.dao.UserDaoImpl" id="userDao" ></bean>-->
重新運行,看下結果
注入成功了,說明byType的自動個裝配方式中如果存在多個相同類型不同id的bean標籤,則拋出異常,如果沒有匹配的bean,則不自動裝配
最後看下構造方法注入
首先
default-autowire="constructor"
然後在UserService中注釋掉set方法,添加userDao的構造方法
private String userName; private UserDao userDao; public UserService(UserDao userDao) { this.userDao = userDao; } public String getUserName() { return userName; } public void setUserName(String userName1) { this.userName = userName1; } public UserDao getUserDao() { return userDao; }
,運行,看結果
注入成功了
我們把userDao的構造方法注釋了,再運行看下結果
沒注入成功,但也不報錯
好,到此為止,我們的xml配置將對象交給spring管理就講完了總結一下
可以看出,no和Default是不自動裝配的,byName和byType是通過set方法自動裝配的,同時要確保有空構造存在,我猜底層是用newInstance()實現的具體源碼後面再看
byName是根據set方法名自動裝配的,set方法名要和bean標籤的id相對應,否則,注入不成功,但不會報錯
byType是根據類型裝配的,如果存在多個該屬性類型的bean標籤,則拋出異常,如果沒有匹配的bean,則不自動裝配
constructor是根據構造方法來裝配的,如果容器中沒有一個構造函數參數類型的bean,則不自動裝配
可以看到這種xml格式其實是非常麻煩的,實際項目中我們一般通過註解來將對象交給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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" > <context:component-scan base-package="com.lusaisai"></context:component-scan> </beans>
我們來看例子,注意上面的spring配置文件中一個bean標籤都沒有了,加入了@Component註解,
這裡也可以用@Repository,@Service
,@Controller,可以將對象注入到spring容器進行管理,效果是一樣的
@Component public class UserDaoImpl implements UserDao { @Override public void test() { System.out.println("UserDaoImpl"); } }
這裡在依賴的對象上加入了@autowire註解,我猜底層是通過filed.set調用的,所以不需要set方法,這個以後看源碼了再講
@Component public class UserService { private String userName="lusai"; @Autowired private UserDao userDao; public String getUserName() { return userName; } public void setUserName(String userName1) { this.userName = userName1; } public UserDao getUserDao() { return userDao; } }
當然也可以將註解加在set方法上,如下
@Component
public class UserService {
private String userName="lusai";
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName1) {
this.userName = userName1;
}
public UserDao getUserDao() {
return userDao;
}
測試一下
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); System.out.println(userService.getUserName()); UserDao userDao = userService.getUserDao(); System.out.println(userDao); }
看下結果
可以看到,注入成功了
如果我們再給UserDao介面的另一個實現類也交給spring管理,會不會報錯呢?如下
@Component public class UserDaoImpl2 implements UserDao{ @Override public void test() { System.out.println("UserDaoImpl2"); } }
運行一下看結果
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lusaisai.dao.UserDao' available: expected single matching bean but found 2: userDaoImpl,userDaoImpl2 at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:221) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1225) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:668) ... 15 more
果然報錯了,說明同一個介面,最好不要寫多個實現類,那我非要寫多個實現類怎麼辦呢
我們可以用@Resource,並指定它的name屬性的別名為類名的首字母小寫,如下
@Component public class UserService { private String userName="lusai"; @Resource(name = "userDaoImpl2") private UserDao userDao; public String getUserName() { return userName; } public void setUserName(String userName1) { this.userName = userName1; } public UserDao getUserDao() { return userDao; } }
運行一下,看看結果
成功了
總結:先來張網上的圖
@Autowired與@Resource都可以用來裝配bean. 都可以寫在欄位上,或寫在setter方法上。兩者如果都寫在欄位上,那麼就不需要再寫setter方法
@Autowired默認按類型裝配(這個註解是屬業spring的),需要導入包org.springframework.beans.factory.annotation.Autowired 默認按照類型來進行裝配
@Resource(這個註解屬於java的),需要導入包javax.annotation.Resource。默認按照名稱進行裝配,名稱可以通過name屬性進行指定
最後,現在流行用javaconfig而不是xml來配置spring,這裡貼一下javaconfig的程式碼
第一種:相當於寫bean標籤,如下,這裡使用@bean註解 將UserService 和UserDaoImpl對象交給spring管理
@Configuration public class SpringConfig { @Bean public UserService userService(){ return new UserService(); } @Bean public UserDao userDao(){ return new UserDaoImpl(); } }
這裡注入依賴
public class UserService { private String userName="lusai"; @Autowired private UserDao userDao; public String getUserName() { return userName; } public void setUserName(String userName1) { this.userName = userName1; } public UserDao getUserDao() { return userDao; } }
這是測試demo
public static void main(String[] args) { AnnotationConfigApplicationContext annotationContext = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = (UserService) annotationContext.getBean("userService"); System.out.println(userService.getUserName()); UserDao userDao = userService.getUserDao(); System.out.println(userDao); }
看下結果
第二種:相當於掃描包,把上面的兩個@bean註解注釋了,開啟掃描
@Configuration @ComponentScan("com.lusaisai") public class SpringConfig { /*@Bean public UserService userService(){ return new UserService(); } @Bean public UserDao userDao(){ return new UserDaoImpl(); }*/ }
在UserService 和UserDaoImpl對象上加上@Component,將他們交給spring管理,如下
@Component public class UserDaoImpl implements UserDao { @Override public void test() { System.out.println("UserDaoImpl"); } }
@Component public class UserService { private String userName="lusai"; @Autowired private UserDao userDao; public String getUserName() { return userName; } public void setUserName(String userName1) { this.userName = userName1; } public UserDao getUserDao() { return userDao; } }
重新測試
可以看到,注入成功了.
最後 @Autowired與@Resource 和xml中的 default-autowire=”byName” 和byType是不是相同的原理的呢
先說結論:兩者不是同一套邏輯,這裡就後面看源碼的時候再來解釋吧