Spring深入浅出IoC(上)

简介: 二、IoC容器IoC(Inversion of Control,控制反转)也被称为DI(Dependency Injection,依赖注入)。在实际操作中,假如A调用了B,我们可以称A依赖于B,此时A是调用者,B是被调用者,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。在Spring依赖注入的模式下,创建被调用者的工作不再由调用者来完成,因此称为控制反转,创建被调用者实例的工作由Spring容器来完成,然后注入给调用者,因为也称为依赖注入。


二、IoC容器



IoC(Inversion of Control,控制反转)也被称为DI(Dependency Injection,依赖注入)。在实际操作中,假如A调用了B,我们可以称A依赖于B,此时A是调用者,B是被调用者,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。在Spring依赖注入的模式下,创建被调用者的工作不再由调用者来完成,因此称为控制反转,创建被调用者实例的工作由Spring容器来完成,然后注入给调用者,因为也称为依赖注入。在Spring中完成这个功能的两个重要的接口:

  • BeanFactory:提供一种高级的配置机制能够管理任何类型的对象。
  • ApplicationContext:是BeanFactory接口的子接口,它更容易集成Spring的AOP功能、消息资源处理、事件发布和特定的上下文应用层。比如:WebApplicationContext

在Spring中,由Spring的IoC容器管理的对象叫做beans,bean就是由Spring IoC容器实例化、组装和以其他方式管理的对象。此外bean只是你应用中许多对象中的一个,Beans以及他们之间的依赖关系是通过容器配置元数据反映出来的。下图是Spring如何工作的展示,你应用中所有的类都由元数据组装到一起,所以当ApplicationContext创建和实例化后,你就有一个完全可配置和中执行的系统或应用。

微信图片114.png

在使用Spring框架时,配置Bean的方式有三种:

  • 基于XML配置
  • 基于注解配置
  • 基于Java配置

在后面我们会讲到基于三种方式的Bean配置,为了由浅入深地讲解, 使用BeanFactory的实现类来写基于XML配置的Bean管理,使用ApplicationContext的实现类来实现基于注解和基于Java的配置。


1. BeanFactory



使用前面创建好的Maven项目开始实验,使用简单的XmlBeanFactory来实现Spring的IoC功能。


1.1 使用容器


  1. 创建两个实体类并且存在依赖关系
// 这个类是被调用方
public class BeanProvider {
    public void sayHello(String name, Integer age) {
        System.out.println("name = [" + name + "], age = [" + age + "]");
    }
}
// 这个类是调用方
public class BeanExample {
    private String name;
    private Integer age;
    private BeanProvider beanProvider;
    // Getter and Setter
    public void run() {
        this.beanProvider.sayHello(this.name, this.age);
    }
}

2、配置元数据使用Spring来管理类

在项目的src/main/resources目录下创建bean.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
         http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample"/>
    <bean id="beanProvider" class="com.ajn.spring.beanfactory.bean.BeanProvider"/>

3、编写主方法类开始实验

