谈谈SpringFramework与IoC依赖查找

谈谈SpringFramework与IoC依赖查找

谈谈SpringFramework与IoC依赖查找插图 (1).png

生活不会按照你想要的方式进行,它会给你一段时间,让你孤独又迷惘,等你度过低潮,那些独处的时光必定能照亮你的路。走得最急的,都是最美的风景;伤得最深的,也总是那些最真的感情。收拾起心情,继续向前走,就会发现:错过花,你将收获雨,错过雨,你会遇到彩虹。

1. 面试题

先说下该篇文章可延伸出的面试题.

1. 谈谈SpringFramework / 说说你理解的SpringFramework

SpringFramework 是一个开源的、松耦合的、分层的、可配置的一站式企业级 Java 开发框架,它的核心是 IOC 与 AOP ,它可以更容易的构建出企业级 Java 应用,并且它可以根据应用开发的组件需要,整合对应的技术。

松耦合的: 为了描述IOC和AOP, 可能会延伸出IOC松耦合相关内容
可配置: 给后面的SpringBoot(约定大于配置)做铺垫
IOC 与 AOP: Inverse of Control 控制反转、Aspect Oriented Programming 面向切面编程


2. 为何使用SpringFramework

可通过如下几点进行描述:

  1. IOC 实现了组件之间的解耦
  2. AOP 切面编程将应用业务做统一或特定的功能增强, 可实现应用业务与增强逻辑的解耦
  3. 容器管理应用中使用的Bean、托管Bean的生命周期、事件与监听的驱动机制
  4. Web、事务控制、测试、与其他技术的整合

3. SpringFramework包含哪些模块?

  • beans、core、context、expression 【核心包】
  • aop 【切面编程】
  • jdbc 【整合 jdbc 】
  • orm 【整合 ORM 框架】
  • tx 【事务控制】
  • web 【 Web 层技术】
  • test 【整合测试】
  • ......

4. 依赖查找与依赖注入的对比

作用目标 实现方式
依赖查找(DL) 通常是类成员 使用上下文(容器)主动获取
依赖注入(DI) 可以是方法体内也可以是方法体外 借助上下文被动的接收

5. BeanFactory与ApplicationContext的对比

BeanFactory 接口提供了一个抽象的配置和对象的管理机制,

ApplicationContext 是 BeanFactory 的子接口,它简化了与 AOP 的整合、消息机制、事件机制,以及对 Web 环境的扩展( WebApplicationContext 等)

ApplicationContext 主要扩展了以下功能:

  • AOP 的支持( AnnotationAwareAspectJAutoProxyCreator 作用于 Bean 的初始化之后 )
  • 配置元信息( BeanDefinitionEnvironment 、注解等 )
  • 资源管理( Resource 抽象 )
  • 事件驱动机制( ApplicationEventApplicationListener
  • 消息与国际化( LocaleResolver
  • Environment 抽象( SpringFramework 3.1 以后)

2. SpringFramework发展史

在Spring技术之前,J2EE兴起,当时的J2EE学习成本极高,开发速度慢,开发出来的程序性能消耗也高,已经跟不上当时应用程序的需要。
在2002 年,Rod Johnson写了一本书名为《Expert One-on-One J2EE design and development》 ,书中对当时现有的 J2EE 应用的架构和EJB框架存在的臃肿、低效等问题提出了质疑,并且积极寻找和探索解决方案。

基于普通Java类和依赖注入的思想提出了更为简单的解决方案,这便是Spring框架核心思想的萌芽

过了 2 年,2004 年 SpringFramework 1.0.0 横空出世,随后 Rod Johnson 又写了一本书《Expert one-on-one J2EE Development without EJB》,当时在 J2EE 开发界引起了巨大轰动,这本书中直接告诉开发者完全可以不使用 EJB 开发 J2EE 应用,而是可以换用一种更轻量级、更简单的框架来代替,那就是 SpringFramework

那时在开发界是种种的质疑,大概是这样的,纳尼? 质疑IBM诸多大佬的设计精华,这个是什么人?为何如此嚣张? 而后 还是被一些开发者尝试使用了,使用后发现确实要比EJB好用,不那么臃肿,性能也有所改善,提供的一些特性也优于EJB,于是就慢慢转投SpringFramework

下面展示下SpringFramework重要版本的更新时间及主要特性

SpringFramework版本 对应jdk版本 重要特性
SpringFramework 1.x jdk 1.3 基于 xml 的配置
SpringFramework 2.x jdk 1.4 改良 xml 文件、初步支持注解式配置
SpringFramework 3.x Java 5 注解式配置、JavaConfig 编程式配置、Environment 抽象
SpringFramework 4.x Java 6 SpringBoot 1.x、核心容器增强、条件装配、WebMvc 基于 Servlet3.0
SpringFramework 5.x Java 8 SpringBoot 2.x、响应式编程、SpringWebFlux、支持 Kotlin

3. IOC依赖查找

基础框架搭建

  1. 创建Maven模块,这里以ioc-learning为例

  2. 引入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
  1. 创建配置文件 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>
  1. 声明普通类Person.java
public class Person {
}
  1. ioc-learning-dl.xml配置文件加入Persion的声明
<bean id="person" class="com.huodd.bean.Person"></bean>
  1. 创建启动类
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);
    }
}
  1. 运行打印
com.huodd.bean.Person@57baeedf

成功打印出 Person 的全限定类名 + 内存地址,证明编写成功。

3.1 byName 名称查找

上述基础框架中的步骤6

核心代码

