談談SpringFramework與IOC依賴查找
1. 面試題
先說下該篇文章可延伸出的面試題.
1. 談談SpringFramework / 說說你理解的SpringFramework
SpringFramework 是一個開源的、松耦合的、分層的、可配置的一站式企業(yè)級 Java 開發(fā)框架,它的核心是 IOC 與 AOP ,它可以更容易的構建出企業(yè)級 Java 應用,并且它可以根據(jù)應用開發(fā)的組件需要,整合對應的技術。
松耦合的: 為了描述IOC和AOP, 可能會延伸出IOC松耦合相關內(nèi)容 可配置: 給后面的SpringBoot(約定大于配置)做鋪墊 IOC 與 AOP: Inverse of Control 控制反轉、Aspect Oriented Programming 面向切面編程
2. 為何使用SpringFramework
可通過如下幾點進行描述:
IOC 實現(xiàn)了組件之間的解耦
AOP 切面編程將應用業(yè)務做統(tǒng)一或特定的功能增強, 可實現(xiàn)應用業(yè)務與增強邏輯的解耦
容器管理應用中使用的Bean、托管Bean的生命周期、事件與監(jiān)聽的驅動機制
Web、事務控制、測試、與其他技術的整合
3. SpringFramework包含哪些模塊?
- beans、core、context、expression 【核心包】
- aop 【切面編程】
- jdbc 【整合 jdbc 】
- orm 【整合 ORM 框架】
- tx 【事務控制】
- web 【 Web 層技術】
- test 【整合測試】
- ......
4. 依賴查找與依賴注入的對比
5. BeanFactory與ApplicationContext的對比
BeanFactory 接口提供了一個抽象的配置和對象的管理機制,
ApplicationContext 是 BeanFactory 的子接口,它簡化了與 AOP 的整合、消息機制、事件機制,以及對 Web 環(huán)境的擴展( WebApplicationContext 等)
ApplicationContext 主要擴展了以下功能:
- AOP 的支持( AnnotationAwareAspectJAutoProxyCreator 作用于 Bean 的初始化之后 )
- 配置元信息( BeanDefinition 、Environment 、注解等 )
- 資源管理( Resource 抽象 )
- 事件驅動機制( ApplicationEvent 、ApplicationListener )
- 消息與國際化( LocaleResolver )
- Environment 抽象( SpringFramework 3.1 以后)
2. SpringFramework發(fā)展史
在Spring技術之前,J2EE興起,當時的J2EE學習成本極高,開發(fā)速度慢,開發(fā)出來的程序性能消耗也高,已經(jīng)跟不上當時應用程序的需要。在2002 年,Rod Johnson寫了一本書名為《Expert One-on-One J2EE design and development》 ,書中對當時現(xiàn)有的 J2EE 應用的架構和EJB框架存在的臃腫、低效等問題提出了質疑,并且積極尋找和探索解決方案。
基于普通Java類和依賴注入的思想提出了更為簡單的解決方案,這便是Spring框架核心思想的萌芽
過了 2 年,2004 年 SpringFramework 1.0.0 橫空出世,隨后 Rod Johnson 又寫了一本書**《Expert one-on-one J2EE Development without EJB》**,當時在 J2EE 開發(fā)界引起了巨大轟動,這本書中直接告訴開發(fā)者完全可以不使用 EJB 開發(fā) J2EE 應用,而是可以換用一種更輕量級、更簡單的框架來代替,那就是 SpringFramework 。
那時在開發(fā)界是種種的質疑,大概是這樣的,納尼? 質疑IBM諸多大佬的設計精華,這個是什么人?為何如此囂張? 而后 還是被一些開發(fā)者嘗試使用了,使用后發(fā)現(xiàn)確實要比EJB好用,不那么臃腫,性能也有所改善,提供的一些特性也優(yōu)于EJB,于是就慢慢轉投SpringFramework
下面展示下SpringFramework重要版本的更新時間及主要特性
3. IOC依賴查找
基礎框架搭建
1.創(chuàng)建Maven模塊,這里以ioc-learning為例
2.引入依賴
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>5.2.8.RELEASE</version>
- </dependency>
3.創(chuàng)建配置文件 ioc-learning-dl.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">
- </beans>
4.聲明普通類Person.java
- public class Person {
- }
5.ioc-learning-dl.xml配置文件加入Persion的聲明
- <bean id="person" class="com.huodd.bean.Person"></bean>
6.創(chuàng)建啟動類
- public class DlApplication {
- public static void main(String[] args) {
- // 讀取配置文件 使用接口 BeanFactory 接收
- BeanFactory factory = new ClassPathXmlApplicationContext("dl/ioc-learning-dl.xml");
- // 通過配置文件中聲明的 id 進行對象的獲取
- Person person = (Person) factory.getBean("person");
- System.out.println(person);
- }
- }
7.運行打印
- com.huodd.bean.Person@57baeedf
成功打印出 Person 的全限定類名 + 內(nèi)存地址,證明編寫成功。
3.1 byName 名稱查找
上述基礎框架中的步驟6
核心代碼
- Person person = (Person) factory.getBean("person");
3.2 byType 類型查找
1. 普通類
1.修改配置文件 ioc-learning-dl.xml 將person的聲明中id屬性去掉
- <bean class="com.huodd.bean.Person"></bean>
2.修改啟動類
- public static void main(String[] args) {
- BeanFactory factory = new ClassPathXmlApplicationContext("dl/ioc-learning-dl.xml");
- // Person person = (Person) factory.getBean("person");
- Person person = factory.getBean(Person.class);
- System.out.println(person);
- }
getBean方法參數(shù)中直接傳入Class類型 返回值也無需再進行強轉
3.運行main方法 打印如下
- com.huodd.bean.Person@61862a7f
2. 接口
1.創(chuàng)建接口demoDao 以及 實現(xiàn)類 DemoDaoImpl
- public interface DemoDao {
- List<String> findAll();
- }
- public class DemoDaoImpl implements DemoDao{
- @Override
- public List<String> findAll() {
- return Arrays.asList("user1", "user2", "user3");
- }
- }
2.修改配置文件 ioc-learning-dl.xml 加入 DemoDaoImpl的聲明
- <bean class="com.huodd.dao.DemoDaoImpl"></bean>
3.修改啟動類
- public static void main(String[] args) {
- BeanFactory factory = new ClassPathXmlApplicationContext("dl/ioc-learning-dl.xml");
- DemoDao demoDao = factory.getBean(DemoDaoImpl.class);
- System.out.println(demoDao);
- System.out.println(demoDao.findAll());
- }
4.運行main方法 打印結果如下
- com.huodd.dao.DemoDaoImpl@7334aada
- [user1, user2, user3]
由此可見 DemoDaoImpl 注入成功 并且BeanFactory可以根據(jù)接口類型找到對應的實現(xiàn)類
3.3 高級查找
ofType 根據(jù)類型查找多個
如果一個接口有多個實現(xiàn)類,如何一次性的把所有的實現(xiàn)類都取出來呢? 前面用到的getBean方法顯然無法滿足 需使用到ofType方法
1.繼上面的代碼 創(chuàng)建2個DemoDao的實現(xiàn)類 如下
- public class DemoMysqlDaoImpl implements DemoDao {
- @Override
- public List<String> findAll() {
- return Arrays.asList("mysql_user1", "mysql_user2", "mysql_user3");
- }
- }
- public class DemoOracleDaoImpl implements DemoDao {
- @Override
- public List<String> findAll() {
- return Arrays.asList("oracle_user1", "oracle_user2", "oracle_user3");
- }
- }
2.修改配置文件 ioc-learning-dl.xml 加入新建的兩個實現(xiàn)類的聲明
- <bean class="com.huodd.dao.impl.DemoMysqlDaoImpl"></bean>
- <bean class="com.huodd.dao.impl.DemoOracleDaoImpl"></bean>
3.修改啟動類
- public static void main(String[] args) {
- ApplicationContext ctx = new ClassPathXmlApplicationContext("dl/ioc-learning-dl.xml");
- Map<String, DemoDao> beans = ctx.getBeansOfType(DemoDao.class);
- beans.forEach((beanName, bean) -> {
- System.out.println(beanName + " : " + bean.toString());
- });
- }
運行main方法 打印結果如下
- com.huodd.dao.impl.DemoMysqlDaoImpl#0 : [mysql_user1, mysql_user2, mysql_user3]
- com.huodd.dao.impl.DemoOracleDaoImpl#0 : [oracle_user1, oracle_user2, oracle_user3]
細心的小伙伴可能會發(fā)現(xiàn) 為何這里讀取配置文件的返回值使用的是ApplicationContext 而不使用BeanFactory
ApplicationContext 也是一個接口,通過IDEA的diagram查看類的繼承鏈,可以看到該接口繼承了BeanFactory
官方文章中有這樣的解釋:
org.springframework.beans 和 org.springframework.context 包是 SpringFramework 的 IOC 容器的基礎。BeanFactory 接口提供了一種高級配置機制,能夠管理任何類型的對象。ApplicationContext 是 BeanFactory 的子接口。它增加了:
- 與 SpringFramework 的 AOP 功能輕松集成
- 消息資源處理(用于國際化)
- 事件發(fā)布
- 應用層特定的上下文,例如 Web 應用程序中使用的 WebApplicationContext
如此說來 ApplicationContext 包含了 **BeanFactory 的所有功能,**并且還擴展了更多的特性
其實對于我們目前的最主要原因是BeanFactory 中木有getBeansOfType()這個方法~~~
withAnnotation 根據(jù)注解查找
IOC 容器還可以根據(jù)類上標注的注解來查找對應的 Bean
1.創(chuàng)建一個注解類
- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE)
- public @interface animal {
- }
2.創(chuàng)建幾個bean對象
- @Animal
- public class Dog {
- }
- @Animal
- public class Cat {
- }
- public class Xiaoming {
- }
其中只有Xiaoming這個類沒有添加@Animal注解
3.修改XML配置文件,添加如下三個聲明
- <bean id="dog" class="com.huodd.bean.Dog"></bean>
- <bean id="cat" class="com.huodd.bean.Cat"></bean>
- <bean id="xiaoming" class="com.huodd.bean.Xiaoming"></bean>
4.修改啟動類
- public class DlApplication {
- public static void main(String[] args) {
- ApplicationContext ctx = new ClassPathXmlApplicationContext("dl/ioc-learning-dl.xml");
- Map<String, Object> beans = ctx.getBeansWithAnnotation(Animal.class);
- beans.forEach((beanName, bean) -> {
- System.out.println(beanName + " : " + bean);
- });
- }
- }
5.運行main方法 打印結果如下
- dog : com.huodd.bean.Dog@313ac989
- cat : com.huodd.bean.Cat@4562e04d
延遲查找
對于一些特殊場景,需要依賴容器中某些特定的bean 但是當他們不存在時如何使用默認/或者缺省策略來處理邏輯呢?
這里我們先把xml配置文件中的 Dog 的聲明暫時刪掉
這樣我們在獲取dog的時候ctx.getBean(Dog.class)就會報錯
NoSuchBeanDefinitionException
1.現(xiàn)有方案啟用缺省策略
- Dog dog;
- try {
- dog = ctx.getBean(Dog.class);
- } catch (NoSuchBeanDefinitionException e) {
- // 找不到Dog時手動創(chuàng)建
- dog = new Dog();
- }
- System.out.println(dog);
這里我們把業(yè)務代碼寫在了catch代碼塊中,不夠優(yōu)雅,性能也有待改善,而且如果后期每個bean都這樣處理,代碼量巨大
2.改造下 獲取之前檢查
- Dog dog = ctx.containsBean("dog") ? (Dog) ctx.getBean("dog") : new Dog();
這里使用到了ApplicationContext中的方法 containsBean 用于檢查容器中是否有指定的bean
該方法看似已經(jīng)沒有問題了,但是要考慮到該方法傳遞的參數(shù)只能傳遞bean的id 不能按照bean的類型去查找 如果bean的名字是其他的呢,工作量還是巨大的
3.使用延遲查找
該機制的大概思路為 當我們想要獲取一個Bean的時候,先返回給我們一個包裝類,等到我們真正去使用的時候再去“拆包”檢查里面到底有沒有該Bean對象
使用方法如下
- ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);
上面代碼可以看到 就是按照前面的思路進行處理的,返回了一個“包裝”給我們,當我們使用的時候直接調用getObject方法
但如果 容器中沒有該Bean 還是會報 NoSuchBeanDefinitionException ,下面會介紹下ObjectProvider提供的其他方法
- getIfAvailable()該方法可以在找不到Bean的時候返回null 而不拋出異常
可以使用如下方法實現(xiàn)
- Dog dog = dogProvider.getIfAvailable(Dog::new);
- ifAvailable()該方法是在取到Bean后馬上或者間歇的使用
代碼如下
- dogProvider.ifAvailable(dog -> System.out.println(dog)); // 或者使用方法引用
以上就是關于SpringFramework以及IoC的依賴查找相關內(nèi)容,小伙伴可以再去頂部查看下面試題,是否都可以理解了并且掌握了呢?