Spring 依赖注入

1.依赖注入

依赖注入(DI)用来管理Bean之间的依赖关系,将几个独立的、不同的类组合在一起来完成工作。

当一个对象被创建时,IOC 容器会注入它的依赖,这个过程与传统创建对象的方式是相反的故称为控制反转(IOC)。
传统创建对象的方式:

public TextEditor() 
{
	spellChecker = new SpellChecker();
}

控制反转(IOC):

public TextEditor(SpellChecker spellChecker) 
{
      this.spellChecker = spellChecker;
}

在这里,TextEditor 不应该担心 SpellChecker 的实现。SpellChecker 将会独立实现,并且在 TextEditor 实例化的时候将提供给 TextEditor,整个过程是由 Spring 框架的控制。

在这里,我们已经从 TextEditor 中删除了全面控制,并且把它保存到其他地方(即 XML 配置文件),且依赖关系(即 SpellChecker 类)通过类构造函数被注入到 TextEditor 类中。因此,控制流通过依赖注入(DI)已经“反转”,因为你已经有效地委托依赖关系到一些外部系统。

使用DI后代码十分的简洁,并且在定义对象依赖时也能有效的解耦。对象不需要过多的去关注它依赖的对象,所以类更容易测试,特别是依赖于接口或者是抽象类时。可以在单元测试中使用stub或者mock实现。

DI存在两种主要的形式:基于构造器注入和Setter方法注入。

1.1.基于构造器注入

构造注入通过容器提供指定的参数调用构造方法来完成,这和通过提供给静态的工厂方法的参数来创建对象几乎是等价的。
下面的样例展示了一个类通过构造方法来完成依赖注入的过程。注意:这个类是普通的Java对象,不依赖于容器相关的接口、基类或者注解。

public class SimpleMovieLister 
{
    // SimpleMovieLister依赖于MovieFinder
    private MovieFinder movieFinder;

    // Spring通过构造器注入MovieFinder实例
    public SimpleMovieLister(MovieFinder movieFinder) 
    {
        this.movieFinder = movieFinder;
    }

    //使用被注入MovieFinder实例来完成的业务逻辑已被忽略
}

1.1.1.构造参数的识别

构造参数的识别是通过类型匹配来完成的,如果在Bean的定义中没有模糊不清的构造参数,那么在Bean定义中指定的构造参数的顺序 也就是 Bean被实例化时构造器加载参数的顺序。如下:

package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
        // ...
    }
}

现在,参数都是明确的。假定Bar和Baz没有继承关系,因此下面的配置没有任何问题,你也不需要在元素中指定参数的索引和类型。

<beans>
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
    </bean>

    <bean id="bar" class="x.y.Bar"/>

    <bean id="baz" class="x.y.Baz"/>
</beans>

当引用的Bean的类型是确定的,如上一个Bar 类型, Baz 类型,这能很好的进行参数匹配。如果使用的是简单类型(基本类型、字符串等等),Spring就不能判断其类型了,所以不能独自完成类型匹配。参考如下代码:

package examples;

public class ExampleBean {

    //计算最终答案的年份
    private int years;

