写点什么

谈谈 SpringFramework 与 IoC 依赖查找

发布于: 2021 年 01 月 14 日

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

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.xjdk 1.3 基于 xml 的配置 SpringFramework 2.xjdk 1.4 改良 xml 文件、初步支持注解式配置 SpringFramework 3.xJava 5 注解式配置、JavaConfig 编程式配置、Environment 抽象 SpringFramework 4.xJava 6SpringBoot 1.x、核心容器增强、条件装配、WebMvc 基于 Servlet3.0SpringFramework 5.xJava 8SpringBoot 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 对象

@Animalpublic class Dog {}
@Animalpublic 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@313ac989cat : 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 而不抛出异常

  • ifAvailable()该方法是在取到 Bean 后马上或者间歇的使用

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


用户头像

还未添加个人签名 2020.09.07 加入

还未添加个人简介

评论

发布
暂无评论
谈谈SpringFramework与IoC依赖查找