Spring5全栈知识体系

Spring 5 中文解析核心篇-IoC容器之依赖关系

2020-08-27  本文已影响0人  青年IT男

一个典型的企业应用不是由一个简单的对象(在Spring中叫bean)组成。即使是最简单的应用程序,也有一些对象协同工作,以呈现最终用户视为一致的应用程序。(备注:相当于所有的bean一起协同工作对于用户是无感知的)。下一部分将说明如何从定义多个独立的Bean对象协作去实现应用程序的目标。

1.4.1 依赖注入

依赖注入是从工厂方法构造或返回的实例并通过设置对象实例的构造参数、工厂方法参数或者属性去定义它的依赖关系(与它一起工作的对象)的过程。当创建bean的时候容器注入这些依赖。从根本上讲,此过程是通过使用类的直接构造或服务定位器模式来控制bean自身依赖关系的实例化或位置的bean本身的逆过程(因此称为控制反转)。

DI(依赖注入)使代码更简洁和解偶,当为这些对象提供依赖时候是更高效的。(通过依赖注入来注入对象更高效)。对象不用去主动查找它的依赖项并且不用知道依赖类的位置。这样的结果是我们的类变成了更容易被测试,特别地,当这些依赖在接口或者抽象类上时,在单元测试中去使用stub或者mock方式去实现这些接口和抽象类。

DI(依赖注入)存在两个重要的变体:基于构造函数的依赖注入基于Setter的依赖注入

  <bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
      <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
  </bean>
  
  <bean id="anotherExampleBean" class="examples.AnotherBean"/>
  <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

ExampleBean对应的类定义:

public class ExampleBean {

    // a private constructor
  private ExampleBean(...) {
        ...
    }

    //静态工厂方法
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}

静态工厂方法的参数由<constructor-arg />元素提供,与实际使用构造函数时完全相同。通过工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同(尽管在此例中为相同)。实例(非静态)工厂方法可以以基本上相同的方式使用(除了使用factory-bean属性代替class属性之外),因此不在这里详细讨论。

参考代码:com.liyong.ioccontainer.starter.XmlDependecyInjectContainer

1.4.2 详细介绍依赖项和配置

在前面的章节提到,我们可以定义bean的属性和构造函数参数去引用其他被管理的bean(协同者)或者作为一个内联值定义。Spring的XML元数据配置支持子元素类型<property/>和<constructor-arg/>这个为了这个设计目的。

前面bean定义片段和下面的片段相同:

  <bean id="theTargetBean" class="..." />
  <bean id="client" class="...">
      <property name="targetName" value="theTargetBean"/>
  </bean>

参考代码:com.liyong.ioccontainer.starter.XmlIocContainer

第一种形式优于第二种形式,因为使用idref标记可使容器在部署时验证所引用的名Bean实际上是否存在。在第二个变体中,不对传递给客户端bean的targetName属性的值执行验证。拼写错误仅在实际实例化客户端bean时才发现(最有可能导致致命的结果)。如果这个客户端bean是原型bean,这个拼写和结果异常可能在这个容器部署后很久才被发现。

idref标签元素上的的local属性在spring-beans.xsd 4.0后不在支持。因为它没有提供常规的bean引用值。当升级到spring-beans.xsd 4.0更改idref localidref bean

idref标签元素带来的价值的地方是在ProxyFactoryBean bean定义中配置AOP拦截器。指定拦截器名称时使用<idref/>元素可防止你拼写错误的拦截器ID。

1.4.3 使用depends-on

如果bean依赖其他bean,也就是意味着bean需要设置依赖的bean属性。典型地,我们可以基于XML配置元数据使用ref去完成。然而,一些bean之间的依赖不是直接的。一个例子是在类中一个静态的初始化器需要被触发,例如:数据库驱动注册。depends-on属性能够显示地强制一个或多个bean在依赖bean初始化之前初始化。下面的例子使用depends-on属性去表达对一个简单bean的依赖。

<!--beanOne依赖manager-->
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

为了表达多个bean的依赖,提供一个bean名称的集合列表作为depends-on属性值(,;空格是有效的分隔符)。

<!--beanOne依赖manager,accountDao-->
<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" />

仅仅在单例bean场景下,depends-on属性能够指定初始化时间依赖和对应的销毁时间依赖。与给定bean定义依赖关系的从属bean首先被销毁,然后再销毁给定bean本身(备注:被依赖的bean先销毁,在销毁宿主bean)。因此,depends-on可以控制关闭顺序。

参考代码:com.liyong.ioccontainer.starter.XmlDependOnSpaceIocContainer

1.4.4 bean的懒加载

