Spring核心技术(四)——Spring的依赖及其注入(续二)
前面两篇文章描述了IoC容器中依赖的概念,包括依赖注入以及注入细节配置。本文将继续描述玩全部的依赖信息。
使用 depends-on
如果一个Bean是另一个Bean的依赖的话,通常来说这个Bean也就是另一个Bean的属性之一。多数情况下,开发者可以在配置XML元数据的时候使用<ref/>标签。然而,有时Bean之间的依赖关系不是直接关联的。比如:需要调用类的静态实例化器来触发,类似数据库驱动注册。depends-on属性会使明确的强迫依赖的Bean在引用之前就会初始化。下面的例子使用depends-on属性来让表示单例Bean上的依赖的。
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
如果想要依赖多个Bean,可以提供多个名字作为depends-on的值,以逗号,空格,或者分号分割,如下:
<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情况)。独立的定义了depends-on属性的Bean会优先销毁,优于被depends-on的Bean来销毁,这样depends-on可以控制销毁的顺序。
延迟初始化的Bean
默认情况下,ApplicationContext会在实例化的过程中创建和配置所有的单例Bean。总的来说,这个预初始化是很不错的。因为这样能及时发现环境上的一些配置错误,而不是系统运行了很久之后才发现。如果这个行为不是迫切需要的,开发者可以通过将Bean标记为延迟加载就能阻止这个预初始化。延迟初始化的Bean会通知IoC不要让Bean预初始化而是在被引用的时候才会实例化。
在XML中,可以通过<bean/>元素的lazy-init属性来控制这个行为。如下:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
当将Bean配置为上面的XML的时候,ApplicationContext之中的lazyBean是不会随着ApplicationContext的启动而进入到预初始化状态的,而那些非延迟加载的Bean是处于预初始化的状态的。
然而,如果一个延迟加载的类是作为一个单例非延迟加载的Bean的依赖而存在的话,ApplicationContext仍然会在ApplicationContext启动的时候加载,因为作为单例Bean的依赖,会随着单例Bean的实例化而实例化。
开发者可以通过使用<beans/>的default-lazy-init属性来在容器层次控制Bean是否延迟初始化,比如:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
自动装配关联
Spring容器可以根据Bean之间的依赖关系自动装配。开发者可以令Spring通过ApplicationContext来来自动解析这些关联。自动的装载有很多的优点:
- 自动装载能够明显的减少指定的属性或者是构造参数。
- 自动装载可以扩展开发者的对象。比如说,如果开发者需要加一个依赖,依赖就能够不需要开发者特别关心更改配置就能够自动满足。这样,自动装载在开发过程中是极度高效的,不用明确的选择装载的依赖会使系统更加的稳定。
当使用基于XML的元数据配置的时候,开发者可以指定自动装配的方式。通过配置<bean/>元素的autowire属性就可以了。自动装载有如下四种方式,开发者可以指定每个Bean的装载方式,这样Bean就知道如何加载自己的依赖。
| 模式 | 解释 |
|---|---|
| no | (默认)不装载。Bean的引用必须通过ref元素来指定。对于比较大项目的部署,不建议修改默认的配置,因为特指会加剧控制。在某种程度上来说,默认的形式也说明了系统的结构。 |
| byName | 通过名字来装配。Spring会查找所有的Bean直到名字和属性相同的一个Bean来进行装载。比如说,如果Bean配置为根据名字来自动装配,它包含了一个属性名字为master(也就是包含一个setMaster(..)方法),Spring就会查找名字为master的Bean,然后用之装载 |
| byType | 如果需要自动装配的属性的类型在容器之中存在的话,就会自动装配。如果容器之中存在不止一个类型匹配的话,就会抛出一个重大的异常,说明开发者最好不要使用byType来自动装配那个Bean。如果没有匹配的Bean存在的话,不会抛出异常,只是属性不会配置。 |
| 构造函数 | 类似于byType的注入,但是应用的构造函数的参数。如果没有一个Bean的类型和构造函数参数的类型一致,那么仍然会抛出一个重大的异常 |
通过 byType 或者 构造函数 的自动装配方式,开发者可以装在数组和强类型集合。在如此的例子之中,所有容器之中的匹配指定类型的Bean会自动装配到Bean上来完成依赖注入。开发者可以自动装配key为String的强类型的Map。自动装配的Map值会包含所有的Bean实例值来匹配指定的类型,Map的key会包含关联的Bean的名字。
自动装配的限制和劣势
自动装载如果在整个的项目的开发过程中使用,会工作的很好。但是如果不是全局使用,而只是用之来自动装配几个Bean的话,会很容易迷惑开发者。
下面是一些自动装配的劣势和限制
- 精确的
property以及constructor-arg参数配置,会覆盖掉自动装配的配置。开发不能够自动装配所谓的简单属性,比如Primitive类型或者字符串。 - 自动装配并有精确装配准确。尽管如上面的表所描述,Spring会尽量小心来避免不必要的错误装配,但是Spring管理的对象关系仍然不如文档描述的那么精确。
- 装配的信息对开发者可见性不好,因为这一切都由Spring容器管理。
- 容器中的可能会存在很多的Bean匹配Setter方法或者构造参数。比如说数组,集合或者Map等。然而依赖却希望仅仅一个匹配的值,含糊的信息是无法解析的。如果没有独一无二的Bean,那么就会抛出异常。
在后面的场景,开发者有如下的选择
- 放弃自动装配有利于精确装配
- 可以通过配置
autowire-candidate属性为false来阻止自动装配 - 通过配置
<bean/>元素的primary属性为true来指定一个bean为主要的候选Bean - 实现更多的基于注解的细粒度的装配配置。
排除一个Bean,使之不自动装配
在每个Bean的基础之上,开发者可以阻止Bean来自动装配。在基于XML的配置中,可以配置<bean/>元素的autowire-candidate属性为false来做到这一点。容器在读取到这个配置后,会让这个Bean对于自动装配的结构中不可见(包括注解形式的配置比如@Autowired)
开发者可以通过模式匹配而不是Bean的名字来限制自动装配的候选者。最上层的<beans/>元素会在default-autowire-candidates属性中来配置多种模式。比如,限制自动装配候选者的名字以Repository结尾,可以配置*Repository。如果需要配置多种模式,只需要用逗号分隔开即可。当然Bean中如果配置了autowire-candidate的话,这个信息拥有更高的优先级。
上面的这些技术在配置那些不需要自动装配的Bean是很有效的。当然这并不是说这类Bean本身无法自动装配其他的Bean,而是说这些Bean不在作为自动装配依赖的候选了。
方法注入
在大多数的应用场景下,大多数的Bean都是单例的。当这个单例的Bean需要和另一个单例的或者非单例的Bean联合使用的时候,开发者只需要配置依赖的Bean为这个Bean的属性即可。但是有时会因为不同的Bean生命周期的不同而产生问题。假设单例的Bean A在每个方法调用中使用了非单例的Bean B。容器只会创建Bean A一次,而只有一个机会来配置属性。那么容器就无法给Bean A每次都提供一个新的Bean B的实例。
一个解决方案就是放弃一些IoC。开发者可以通过实现ApplicationContextAware接口令Bean A可以看到ApplicationContext,从而通过调用getBean("B")来在Bean A 需要新的实例的时候来获取到新的B实例。参考下面的例子:
// a class that uses a stateful Command-style class to perform some processing
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() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
上面的代码并不是让人十分满意,因为业务的代码已经与Spring框架耦合在了一起。Spring提供了一个稍微高级的点特性方法注入的方式,可以用来处理这种问题。
查找方法注入
查找方法注入就是容器一种覆盖容器管理Bean的方法,来返回查找的另一个容器中的Bean的能力。查找方法通常就包含前面场景提到的Bean。Spring框架通过使用CGLIB库生成的字节码来动态生成子类来覆盖父类的方法实现方法注入。
- 为了让这个动态的子类方案正常,那么Spring容器所继承的父类不能是
final的,而覆盖的方法也不能是final的。- 针对这个类的单元测试因为存在抽象方法,所以必须实现子类来测试
- 组件扫描的所需的具体方法也需要具体类。
- 一个关键的限制在于查找方法与工厂方法是不能协同工作的,尤其是不能和配置类之中的
@Bean的方法,因为容器不在负责创建实例,而是创建一个运行时的子类。- 最后,被注入的到方法的对象不能被序列化。
看到前面的代码片段中的CommandManager类,我们发现发现Spring容器会动态的覆盖createCommand()方法。CommandManager类不在拥有任何的Spring依赖,如下:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在包含需要注入的方法的客户端类当中,注入的方法需要有如下的函数签名
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法为抽象,那么动态生成的子类会实现这个方法。否则,动态生成的子类会覆盖类中的定义的原方法。例如:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>
上面的commandManager在当它需要一个command Bean实例的时候就会调用自己的方法createCommand()。开发者一定要谨慎配置command Bean的为prototype类型的Bean。如果所需的Bean为单例的,那么这个方法注入返回的将都是同一个实例。
任意的方法替换
从前面的描述中,我们知道查找方法是有能力来覆盖任何由容器管理的Bean的方法的。开发者最好跳过这一部分,除非一定需要使用这个功能。
通过配置基于XML的配置元数据,开发者可以使用replaced-method元素来替换一个存在的方法的实现。考虑如下情况:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
一个实现了org.springframework.beans.factory.support.MethodReplacer接口的类会提供一个新方法的定义。
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
如果需要覆盖Bean的方法需要配置XML如下:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
开发者可以使用更多的<replaced-method>中的<arg=type/>元素来指定需要覆盖的方法。当需要覆盖的方法存在重载方法时,指定参数才是必须的。为了方便起见,字符串的类型是会匹配如下类型,完全等同于java.lang.String。
java.lang.String
String
Str
因为通常来说参数的个数已经足够区别不同的方法了,这种快捷的写法可以省去很多的代码。
至此,已经完全描述了Spring核心技术中的依赖注入的基本信息,下一篇博文将会介绍Bean的scope。
Spring核心技术(四)——Spring的依赖及其注入(续二)的更多相关文章
- Spring框架学习之高级依赖关系配置(二)
紧接着上篇内容,本篇文章将主要介绍XML Schema的简化配置和使用SpEL表达式语言来优化我们的配置文件. 一.基于XML Schema的简化配置方式 从Spring2.0以来,Spring支持使 ...
- spring入门(四) spring mvc返回json结果
前提:已搭建好环境 1.建立Controller package com.ice.controller; import com.ice.model.Person; import org.springf ...
- Spring重温(四)--Spring自动组件扫描
通常情况下,声明所有的Bean类或组件的XML bean配置文件,这样Spring容器可以检测并注册Bean类或组件. 其实,Spring是能够自动扫描,检测和预定义的项目包并实例化bean,不再有繁 ...
- Spring学习(四)-----Spring Bean引用同xml和不同xml bean的例子
在Spring,bean可以“访问”对方通过bean配置文件指定相同或不同的引用. 1. Bean在不同的XML文件 如果是在不同XML文件中的bean,可以用一个“ref”标签,“bean”属性引用 ...
- 从零开始学习Node.js例子四 多页面实现数学运算 续二(client端和server端)
1.server端 支持数学运算的服务器,服务器的返回结果用json对象表示. math-server.js //通过监听3000端口使其作为Math Wizard的后台程序 var math = r ...
- Spring.Net控制翻转、依赖注入、面向切面编程
Spring.Net快速入门:控制翻转.依赖注入.面向切面编程 Spring.Net主要功能: 1.IoC:控制翻转(Inversion of Control) 理解成抽象工厂翻转控制:就是创建对象 ...
- 【SSH系列】深入浅出spring IOC中三种依赖注入方式
spring的核心思想是IOC和AOP,IOC-控制反转,是一个重要的面向对象编程的法则来消减计算机程序的耦合问题,控制反转一般分为两种类型,依赖注入和依赖查找,依赖什么?为什么需要依赖?注入什么?控 ...
- Spring IOC(三)依赖注入
本系列目录: Spring IOC(一)概览 Spring IOC(二)容器初始化 Spring IOC(三)依赖注入 Spring IOC(四)总结 目录 1.AbstractBeanFactory ...
- spring IOC中三种依赖注入方式
Spring的核心思想是IOC和AOP,IOC-控制反转,是一个重要的面向对象编程的法则,用来消减计算机程序之间的耦合问题,控制反转一般分为两种类型,依赖注入和依赖查找,依赖什么?为什么需要依赖?注入 ...
随机推荐
- nginx开发(二)配置mp4文件在线播放
1: 第一步先开打nginx的文件夹遍历功能 vi /usr/local/nginx/conf/nginx.conf #编辑配置文件,在http {下面添加以下内容: autoindex on; #开 ...
- Windows 使用之那些你还不知道操作
作者:你未读 整理:君未读 关于使用 win 系统的基本普及. 建议更换 win10 系统 可能很多朋友看到这个建议,心里很不爽,还很不服气,别急,且看官方给出的信息. 也就是说在 2019 年你完全 ...
- 【原创】Maven安装和配置
ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 前提 利用maven进行java项目或J2EE项目开发,要求电脑已配置java开发环境(JDK) 下载 下载地址:http://maven.a ...
- linux 定义变量 ,添加变量值
1.设置值$name=test2. 输出变量的值 echo $echo $name3. 增加变量内容PATH=$PATH:/home/bin/testPATH="$PATH":/h ...
- Linux C编程 GCC的使用
本篇文章面向一些会linux文件操作与vim基本命令,编程大佬请移步勿喷. gcc是默认安装的,但是其还缺少常用的头文件和库文件,所以还需要build-essential这个包,可以在联网状态下使用如 ...
- Oracle随机选择一条记录SQL
Oracle随机选择一条记录SQL:
- WCF wsdlexception(at/html):faultCode=INVALID_WSDL
WCF 部署正常,通过浏览器查看服务也OK,但是通过SOAP UI创建客户端请求时就异常: wsdlexception(at/html):faultCode=INVALID_WSDL: Expecte ...
- 17 C#中的循环执行 while循环
在编程中有代码的执行主要有三种方式.(1)顺序执行,也就是一条语句一条语句按顺序执行:(2)条件执行,也就是if...else.当某种条件满足时执行一些代码:(3)循环执行,就是当某种条件满足的时候, ...
- android调用webservice接口获取信息
我的有一篇博客上讲了如何基于CXF搭建webservice,service层的接口会被部署到tomcat上,这一篇我就讲一下如何在安卓中调用这些接口传递参数. 1.在lib中放入ksoap2的jar包 ...
- OpenTSDB监控
OpenTSDB监控