文章目录

  • 依赖注入
  • 依赖注入的配置详解
  • depends-on
  • lazy-init
  • 自动装载
  • 方法注入

依赖注入

依赖注入就是在Spring创建Bean的时候,去实例化该Bean构造函数所需的参数,或者通过Setter方法去设置该Bean的属性。

Spring的依赖注入有两种基于构造函数的依赖注入和基于setter的依赖注入。

基于构造函数的依赖注入

构造函数的注入是通过构造函数的参数来实现的。如下所示:

public class ExampleBean {// Number of years to calculate the Ultimate Answerprivate int years;// The Answer to Life, the Universe, and Everythingprivate String ultimateAnswer;public ExampleBean(int years, String ultimateAnswer) {this.years = years;this.ultimateAnswer = ultimateAnswer;}
}

该Bean有一个两个参数的构造函数,那么怎么注入这些参数呢?
有三种方法。

方法1:按构造函数的类型匹配:

    <bean id="exampleBeanA" class="com.flydean.beans.ExampleBean"><constructor-arg type="int" value="7500000"/><constructor-arg type="java.lang.String" value="42"/></bean>

这里通过指定参数的类型,即可以指定哪个参数是years,哪个参数是ultimateAnswer。

方法2:构造函数索引:

    <bean id="exampleBeanB" class="com.flydean.beans.ExampleBean"><constructor-arg index="0" value="7500000"/><constructor-arg index="1" value="42"/></bean>

Spring中可以通过构造函数的索引来指定特定的参数。要注意Spring的索引是从0开始的。

方法3:构造函数名字匹配:

    <bean id="exampleBeanC" class="com.flydean.beans.ExampleBeanWithConstructorProperties"><constructor-arg name="years" value="7500000"/><constructor-arg name="ultimateAnswer" value="42"/></bean>

如果通过构造函数的名字来匹配,需要注意必须在编译的时候开启调试标志,要不然Spring不能在构造函数中找到参数名。

如果不想启用调试标志,则必须使用@ConstructorProperties JDK注解显式命名构造函数参数。

public class ExampleBeanWithConstructorProperties {// Number of years to calculate the Ultimate Answerprivate int years;// The Answer to Life, the Universe, and Everythingprivate String ultimateAnswer;@ConstructorProperties({"years", "ultimateAnswer"})public ExampleBeanWithConstructorProperties(int years, String ultimateAnswer) {this.years = years;this.ultimateAnswer = ultimateAnswer;}
}

基于Setter的注入

Setter注入主要用来无参构造器或者获得对象实例之后才设置对象的属性。下面是Setter的例子:

public class SimpleMovieLister {// the SimpleMovieLister has a dependency on the MovieFinderprivate MovieFinder movieFinder;// a setter method so that the Spring container can inject a MovieFinderpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// business logic that actually uses the injected MovieFinder is omitted...
}

对于的XML文件如下:

    <!--Setter DI --><bean id="movieFinder" class="com.flydean.beans.MovieFinder"/><bean id="simpleMovieLister" class="com.flydean.beans.SimpleMovieLister"><property name="movieFinder" ref="movieFinder"/></bean>

除了XML配置,也可以使用注解:@Component、@Controller。或者使用@Configuration注解中的@Bean方法。

如何选择?

既然有这样两种注入方式,我们怎么选择呢?

通常来说,对于必须的属性,我们通过构造函数来注入。对于可选属性,我们通过Setter注入。当然你也可以在Setter方法中使用@Required注解。

当然如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。

循环依赖

循环依赖主要出现在构造函数注入的情况。

类A通过构造函数注入需要类B的实例,类B通过构造函数注入需要类A的实例。如果为要相互注入的类A和类B配置bean,那么SpringIOC容器在运行时检测到这个循环引用,会抛出BeanCurrentlyInCreationException。

解决方式就是使用Setter注入。

依赖注入的配置详解

基本类型,字符串或者其他