public static void main(String[] args) {
    BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
    BeanExample bean = (BeanExample) beanFactory.getBean("beanExample");
    bean.run();

此时运行代码是会报空指针的,因为BeanExample类中依赖的beanProvider还未注入。


1.2 依赖注入


1.2.1 属性注入

<bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample">
    <property name="name" value="ajn"/>
    <property name="age" value="24"/>
    <property name="beanProvider" ref="beanProvider"/>
</bean>

将前面配置好的bean添加property子标签,这些属性会通过set方法注入到创建的实例中。此时运行主方法成功输出:

name = [ajn], age = [24]

我们发现BeanExample类不仅注入了基本数据类型,还有引用的beanProvider。配置实例属性的值有两种方式:

  • value:等值注入,将具体的值注入到实例属性中
  • ref:引用注入,将被依赖类的实例引用注入到属性中

当有依赖注入的情况时,所有参与的类都必须交由Spring容器管理。


1.2.2 构造器注入

BeanExample类,添加一个全参构造方法:

public BeanExample() {
}
public BeanExample(String name, Integer age, BeanProvider beanProvider) {
    this.name = name;
    this.age = age;
    this.beanProvider = beanProvider;
}

注上面配置的bean子标签替换成constructor-arg配置,使用index指定参数位置:

<bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample">
    <constructor-arg index="0" value="ajn"/>
    <constructor-arg index="1" value="24"/>
    <constructor-arg index="2" ref="beanProvider"/>
</bean>

此时运行主方法输出结果是一样的,不过在这里我们要注意,Spring创建实例也是通过类的构造器来创建的,如果我们配置了一个全参的构造器,那么就无法使用无参构造器,这种情况我们在编码过程中要避免,养成一个好的习惯,创建有参构造器时,也要创建一个无参构造器。


1.2.3 静态工厂注入


创建一个静态工厂类如下:

public class BeanProivderFactory {
    public static BeanProvider getBeanProvider() {
        return new BeanProvider();
    }
}

配置通过静态工厂方式注入:

<bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample">
    <constructor-arg index="0" value="ajn"/>
    <constructor-arg index="1" value="24"/>
    <constructor-arg index="2" ref="beanProviderFactory"/>
</bean>
<bean id="beanProviderFactory" class="com.ajn.spring.beanfactory.bean.BeanProivderFactory" factory-method="getBeanProvider"/>

首先把工厂类的bean,然后配置factory-method属性指定工厂方法,再把beanProvider的引用换成工厂类的引用。运行主方法会输出同样的内容。


1.3 自动注入(autowire)


<bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample" autowire="byName">
    <property name="name" value="ajn"/>
    <property name="age" value="24"/>
</bean>

将Bean添加属性autowire值为byName配置自动注入,几种常用的自动注入方式:

  • no:默认值,不使用自动装配,使用注入
  • byName:通过属性名称和bean的名称自动注入
  • byType:通过属性类型和bean的类型自动注入
  • constructor:通过构造器参数以byType的方式注入

可以在 标签添加 default-autowire 属性来指定默认自动注入方式。此时Spring中所有的bean都是自动注入的候选者,有时候我们不希望其中某一个bean通过自动注入机制注入到其他bean,可以在这个bean上添加 autowire-candidate="false" 属性,这样就从自动注入中排除这个bean。还有一种情况,当有多个候选者bean都符合注入的要求时,比如一个接口有多个实现类,这时Spring不知道会注入哪一个,这时可以在一个bean上的 标签上添加 primary="true" 属性来确定主要候选者。

这里我们最简单地展示注入,还有一些特殊情况配置,比如:注入内部类、注入集合、注入空值等。


1.4 指定依赖关系(depends-on)


有时候Bean之间的依赖关系并不是特别明确,比如初始化一个静态类,可以使用depends-on属性来明确指定在初始化该bean之前,强制初始化一个或多个bean。例如:

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

depends-on配置多个bean分隔符可以用逗号、空格或分号

除了指定依赖关系之外,标签还可以配置抽象abstract="true"属性和配置继承parent="accountDao"指定继承关系。


1.5 作用域(scope)


1.5.1 单例作用域

默认Spring容器管理的bean都会以单例模式创建一个实例,当这个bean被别的bean引用时,都只会引用这一个实例,Spring的单例模式不同于我们平时使用的单例模式,四人帮定义的单例是指在JVM进程中仅有一个实例,而Spring的单例是指在一个Spring容器中仅有一个实例。

微信图片113.png

XML模式配置单例:

<!--默认作用域为单例-->
<bean id="accountService" class="com.something.DefaultAccountService"/>
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>


1.5.2 原型作用域

每次请求以原型作用域模式创建的bean时都会创建一个该bean新的实例,也就是在该bean被注入其他bean或者使用getBean()方法来请求它的时候。一般情况下,我们会对有状态bean使用原型作用域,对无状态bean使用单例作用域。比如:DAO层不会配置成原型作用域,因为典型的DAO不会保持任何会话状态。与其他作用域不同的是,Spring不管理一个原型作用域bean的完整的生命周期。

微信图片112.png

XML模式配置原型:

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

除了单例模式和原型模式的作用域之外,还有Request, Session, Application, 和WebSocket作用域,我们还可以自定义作用域,具体怎么操作自己拓展。


1.6 懒加载(lazy-init)


默认情况下,IoC容器会以单例模式初始化创建好所有的bean实例,通常这种预先实例化的方案是可取的,因为配置或环境的错误在容器启动的时候就会被立即发现。如果这种方案不能满足你的需求,可以配置在第一次请求bean的时候创建实例,在XML配置中,在 标签使用 lazy-init 属性:

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>

但是,如果一个懒加载的bean被别的非懒加载单例bean依赖时,它也会在容器启动的时候创建,因为必须要保持一个安全的依赖关系。


1.7 使用外部属性值(${ })


我们可以将一些配置属性,比如:数据库连接地址、用户名和密码等相关配置,这些配置通常是与环境相关并且修改的几率很大,所以为了降低修改主XML文件或容器文件的复杂性和风险,我们会把它们抽象到一个.properties文件中,并通过占位符来注入到bean属性中。

修改bean.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder location="classpath:spring.properties"/>
    <bean id="beanExample" class="com.ajn.spring.beanfactory.bean.BeanExample">
        <property name="name" value="${name}"/>
        <property name="age" value="${age}"/>
        <property name="beanProvider" ref="beanProvider"/>
    </bean>
    <bean id="beanProvider" class="com.ajn.spring.beanfactory.bean.BeanProvider"/>
</beans>

src/main/resources目录下新建资源文件spring.properties如下:

name=ajn
 age=24

classpath表示类所在的路径,${key}为占位符,key对应.properties文件中的key。


2. ApplicationContext



前面我们使用简单的BeanFactory接口完成了IoC容器管理Bean的实验,后面将使用功能更多的ApplicationContext来实现其他功能。该接口下有很多开箱即用的实现类,比如ClassPathXmlApplicationContextFileSystemXmlApplicationContext,因为我们在实际应用中,用的最多的也是该接口,它是BeanFactory的子接口,所有功能更强大。修改主方法类:

public static void main(String[] args) {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
    // applicationContext.registerShutdownHook();
    BeanExample bean = (BeanExample) applicationContext.getBean("beanExample");
    bean.run();
    applicationContext.close();

applicationContext.registerShutdownHook();applicationContext.close();这两个方法都是为了演示销毁的生命周期而用,至于它们有什么区别自己拓展。


2.1 生命周期

微信图片111.jpg

生命周期

  • InitializingBean接口只含有一个方法afterPropertiesSet(),在容器初始化bean之前执行
  • DisposableBean接口也只有一个方法destroy(),在容器销毁bean之后执行
  • BeanPostProcessor接口提供两个接口postProcessBeforeInitialization()postProcessAfterInitialization分别在初始化前后执行,如果你需要自定义一些Spring默认不提供的特性或生命周期行为,你可以使用它
  • 一般不推荐使用InitializingBean接口和DisposableBean接口,因为它们会加大代码与Spring的耦合,推荐使用bean的自定义初始化方法init-method和销毁方法destroy-method,配置方法如下:

添加初始化和销毁方法:

public class BeanProvider {
    // ...
    public void init() {
        System.out.println("BeanProvider.init");
    }
    public void destroy() {
        System.out.println("BeanProvider.destroy");
    }
}

然后在对应标签添加两个属性:

<bean id="beanProvider" class="com.ajn.spring.beanfactory.bean.BeanProvider" init-method="init" destroy-method="destroy"/>

在基于注解配置情况时,可以使用 @PostConstruct@PreDestroy 来代替init-methoddestroy-method


Aware接口

容器管理的bean一般不需要了解容器的状态和直接使用容器,但是在某些情况下,是需要在bean中直接对IoC容器进行操作的,这时候,就需要在Bean中设定对容器的感知。该类接口就会提供对应操作的感知,例如:ApplicationContextAwareBeanNameAware,可以获取相应的ApplicationContext和BeanName的信息。需要注意的是,这些接口会将代码绑定到Spring API而且不遵循IoC风格,所以我们只建议将他们使用于需要以编程方式访问容器的基础框架bean。


BeanPostProcessor接口

BeanPostProcessor接口可以用来自定义扩展bean,实现该接口来提供你自己的实例逻辑、依赖解析逻辑等等。如果要在Spring容器完成实例化、配置和初始化bean之后实现一些自定义逻辑,你也可以定义一个或多个BeanValidationPostProcessor接口的实现类来完成。


2.2 生命周期实例


新建类BeanLifeCycle.java并实现相关接口如下:

public class BeanLifeCycle implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, BeanPostProcessor, InitializingBean, DisposableBean {
    @Override
    public void setBeanName(String name) {
        System.out.println("BeanLifeCycle.setBeanName");
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("BeanLifeCycle.setBeanFactory");
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("BeanLifeCycle.setApplicationContext");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BeanLifeCycle.afterPropertiesSet");
    }
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanLifeCycle.postProcessBeforeInitialization beanName: " + beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanLifeCycle.postProcessAfterInitialization beanName: " + beanName);
        return bean;
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("BeanLifeCycle.destroy");
    }
    private void init() {
        System.out.println("BeanLifeCycle.init");
    }
    private void destroyByClass() {
        System.out.println("BeanLifeCycle.destroyByClass");
    }
}