Person person = (Person) factory.getBean("person");

3.2 byType 类型查找

1. 普通类

  1. 修改配置文件 ioc-learning-dl.xmlperson的声明中id属性去掉
<bean class="com.huodd.bean.Person"></bean>
  1. 修改启动类
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方法参数中直接传入Class类型 返回值也无需再进行强转

  1. 运行main方法 打印如下
com.huodd.bean.Person@61862a7f

2. 接口

  1. 创建接口demoDao 以及 实现类 DemoDaoImpl
public interface DemoDao {
    List<String> findAll();
}

public class DemoDaoImpl implements DemoDao{
    @Override
    public List<String> findAll() {
        return Arrays.asList("user1", "user2", "user3");
    }
}

  1. 修改配置文件 ioc-learning-dl.xml 加入 DemoDaoImpl的声明
<bean class="com.huodd.dao.DemoDaoImpl"></bean>
  1. 修改启动类
 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());
    }
  1. 运行main方法 打印结果如下
com.huodd.dao.DemoDaoImpl@7334aada
[user1, user2, user3]

由此可见 DemoDaoImpl 注入成功 并且BeanFactory可以根据接口类型找到对应的实现类

3.3 高级查找

ofType 根据类型查找多个

如果一个接口有多个实现类,如何一次性的把所有的实现类都取出来呢? 前面用到的getBean方法显然无法满足 需使用到ofType方法

  1. 继上面的代码 创建2个DemoDao的实现类 如下
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");
    }
}
  1. 修改配置文件 ioc-learning-dl.xml 加入新建的两个实现类的声明
 <bean class="com.huodd.dao.impl.DemoMysqlDaoImpl"></bean>
 <bean class="com.huodd.dao.impl.DemoOracleDaoImpl"></bean>
  1. 修改启动类
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]

细心的小伙伴可能会发现 为何这里读取配置文件的返回值使用的是ApplicationContext 而不使用BeanFactory

ApplicationContext 也是一个接口,通过IDEA的diagram查看类的继承链,可以看到该接口继承了BeanFactory

官方文章中有这样的解释:

org.springframework.beansorg.springframework.context 包是 SpringFramework 的 IOC 容器的基础。BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象。ApplicationContextBeanFactory 的子接口。它增加了:

  • 与 SpringFramework 的 AOP 功能轻松集成
  • 消息资源处理(用于国际化)
  • 事件发布
  • 应用层特定的上下文,例如 Web 应用程序中使用的 WebApplicationContext

如此说来 ApplicationContext 包含了 BeanFactory 的所有功能,并且还扩展了更多的特性

其实对于我们目前的最主要原因是BeanFactory 中木有getBeansOfType()这个方法~~~

withAnnotation 根据注解查找

IOC 容器还可以根据类上标注的注解来查找对应的 Bean

  1. 创建一个注解类
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface animal {
}
  1. 创建几个bean对象
@Animal
public class Dog {
}

@Animal
public class Cat {
}

public class Xiaoming {
}

其中只有Xiaoming这个类没有添加@Animal注解

  1. 修改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>
  1. 修改启动类
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);
        });
    }
}
  1. 运行main方法 打印结果如下
dog : com.huodd.bean.Dog@313ac989
cat : com.huodd.bean.Cat@4562e04d

延迟查找

对于一些特殊场景,需要依赖容器中某些特定的bean 但是当他们不存在时如何使用默认/或者缺省策略来处理逻辑呢?

这里我们先把xml配置文件中的 Dog 的声明暂时删掉

这样我们在获取dog的时候ctx.getBean(Dog.class) 就会报错 NoSuchBeanDefinitionException

  1. 现有方案启用缺省策略
Dog dog;
try {
    dog = ctx.getBean(Dog.class);
} catch (NoSuchBeanDefinitionException e) {
    // 找不到Dog时手动创建
    dog = new Dog();
}
System.out.println(dog);

这里我们把业务代码写在了catch代码块中,不够优雅,性能也有待改善,而且如果后期每个bean都这样处理,代码量巨大

  1. 改造下 获取之前检查
 Dog dog = ctx.containsBean("dog") ? (Dog) ctx.getBean("dog") : new Dog();

这里使用到了ApplicationContext中的方法 containsBean 用于检查容器中是否有指定的bean

该方法看似已经没有问题了,但是要考虑到该方法传递的参数只能传递bean的id 不能按照bean的类型去查找 如果bean的名字是其他的呢,工作量还是巨大的

  1. 使用延迟查找

该机制的大概思路为 当我们想要获取一个Bean的时候,先返回给我们一个包装类,等到我们真正去使用的时候再去“拆包”检查里面到底有没有该Bean对象

使用方法如下

ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);

上面代码可以看到 就是按照前面的思路进行处理的,返回了一个“包装”给我们,当我们使用的时候直接调用getObject方法

但如果 容器中没有该Bean 还是会报 NoSuchBeanDefinitionException ,下面会介绍下ObjectProvider提供的其他方法

  • getIfAvailable()该方法可以在找不到Bean的时候返回null 而不抛出异常

    可以使用如下方法实现

    Dog dog = dogProvider.getIfAvailable(Dog::new);
    
  • ifAvailable()该方法是在取到Bean后马上或者间歇的使用

    代码如下

    dogProvider.ifAvailable(dog -> System.out.println(dog)); // 或者使用方法引用
    

以上就是关于SpringFramework以及IoC的依赖查找相关内容,小伙伴可以再去顶部查看下面试题,是否都可以理解了并且掌握了呢.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容