    //生活、宇宙、万物的答案
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg  value="ultimateAnswer"/>
    <constructor-arg value="7500000"/>
</bean>

形参实参就不好识别,因为在Bean定义中不知道构造器参数是什么类型的,所以

1.构造参数类型匹配
在上面的例子中,如果你通过type属性指定参数的类型,那么容器可以很好的进行简单参数类型的类型匹配。参考如下代码:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

2.构造参数索引 //推荐
使用index属性指定参数在构造器中的索引,参考如下代码:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

7500000是第一个参数,42是第二个
如果不指定index的值,则默认以出现的顺序为参数索引。

为了解决多个简单类型间的歧义性,有多个相同类型的时候,可以通过指定值在构造参数中的索引。注意:索引从0开始。

3.构造参数名 //不推荐
你也可以指定值所对应构造器中的参数名来消除歧义:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

注意:要使上述样例正常工作,必须启用调试标记来编译代码,这样Spring才能从构造器中查找到参数名。否则你必须使用@ConstructorProperties JDK注解来显式指定Bean的参数名,注解中的参数名与XML文件里一样,参考如下代码:

package examples;

public class ExampleBean 
{
    // 忽略属性
    
    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

最后,如果你想要向一个对象传递一个引用,你需要使用 标签的 ref 属性,如果你想要直接传递值,那么你应该使用如上所示的 value 属性。

1.1.2.例子

这是 TextEditor.java 文件的内容:

package com.tutorialspoint;
public class TextEditor 
{
   private SpellChecker spellChecker;
   public TextEditor(SpellChecker spellChecker) 
   {
      System.out.println("Inside TextEditor constructor." );
      this.spellChecker = spellChecker;
   }
   public void spellCheck() 
   {
      spellChecker.checkSpelling();
   }
}

下面是另一个依赖类文件 SpellChecker.java 的内容:

package com.tutorialspoint;
public class SpellChecker 
{
   public SpellChecker(){
      System.out.println("Inside SpellChecker constructor." );
   }
   public void checkSpelling() {
      System.out.println("Inside checkSpelling." );
   } 
}

以下是 MainApp.java 文件的内容:

package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp 
{
   public static void main(String[] args) 
   {
      ApplicationContext context = 
             new ClassPathXmlApplicationContext("Beans.xml");
      TextEditor te = (TextEditor) context.getBean("textEditor");
      te.spellCheck();
   }
}

下面是配置文件 Beans.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-3.0.xsd">

   <!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor">
      <constructor-arg ref="spellChecker"/>
   </bean>

   <!-- Definition for spellChecker bean -->
   <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
   </bean>

</beans>

当你完成了创建源和 bean 配置文件后,让我们开始运行应用程序。如果你的应用程序运行顺利的话,那么将会输出下述所示消息:

Inside SpellChecker constructor.
Inside TextEditor constructor.
Inside checkSpelling.

先init一个SpellChecker对象,再创到TextEditor构造方法中init一个TextEditor对象,再调用SpellChecker对象的checkSpelling方法

1.2.基于Setter方法注入

容器通过无参构造方法或者无参静态工厂方法实例化Bean之后,再通过调用Setter方法来完成依赖注入。

1.2.1.例子

下面是 TextEditor.java 文件的内容:

package com.tutorialspoint;
/*与构造注入不同的是,没有带参数的构造方法,使用一个setSpellChecker来设置属性spellChecker
这TMD区别很大吗?不就是Java里的set参数、get参数方法吗?*/
public class TextEditor 
{
   private SpellChecker spellChecker;
   // a setter method to inject the dependency.
   public void setSpellChecker(SpellChecker spellChecker) 
   {
      System.out.println("Inside setSpellChecker." );
      this.spellChecker = spellChecker;
   }
   // a getter method to return spellChecker
   public SpellChecker getSpellChecker() {
      return spellChecker;
   }
   public void spellCheck() {
      spellChecker.checkSpelling();
   }
}

在这里,你需要检查设值函数方法的名称转换。要设置一个变量 spellChecker,我们使用 setSpellChecker() 方法,该方法与 Java POJO 类非常相似。让我们创建另一个依赖类文件 SpellChecker.java 的内容:

//与构造注入一样
package com.tutorialspoint;
public class SpellChecker 
{
   public SpellChecker(){
      System.out.println("Inside SpellChecker constructor." );
   }
   public void checkSpelling() {
      System.out.println("Inside checkSpelling." );
   }  
}

以下是 MainApp.java 文件的内容:

package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp 
{
   public static void main(String[] args) 
   {
      ApplicationContext context = 
             new ClassPathXmlApplicationContext("Beans.xml");
      TextEditor te = (TextEditor) context.getBean("textEditor");
      te.spellCheck();
   }
}

下面是配置文件 Beans.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-3.0.xsd">