如果< property/>元素的value属性是基本类型,Spring会将其转换为类需要的类型,配置如下:

    <!--Setter DI --><bean id="movieFinder" class="com.flydean.beans.MovieFinder"><property name="name" value="movie"/><property name="number" value="123456"/></bean>

这是一个常见的Setter注入。为了简洁,也可以使用p-namespace,如下:

<?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:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="movieFinder" class="com.flydean.beans.MovieFinder"p:name="movie"p:number="123456"/>
</beans>

Spring容器使用JavaBeans属性编辑器机制将< value/>元素中的文本转换为java.util.properties实例。这是一个很好的快捷方式,如下所示:

    <bean id="mappings"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><!-- typed as a java.util.Properties --><property name="properties"><value>jdbc.driver.className=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/mydb</value></property></bean>

注意上面例子中的value里面的值。

ref

通过< ref/>标记的bean属性允许在同一容器或父容器中创建对任何bean的引用,而不管它是否在同一XML文件中。bean属性的值可以与目标bean的id属性相同,也可以与目标bean的name属性中的一个值相同。以下示例显示如何使用ref元素:

<ref bean="someBean"/>

内部bean

在< property/> 或者 < constructor-arg/>元素内部的< bean/>元素可以定义一个内部bean,下面是个例子:

    <bean id="simpleMovieLister" class="com.flydean.beans.SimpleMovieLister"><property name="movieFinder"><bean class="com.flydean.beans.MovieFinder"><property name="name" value="movie"/><property name="number" value="123456"/></bean></property></bean>

内部bean定义不需要ID或名称。如果指定,容器也不会使用这个值作为标识符。容器在创建时也忽略作用域标志,因为内部bean总是匿名的,并且总是用外部bean创建的。不可能单独访问内部bean,也不可能将它们注入到除封闭bean之外的协作bean中。

集合

< list/>, < set/>, < map/>,和 < props/> 分别被用来设置Java Collection类型List, Set, Map,和 Properties 类型的属性和参数。 下面是个例子:

    <bean id="movieFinderE" class="com.flydean.beans.MovieFinder"></bean><bean class="com.flydean.beans.CollectionBean"><property name="properties"><props><prop key="administrator">administrator@example.org</prop><prop key="support">support@example.org</prop><prop key="development">development@example.org</prop></props></property><property name="arrayList"><list><value>"a list element followed by a reference"</value></list></property><property name="hashMap"><map><entry key="an entry" value="just some string"/><entry key ="a ref" value-ref="movieFinderE"/></map></property><property name="hashSet"><set><value>just some string</value><ref bean="movieFinderE" /></set></property></bean>

强类型集合

通过在Java 5中引入泛型类型,可以使用强类型集合。也就是说,可以声明集合类型,使其只能包含(例如)字符串元素。如果使用Spring将强类型集合注入bean,则可以利用Spring的类型转换支持,以便在将强类型集合实例的元素添加到集合之前将其转换为适当的类型。下面的Java类和bean定义的例子:

public class SomeClass {private Map<String, Float> accounts;public void setAccounts(Map<String, Float> accounts) {this.accounts = accounts;}
}
        <bean id="something" class="com.flydean.beans.SomeClass"><property name="accounts"><map><entry key="one" value="9.99"/><entry key="two" value="2.75"/><entry key="six" value="3.99"/></map></property></bean>

Null和Empty字符串值

Null和空字符串是不一样的,如下:

<bean class="ExampleBean"><property name="email" value=""/>
</bean>

上面的例子相当于:

exampleBean.setEmail("");

下面是怎么设置null:

<bean class="ExampleBean"><property name="email"><null/></property>
</bean>

上面的例子相当于:

exampleBean.setEmail(null);

c-namespace