添加XML配置如下:

<bean id="beanLifeCycle" class="com.ajn.spring.applicationcontext.BeanLifeCycle" init-method="init" destroy-method="destroyByClass"/>

运行输出结果:

BeanLifeCycle.setBeanName
BeanLifeCycle.setBeanFactory
BeanLifeCycle.setApplicationContext
BeanLifeCycle.afterPropertiesSet
BeanLifeCycle.init
BeanLifeCycle.postProcessBeforeInitialization beanName: beanProvider
BeanProvider.init
BeanLifeCycle.postProcessAfterInitialization beanName: beanProvider
BeanLifeCycle.postProcessBeforeInitialization beanName: beanExample
BeanExample.init
BeanLifeCycle.postProcessAfterInitialization beanName: beanExample
name = [ajn], age = [24]
BeanExample.destroy
BeanProvider.destroy
BeanLifeCycle.destroy
BeanLifeCycle.destroyByClass


目录
相关文章
|
2月前
|
XML Java 数据格式
Spring IoC容器初始化过程(xml形式)
Spring IoC容器初始化过程(xml形式)
46 0
|
2月前
|
Java Spring
Spring5深入浅出篇:Spring中ioc(控制反转)与DI(依赖注入)
Spring5深入浅出篇:Spring中ioc(控制反转)与DI(依赖注入)
|
1月前
|
Java 数据库连接 API
【Spring】1、Spring 框架的基本使用【读取配置文件、IoC、依赖注入的几种方式、FactoryBean】
【Spring】1、Spring 框架的基本使用【读取配置文件、IoC、依赖注入的几种方式、FactoryBean】
49 0
|
20天前
|
XML Java 数据格式
Spring(一)IOC小案例
Spring(一)IOC小案例
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (下)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
|
2月前
|
XML 缓存 Java
Spring IoC原理解读
Spring IoC原理解读
26 0
|
2月前
|
Java 测试技术 开发者
探究 Spring Boot 的核心:IOC 和 AOP
Spring Boot 作为一种简化 Spring 应用开发的工具,继承了 Spring 框架的核心概念,其中最重要的是控制反转(IOC)和面向切面编程(AOP)。它们是 Spring 框架的基础,同时也深深植根于 Spring Boot 中。本文将讨论 IOC 和 AOP 的概念以及它们在 Spring Boot 中的应用。
59 4
|
2月前
|
缓存 Java uml
SpringBoot2 | Spring IOC 流程中核心扩展接口的12个扩展点源码分析(十一)
SpringBoot2 | Spring IOC 流程中核心扩展接口的12个扩展点源码分析(十一)
37 0
|
3月前
|
存储 设计模式 Java

热门文章

最新文章