   <!-- Definition for textEditor bean -->
   <bean id="textEditor" class="com.tutorialspoint.TextEditor">
      <property name="spellChecker" ref="spellChecker"/>
   </bean>

   <!-- Definition for spellChecker bean -->
   <bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
   </bean>

</beans>

你应该注意定义在基于构造函数注入和基于设值函数注入中的 Beans.xml 文件的区别。唯一的区别就是在基于构造函数注入中,我们使用的是〈bean〉标签中的〈constructor-arg〉元素,而在基于设值函数的注入中,我们使用的是〈bean〉标签中的〈property〉元素。

第二个你需要注意的点是,如果你要把一个引用传递给一个对象,那么你需要使用 标签的 ref 属性,而如果你要直接传递一个值,那么你应该使用 value 属性。

当你完成了创建源和 bean 配置文件后,让我们开始运行应用程序。如果你的应用程序运行顺利的话,那么将会输出下述所示消息:

Inside SpellChecker constructor.
Inside setSpellChecker.
Inside checkSpelling.

1.3.使用构造注入还是Setter方法注入?

当你使用混合注入时通过构造方法完成必要依赖的注入以及通过Setter方法完成可选依赖注入是一个好的编程方式。注意:在Setter方法上标注@Required(这个后面再说)注解使得属性为必需依赖。

Spring团队提倡使用构造注入,该方式可以使得依赖为不可变对象并且不会为空;此外,通过构造注入的依赖被客户端调用时都是完全初始化好的;它还可以作为启示:过多的构造参数是一种不好的编程方式,意味着此类拥有过多的职责,最好重构代码,将多余的职责分离开来。

Setter注入主要用于可选依赖,但是在类中也应该分配合理的默认值,否则在客户端使用此依赖时需要做非空检查。使用Setter注入有一个好处:可以重新配置或者重新注入依赖。通过JMX mbean进行管理是Setter注入的一个引人注目的用例。

DI只在大多场景下是有效的,当处理没有源码的第三方类时,可以选择其它注入方式。比如:第三方类没有暴露任何Setter方法,那只能通过构造注入了。

2.依赖配置

2.1.使用 p-namespace 简化 XML 配置:

如果你有许多的设值函数方法,那么在 XML 配置文件中使用 p-namespace 是非常方便的。让我们查看一下区别:

以带有 标签的标准 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-3.0.xsd">

   <bean id="john-classic" class="com.example.Person">
      <property name="name" value="John Doe"/>
      <property name="spouse" ref="jane"/>
   </bean>

   <bean name="jane" class="com.example.Person">
      <property name="name" value="John Doe"/>
   </bean>

</beans>

上述 XML 配置文件可以使用 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-3.0.xsd">

   <bean id="john-classic" class="com.example.Person"
      p:name="John Doe"
      p:spouse-ref="jane"/>
   </bean>

   <bean name="jane" class="com.example.Person"
      p:name="John Doe"/>
   </bean>

</beans>

格式:p:<name><-ref>=<value>
-ref 部分表明这不是一个直接的值,而是对另一个 bean 的引用。

Spring团队更喜欢<value/>元素而不是value属性
Spring容器通过PropertyEditor将<value/>元素内的文本转换为java.util.Properties实例。这种方式简单快捷。

2.2.< idref/ >元素

此元素是一种通过其他Bean的id值来引用Bean的方式,可以使用在或者元素中,并且拥有错误校验的功能。

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

上面的配置与下面的配置完全等价(运行时):

<bean id="theTargetBean" class="..." />

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

第一种形式的配置要优于第二种,因为<idref/>元素使得容器会在部署时就验证引用的Bean是否真实存在。在第二例子中,targetName的值不会被验证,拼写错误只能在client Bean被实例化时才出现(很有可能出现严重错误)。如果client Bean是原型模式的,拼写错误和异常结果可能会在容器发布很久之后才被发现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值