上面讲到了p-namespace专门是设置bean的属性用的,同样的c-namespace是用来设置构造函数参数的,如下所示:

    <bean id="exampleBeanA" class="com.flydean.beans.ExampleBean"><constructor-arg type="int" value="7500000"/><constructor-arg type="java.lang.String" value="42"/></bean><bean id="exampleBeanB" class="com.flydean.beans.ExampleBean"c:years ="10" c:ultimateAnswer="answer"/>

depends-on

通常一个bean依赖另一个bean,我们会在XML中使用< ref/>来引用,但是这种依赖关系并不直接。我们可以使用depends-on属性来显式强制一个或多个bean在使用此元素的bean初始化之前进行初始化,如下所示:

    <bean id="beanA" class="com.flydean.beans.SomeClass" depends-on="beanB"></bean><bean id="beanB" class="com.flydean.beans.MovieFinder"></bean>

lazy-init

正常来说ApplicationContext中配置成单例模式的bean都会随Spring启动而初始化,如果有特殊的需要,想延长初始化该bean,则可以使用 lazy-init 。一个lazy-initialized bean告诉IOC容器在第一次请求bean实例时创建它,而不是在启动时。

<bean id="beanD" class="com.flydean.beans.MovieFinder" lazy-init="true"></bean>

但是,当一个惰性初始化bean是一个非惰性初始化的singleton bean的依赖项时,ApplicationContext会在启动时创建惰性初始化bean,因为它必须满足singleton的依赖项。

您还可以通过在< beans/>元素上使用默认的lazy init属性在容器级别控制lazy初始化,下面的示例显示:

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

自动装载

如果你想让Spring自动帮你注入bean的依赖bean时候,可以使用Spring的autowiring功能。autowiring 有4种模式:

模式 说明
no (默认)无自动装载。bean必须引用由ref定义的元素。对于较大的部署,不建议更改默认设置,因为显式指定合作者可以提供更大的控制度和清晰性。在某种程度上,它记录了系统的结构。
byName 按属性名称自动装载。Spring寻找与需要自动装载的属性同名的bean。例如,如果bean定义被设置为按名称自动装载,并且它包含一个master属性(即,它有一个setMaster(…)方法),那么spring将查找名为master的bean定义并使用它来设置该属性。
byType 如果容器中只有一个属性类型的bean,则允许自动装载属性。如果存在多个,则会引发一个致命的异常,这表示您不能为该bean使用byType自动装载。如果没有匹配的bean,则不会发生任何事情(未设置属性)。
constructor 类似于byType,但适用于构造函数参数。如果容器中不只有一个构造函数参数类型的bean,则会引发致命错误。

自动注入的限制和缺陷

虽然自动注入用起来很爽,但是它也有如下的缺陷:

  • property和constructor-arg的显示设置会覆盖自动注入,并且自动注入不能注入简单类型。

  • 自动注入不如显示配置精确。

  • 自动注入可能和容器中的很多bean相匹配。可能会出现问题。

从自动装载中排除Bean

使用autowire-candidate属性设置为false,可以防止bean被自动注入。该属性只会影响按类型注入的方式。如果按name注入,则不受影响。

下面是自动注入的例子:

    <bean id="simpleMovieLister" class="com.flydean.beans.SimpleMovieLister" autowire="byType"></bean><!--Setter DI --><bean id="movieFinder" class="com.flydean.beans.MovieFinder"><property name="name" value="movie"/><property name="number" value="123456"/></bean>

SimpleMovieLister如下:

package com.flydean.beans;import lombok.Data;@Data
public class SimpleMovieLister {// the SimpleMovieLister has a dependency on the MovieFinderprivate MovieFinder movieFinder;// a setter method so that the Spring container can inject a MovieFinderpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// business logic that actually uses the injected MovieFinder is omitted...
}

在上面的例子中,movieFinder属性将会被自动注入。

方法注入

在bean的生命周期不同的时候,如果一个bean要依赖于另外一个bean则可能出现问题。 比如一个单例模式的beanA 依赖于多例模式的beanB。 因为beanA是单例模式的,所以在创建beanA的时候就已经将其依赖的beanB创建了,不可能在每次beanA需要beanB的时候都创建一个新的beanB的实例。

