更新时间:2019年07月26日 10时53分18秒 来源:黑马程序员论坛
1.4 依赖 典型的企业应用程序不会只包含单个对象(或 Spring 术语中的 bean)。即使是最简单的应用程序也是由很多对象进行协同工作,以呈现出最终用户所看到的有条理的应用程序。下一节将介绍如何从定义多个独立的 bean 到实现对象之间相互协作从而实现可达成具体目标的应用程序。 1.4.1 依赖注入依赖注入(DI)是一钟对象处理方式,通过这个过程,对象只能通过构造函数参数、工厂方法参数或对象实例化后设置的属性来定义它们的依赖关系(即它们使用的其他对象)。然后容器在创建 bean 时注入这些依赖项。这个过程从本质上逆转了 bean 靠自己本身通过直接使用类的构造函数或服务定位模式来控制实例化或定位其依赖的情况,因此称之为控制反转。 使用 DI 原则的代码更清晰,当对象和其依赖项一起提供时,解耦更有效。对象不查找其依赖项,也不知道依赖项的位置或类。因此,尤其是依赖允许在单元测试中使用模拟实现的接口或抽象基类时,类会变得更容易测试。 DI 存在两个主要变体:基于构造函数的依赖注入和基于 Setter 的依赖注入。 基于构造函数的依赖注入基于构造函数的 DI 由容器调用具有多个参数的构造函数来完成,每个参数表示一个依赖项。和调用具有特定参数的静态工厂方法来构造 bean 几乎一样,本次讨论用相同的方式处理构造函数和静态工厂方法的参数。以下示例显示了一个只能通过构造函数注入进行依赖注入的类: public class SimpleMovieLister { // the SimpleMovieLister has a dependency on a MovieFinder private MovieFinder movieFinder; // a constructor so that the Spring container can inject a MovieFinder public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually uses the injected MovieFinder is omitted...}请注意,这个类没有什么特别之处。它是一个不依赖于特定容器接口、基类或注释的POJO。 构造函数参数解析通过使用的参数类型进行构造函数参数解析匹配。如果 bean 定义的构造函数参数中不存在潜在的歧义,那么在 bean 定义中定义构造函数参数的顺序就是在实例化 bean 时将这些参数提供给适当的构造函数的顺序。参考以下类: package x.y;public class ThingOne { public ThingOne(ThingTwo thingTwo, ThingThree thingThree) { // ... }}假设 ThingTwo 类和 ThingThree 类没有继承关系,则不存在潜在的歧义。那么,以下配置可以正常工作,你也不需要在 <constructor-arg/> 元素中显式指定构造函数参数索引或类型。 <beans> <bean id="beanOne" class="x.y.ThingOne"> <constructor-arg ref="beanTwo"/> <constructor-arg ref="beanThree"/> </bean> <bean id="beanTwo" class="x.y.ThingTwo"/> <bean id="beanThree" class="x.y.ThingThree"/></beans>当引用另一个 bean 时,类型是已知的,并且可以进行匹配(与前面的示例一样)。当使用简单类型时,例如 <value>true</value>,Spring 无法确定值的类型,因此无法在没有帮助的情况下按类型进行匹配。参考以下类: package examples;public class ExampleBean { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; }}构造函数参数类型匹配 在前面的场景中,如果使用 type 属性显式指定构造函数参数的类型,则容器可以使用指定类型与简单类型进行匹配。如下例所示: <bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/></bean>构造函数参数索引 您可以使用 index 属性显式指定构造函数参数的索引,如以下示例所示: <bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/></bean>除了解决多个简单值的歧义之外,指定索引还可以解决构造函数具有相同类型的两个参数的歧义。
构造函数参数名称 也可以使用构造函数参数名称消除歧义,如以下示例所示: <bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/></bean>请记住,为了使这项工作开箱即用,必须在启用调试标志的情况下编译代码,以便 Spring 可以从构造函数中查找参数名称。如果您不能或不想使用 debug 标志编译代码,则可以使用 JDK 批注 @ConstructorProperties 显式命名构造函数参数。然后,示例类必须如下所示: package examples;public class ExampleBean { // Fields omitted @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; }}基于 Setter 的依赖注入基于 setter 的 DI 由容器在调用无参数构造函数或无参数静态工厂方法来实例化 bean 之后调用 setter 方法完成。 以下示例显示了一个只能通过使用纯 setter 注入进行依赖注入的类。这个类是传统的 Java 类。它是一个POJO,它不依赖于特定容器接口、基类或注释。 public class SimpleMovieLister { // the SimpleMovieLister has a dependency on the MovieFinder private MovieFinder movieFinder; // a setter method so that the Spring container can inject a MovieFinder public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually uses the injected MovieFinder is omitted...}ApplicationContext 支持它管理的 bean 使用基于构造函数和基于 setter 的 DI。它还支持在通过构造函数方法注入了一些依赖项之后使用基于 setter 的 DI。你可以以 BeanDefinition 的形式配置依赖项,可以将其与 PropertyEditor 实例结合使用将属性从一种格式转换为另一种格式。然而,大多数 Spring 用户不直接使用这些类(即编码),而是用 XML bean 定义、注解组件(也就是带 @Component, @Controller等注解的类)或基于 Java 的 @Configuration 类中的 @Bean 方法。然后,这些源在内部转换为 BeanDefinition 实例并用于加载整个 Spring IoC 容器实例。 依赖处理过程 容器执行 bean 依赖性解析过程如下:
Spring 容器在创建时验证每个 bean 的配置。但是在实际创建 bean 之前不会设置其属性。作用域为单例且被设置为预先实例化(默认值)的 Bean 会在创建容器时创建。作用域在 Bean 作用域中定义。否则 bean 仅在需要时才会创建。创建 bean 可能会导致很多 bean 被创建,因为 bean 的依赖项及其依赖项的依赖项(依此类推)被创建和分配。请注意,这些依赖项之间不匹配的问题可能会较晚才能被发现 - 也就是说,受影响的 bean 首次创建时。
你通常可以相信 Spring 会做正确的事。它会在容器加载时检测配置问题,例如引用不存在的 bean 和循环依赖关系。当实际创建 bean 时,Spring 会尽可能晚地设置属性并解析依赖关系。这意味着容器正常加载后,如果在创建对象或其中一个依赖项时出现问题,Spring 容器会捕获一个异常 - 例如,bean 因属性缺失或无效而抛出异常。可能会稍后发现一些配置问题,所以 ApplicationContext默认情况下实现预实例化单例 bean。在实际需要之前创建这些 bean 是以前期时间和内存为代价的,ApplicationContext 会在创建时发现配置问题,而不是更晚。你仍然可以覆盖此默认行为,以便单例 bean 可以延迟初始化,而不是预先实例化。 如果不存在循环依赖关系,当一个或多个协作 bean 被注入到依赖 bean 时,每个协作 bean 在注入到依赖 bean 之前会被完全配置。这意味着,如果 bean A 依赖于 bean B,那么 Spring IoC 容器在调用 bean A 上的 setter 方法之前会完全配置 bean B。换句话说,bean 已经被实例化(如果它不是预先实例化的单例),依赖项已经被设置,并调用了相关的生命周期方法(如配置初始化方法 或 InitializingBean 回调方法)。 依赖注入的示例以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。Spring XML 配置文件的一小部分指定了一些 bean 定义,如下所示: <bean id="exampleBean" class="examples.ExampleBean"> <!-- setter injection using the nested ref element --> <property name="beanOne"> <ref bean="anotherExampleBean"/> </property> <!-- setter injection using the neater ref attribute --> <property name="beanTwo" ref="yetAnotherBean"/> <property name="integerProperty" value="1"/></bean><bean id="anotherExampleBean" class="examples.AnotherBean"/><bean id="yetAnotherBean" class="examples.YetAnotherBean"/>以下示例展示了相应的 ExampleBean 类: public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; }}在前面的示例中,声明 setter 与 XML 文件中指定的属性进行匹配。以下示例使用基于构造函数的DI: <bean id="exampleBean" class="examples.ExampleBean"> <!-- constructor injection using the nested ref element --> <constructor-arg> <ref bean="anotherExampleBean"/> </constructor-arg> <!-- constructor injection using the neater ref attribute --> <constructor-arg ref="yetAnotherBean"/> <constructor-arg type="int" value="1"/></bean><bean id="anotherExampleBean" class="examples.AnotherBean"/><bean id="yetAnotherBean" class="examples.YetAnotherBean"/>以下示例展示了相应的 ExampleBean 类: public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public ExampleBean( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { this.beanOne = anotherBean; this.beanTwo = yetAnotherBean; this.i = i; }}bean 定义中指定的构造函数参数将作为 ExampleBean 的构造函数的参数 。 现在思考这个例子的变体,不使用构造函数,而是告诉 Spring 调用静态工厂方法来返回对象的实例: <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(...) { ... } // a static factory method; the arguments to this method can be // considered the dependencies of the bean that is returned, // regardless of how those arguments are actually used. public static ExampleBean createInstance ( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { ExampleBean eb = new ExampleBean (...); // some other operations... return eb; }}静态工厂方法的参数由 <constructor-arg/> 元素提供,与实际使用的构造函数完全相同。工厂方法返回的类的类型不必与包含静态工厂方法的类相同(尽管在本例中是)。实例(非静态)工厂方法可以以基本相同的方式使用(除了使用 factory-bean 属性而不是 class 属性 |
推荐了解热门学科
java培训 | Python人工智能 | Web前端培训 | PHP培训 |
区块链培训 | 影视制作培训 | C++培训 | 产品经理培训 |
UI设计培训 | 新媒体培训 | 产品经理培训 | Linux运维 |
大数据培训 | 智能机器人软件开发 |
传智播客是一家致力于培养高素质软件开发人才的科技公司,“黑马程序员”是传智播客旗下高端IT教育品牌。自“黑马程序员”成立以来,教学研发团队一直致力于打造精品课程资源,不断在产、学、研3个层面创新自己的执教理念与教学方针,并集中“黑马程序员”的优势力量,针对性地出版了计算机系列教材50多册,制作教学视频数+套,发表各类技术文章数百篇。
传智播客从未停止思考
传智播客副总裁毕向东在2019IT培训行业变革大会提到,“传智播客意识到企业的用人需求已经从初级程序员升级到中高级程序员,具备多领域、多行业项目经验的人才成为企业用人的首选。”
中级程序员和初级程序员的差别在哪里?
项目经验。毕向东表示,“中级程序员和初级程序员最大的差别在于中级程序员比初级程序员多了三四年的工作经验,从而多出了更多的项目经验。“为此,传智播客研究院引进曾在知名IT企业如阿里、IBM就职的高级技术专家,集中研发面向中高级程序员的课程,用以满足企业用人需求,尽快补全IT行业所需的人才缺口。
何为中高级程序员课程?
传智播客进行了定义。中高级程序员课程,是在当前主流的初级程序员课程的基础上,增加多领域多行业的含金量项目,从技术的广度和深度上进行拓展。“我们希望用5年的时间,打造上百个高含金量的项目,覆盖主流的32个行业。”传智播客课程研发总监于洋表示。
黑马程序员热门视频教程【点击播放】
Python入门教程完整版(懂中文就能学会) | 零起点打开Java世界的大门 |
C++| 匠心之作 从0到1入门学编程 | PHP|零基础入门开发者编程核心技术 |
Web前端入门教程_Web前端html+css+JavaScript | 软件测试入门到精通 |