默认情况下,ApplicationContext实现更早的创建和配置所有的单例bean作为初始化过程的一部分。通常地,这个前置初始化是可取的,因为错误的配置或环境变量被立即的发现,而不是几个小时甚至几天后才被发现。当这个行为不是可取的时候,我们可以通过标记bean作为一个懒加载的单例bean去阻止提前初始化。一个懒加载bean告诉容器当第一次请求的时候去创建实例而不是在容器启动时候。

在XML配置中,这个行为通过在<bean/>元素的属性lazy-init控制的。下面例子展示:

<!--设置bean延迟初始化 注意:Spring中的bean默认是单例的-->
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

当前面的配置通过ApplicationContext加载并启动时,这个lazy bean没有被提前的初始化,而not.lazy bean被尽早的初始化。

然而,当一个懒加载bean是另一个单例bean的依赖时候,这个懒加载不是懒加载的。ApplicationContext在启动时创建这个懒加载bean,因为它必须满足这个单例bean的依赖。这个懒加载bean被注入到一个单例bean所以它不是懒加载的。

我们也可以在容器级别通过使用<beans>元素的default-lazy-init属性控制懒加载,下面例子展示怎样使用:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

参考代码:com.liyong.ioccontainer.starter.XmlLazyInitilaizedIocContainer

1.4.5自动装配协调者

Spring容器能自动装配协调者bean之间的关系。通过检查ApplicationContext的内容,Spring自动为你的bean解析协同者(其他依赖bean)。自动装配有下面的优势:

当使用基于XML元数据配置,我们可以为这个bean指定自动装配模式通过<bean/>元素的autowire属性。自动装配有4种模式。你可以对每个bean指定自动装配因此可以选择自动装配哪些bean。下面的表格描述了4种装配模式:

Mode Explanation
no 默认不自动装配. bean引用必须通过ref定义. 对于较大的部署,建议不要更改默认设置,因为明确指定协同者可以提供更好的控制和清晰度。 在某种程度上,它记录了系统的结构。
byName 通过属性名自动装配。Spring查找一个bean与自动装配属性名相同的bean名字。例如:如果bean定义是被设置为通过名字自动注入并且包含了一个master属性(也就是,有一个setMaster(..)方法),Spring查找一个master名字的bean并且使用它设置到属性上。
byType 如果在容器中和属性具有相同类型的唯一bean存在会被自动注入到属性。如果有多个bean存在,一个致命的异常被抛出,表示你不能使用byType为bean自动装配。如果没有匹配的bean,不发生任何事情(属性不被设置)。
constructor 类似于byType,但是使用构造函数参数。如果在容器中没有一个bean被匹配到会抛出一个致命的error

使用byType或构造函数自动装配模式,你可以结合数组和类型化的集合,在这种情况下,提供容器中与期望类型匹配的所有自动装配候选,以满足相关性。如果期望key的 类型是String,你可以自动装配强类型的Map实例。一个自动装配Map实例的值由所有匹配期望类型实例组成,并且这个Map实例的这些keybean名称对应。

1.4.6 方法注入

在大多数应用场景中,在容器中大多数bean是单例的。当单例Bean需要与另一个单例Bean协作或非单例Bean需要与另一个非单例Bean协作时,典型的处理依赖通过定义一个bean作为其他bean的属性。当bean的生命周期不同时会出现问题。假设单例bean A需要使用非单例bean B(原型),假设A的每个方法被调用。这个容器仅仅创建单例bean A一次并且只有一次机会去设置属性。容器无法每次为bean A提供一个新的bean B实例(单例A每次从容器获取bean B不能每次提供一个新bean)。

一个解决方案时放弃控制反转。我们也可以通过实现ApplicationContextAware接口让bean A意识到容器。并在bean A每次需要bean B时,通过使用getBean("B")获取一个新实例bean。下面例子展示使用方式:

package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // 通过从容器获取bean
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
      //容器注入ApplicationContext
        this.applicationContext = applicationContext;
    }
}

上面的例子不是可取的,因为这个业务代码需要容器回调耦合了Spring 框架(备注:这个我不敢苟同,上面我也发表了观点)。方法注入,Spring IoC容器高级特性,让我们处理这种场景更简洁。(备注:上面Command配置为原型才能达到效果)

可以阅读更多关于方法注入的动机在 博客入口

作者

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。

博客地址:http://youngitman.tech

CSDN:https://blog.csdn.net/liyong1028826685

微信公众号:


qrcode_for_gh_2a18e179fcca_430-2.jpg

技术交流群:


草料活码430*430.png
上一篇下一篇

猜你喜欢

热点阅读