解决方法有很多种,我们一一进行讲解。

方法1:实现ApplicationContextAware

如果实现了ApplicationContextAware,则可以通过getBean方法在每次需要beanB的时候,请求他的新的实例,如下:


public class CommandManager implements ApplicationContextAware {private ApplicationContext applicationContext;public Object process(Map commandState) {// grab a new instance of the appropriate CommandCommand command = createCommand();// set the state on the (hopefully brand new) Command instancecommand.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 IoC 容器的一个高级特性,它可以很好的处理这种情况。

查找方法注入

查找方法注入是指容器重写container-managed bean上的方法,并返回容器中另一个命名bean。查找通常涉及一个原型bean,如前一节中描述的场景中所示。Spring框架通过使用cglib库中的字节码动态生成覆盖该方法的子类来实现该方法注入。

因为使用了cglib,所以bean不能是final类,方法也不能是final类型。

查找方法不适用于工厂方法,尤其不适用于配置类中的@Bean方法,因为在这种情况下,容器不负责创建实例,因此无法动态创建运行时生成的子类。

下面是一个查找方法的例子:

public abstract class CommandManagerB {public Object process(Map commandState) {// grab a new instance of the appropriate Command interfaceAsyncCommand command = createCommand();return null;}// okay... but where is the implementation of this method?public abstract AsyncCommand createCommand();
}

这里我们定义了一个抽象类,要查找的方法就是createCommand。返回的对象类,如下:

public class AsyncCommand {
}

下面是XML配置文件:

    <!-- a stateful bean deployed as a prototype (non-singleton) --><bean id="myCommand" class="com.flydean.beans.AsyncCommand" scope="prototype"><!-- inject dependencies here as required --></bean><!-- commandProcessor uses statefulCommandHelper --><bean id="commandManager" class="com.flydean.beans.CommandManagerB"><lookup-method name="createCommand" bean="myCommand"/></bean>

CommandMangerB每次调用createCommand,都会返回一个新的AsyncCommand实例。

在基于注解的情况下,也可以这样使用:

public abstract class CommandManagerC {public Object process(Object commandState) {Command command = createCommand();return command.execute();}@Lookup("myCommand")protected abstract Command createCommand();
}

其中@Lookup(“myCommand”) 中的名字也可以省略,则按照默认的类型来解析。

任意方法替换

我们甚至可以使用replaced-method替换bean的方法实现。我们有个MyValueCalculator类,有一个我们想重写的方法computeValue。

public class MyValueCalculator {public String computeValue(String input) {// some real code...return "abc";}// some other methods...
}

一个实现了org.springframework.beans.factory.support.MethodReplacer接口的类提供了新的方法,如下所示:

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 resultString input = (String) args[0];return "def";}
}

bean的定义如下:

    <bean id="myValueCalculator" class="com.flydean.beans.MyValueCalculator"><!-- arbitrary method replacement --><replaced-method name="computeValue" replacer="replacementComputeValue"><arg-type>String</arg-type></replaced-method></bean><bean id="replacementComputeValue" class="com.flydean.beans.ReplacementComputeValue"/>

本章的例子可以参考bean-di中的bean-di部分。

更多精彩内容且看:

  • 区块链从入门到放弃系列教程-涵盖密码学,超级账本,以太坊,Libra,比特币等持续更新
  • Spring Boot 2.X系列教程:七天从无到有掌握Spring Boot-持续更新
  • Spring 5.X系列教程:满足你对Spring5的一切想象-持续更新
  • java程序员从小工到专家成神之路(2020版)-持续更新中,附详细文章教程

更多教程可以参考flydean的博客

Spring5参考指南:依赖注入相关推荐

  1. Spring5参考指南:IOC容器

    文章目录 为什么使用Spring5 什么是IOC容器 配置元数据 实例化容器 XML嵌套 groovy bean定义DSL 使用容器 最近在翻译Spring Framework Documentati ...

