4

谈谈SpringFramework与IoC依赖查找

 3 years ago
source link: https://segmentfault.com/a/1190000038983144
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

谈谈SpringFramework与IoC依赖查找

r6BJvav.png!mobile

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

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依赖查找

基础框架搭建

ioc-learning
<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 的子接口。它增加了:

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的依赖查找相关内容,小伙伴可以再去顶部查看下面试题,是否都可以理解了并且掌握了呢.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK