谈谈SpringFramework与IoC依赖查找

5 篇文章 0 订阅
3 篇文章 0 订阅

谈谈SpringFramework与IoC依赖查找

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

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

getObject`方法

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

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

    可以使用如下方法实现

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

    代码如下

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

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iBaoxing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值