  2. Spring5参考指南:AspectJ高级编程之Configurable

    文章目录 遇到的问题 @Configurable 原理 重要配置 遇到的问题 前面的文章我们讲到了在Spring中使用Aspect.但是Aspect的都是Spring管理的Bean. 现在有一个问题, ...

  3. Spring5参考指南: Resources

    文章目录 内置Resource实现 ResourceLoader ResourceLoaderAware 资源作为依赖 构造ClassPathXmlApplicationContext-快捷方式 资源 ...

  4. Spring5参考指南:JSR 330标准注解

    文章目录 @Inject 和 @Named @Named 和 @ManagedBean 之前的文章我们有讲过,从Spring3.0之后,除了Spring自带的注解,我们也可以使用JSR330的标准注解 ...

  5. Spring5参考指南:组件扫描

    文章目录 组件扫描 @Component 元注解和组合注解 组件内部定义Bean元数据 为自动检测组件命名 为自动检测的组件提供作用域 生成候选组件的索引 组件扫描 上一篇文章我们讲到了annotat ...

  6. Spring5参考指南:基于注解的容器配置

    文章目录 @Required @Autowired @primary @Qualifier 泛型 @Resource @PostConstruct和@PreDestroy Spring的容器配置可以有 ...

  7. Spring5参考指南:Bean作用域

    文章目录 Bean作用域简介 Singleton作用域 Prototype作用域 Singleton Beans 中依赖 Prototype-bean web 作用域 Request scope Se ...

  8. Spring5参考指南:Bean的创建

    文章目录 Spring容器中的Bean Bean的命名 Bean的实例化 Spring容器中的Bean Bean在Spring中就是一个业务组件,我们通过创建各种Bean来完成最终的业务逻辑功能. 在 ...

  9. Spring5参考指南:AOP代理

    文章目录 AOP代理 AOP Proxies原理 AOP代理 通常来说Spring AOP有两种代理方式,一种默认的JDK代理,只能代理接口,一种是CGLIB代理,可以代理具体的类对象. Spring ...

最新文章

  1. Html.Action,Html.RenderAction,Html.ActionLink,Url.Action区别
  2. UML基本架构建模--获取类
  3. Form_Form与OAF页面互相调用(案例)
  4. java面试的基本问题_java面试常见基础问题之一
  5. logical_not torch
  6. 下列关于计算机图形的应用中 错误的是,计算机图形学题库及答案
  7. [APIO2013]机器人(DP+SPFA最短路)
  8. LeetCode 713. 乘积小于K的子数组(滑动窗口)
  9. 开源开放 | 开源立体化漏洞情报知识图谱(四维创智)
  10. c语言用栈编写数制转换程序,数制转换-栈的应用(C++实现)
  11. pt-heartbeat 监测RDS延迟
  12. linux 常用指令汇总
  13. python编程例子_Python的经典入门书籍有哪些?这5本值得一看
  14. mac系统csv转ansi_迅捷文字转语音 for mac
  15. java 服务器程序部署环境搭建
  16. Mac电脑怎样网络在线重装系统
  17. 调和级数相关极限合集
  18. 《When you are old》一如苇中的风,轻柔却难忘
  19. android通过webview调起支付宝app支付
  20. 项目管理专用中英文术语词汇

热门文章

  1. 在php中type有几种属性,HTML表单之input元素的23种type类型
  2. 使用pagination分页插件实现Ajax动态分页
  3. SlopOne推荐算法
  4. 内核隐藏进程(源码)
  5. UDT协议实现分析——UDT Socket的创建
  6. 第10讲:高效存储 MongoDB 的用法
  7. 从Golang调度器的作者视角探究其设计之道!
  8. Soul网关发布2.1.X之后,它到底有多方便?
  9. 基于时序数据库做监控,这里有超流行的开源方案
  10. 这里有一份面筋请查收(五)