欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring5高级编程------IOC的两种类型依赖注入和依赖查找详解

程序员文章站 2022-04-14 16:49:55
...

一、IOC实现控制反转

IOC的核心是DI,旨在提供一种更简单的机制来设置组件依赖项(通常称作对象的协助者),并在整个生命周期里管理这些依赖项。需要某些依赖项的组件通常称为依赖对象,或是在IOC的情况下被称为目标对象。通常,IOC可以被分为两种类型:依赖注入和依赖查找。

二、依赖注入和依赖查找

依赖查找是一种更为传统的方法,虽然初看起来依赖注入有悖常理,但是实际上比依赖查找更灵活且更好用。使用依赖查找时,组件必须获取对依赖项的引用,而使用依赖注入时,依赖项将通过IOC容器注入组件。依赖查找有两种类型:依赖拉取和上下文依赖查找。依赖注入也有两种常见风格:构造函数和setter依赖注入。

1、依赖拉取

依赖拉去是一种常见的IOC容器类型,在依赖拉取中,根据需要从注册表中提取依赖项。任何编写过代码访问EJB(2.1或更早版本)的开发人员都使用过依赖拉去(即通过JNDI API查找EJB组件)。
Spring5高级编程------IOC的两种类型依赖注入和依赖查找详解
Spring还提供依赖拉取作为一种检索框架所管理组件的机制。下面是基于Spring的应用程序中典型的依赖拉取查询:

public class DependencyPull {
	public static void main(String[] args) {
		ApplicationContext ctx = new GenericXmlApplicationContext("classpath:app-context-xml4.xml");
		MessageRenderer mr = ctx.getBean("messageRenderer", MessageRenderer.class);
		mr.renderer();
	}
}

2、上下文依赖查找

上下文依赖查找在某些方面与依赖拉取类似,但在上下文依赖查找中,查找是针对管理资源的容器执行的,而不是来自某个*注册表,并且通常是在某个设定点执行。
Spring5高级编程------IOC的两种类型依赖注入和依赖查找详解
上下文依赖查找通过让组件实现类似以下代码的接口来工作:

/**
 * 服务组件接口
 * 
 * @author dyw
 * @date 2020年7月13日
 */
public interface ManagedComponent {
	void performLookup(Container container);
}

通过实现该接口,一个组件可以向容器发送它想要获得依赖项的信号。容器通常由底层应用程序服务器或框架(例如Tomcat,JBoss或Spring)提供。以下给出一个提供依赖查找服务的简单Container接口:

/**
 * 上下文依赖查找服务接口
 * 
 * @author dyw
 * @date 2020年7月13日
 */
public interface Container {
	Object getDependency(String key);
}

当容器准备将依赖项传递给组件时,会依次调用每个组件的performLookup()方法。然后,组件可以使用Container接口查找所需依赖项,代码如下:

package com.wholesmart.ch2;

/**
 * 一个简单的服务组件实现
 * 
 * @author dyw
 * @date 2020年7月13日
 */
public class ContextualizedDependencyLookup implements ManagedComponent {
	private Dependency dependency;
	@Override
	public void performLookup(Container container) {
		this.dependency = (Dependency) container.getDependency("myDepandency");
	}
	@Override
	public String toString() {
		return dependency.toString();
	}
}

3、构造函数依赖注入

当在组件的构造函数中提供依赖项时,就会发生构造函数依赖注入。首先,组件声明一个或是一组构造函数,并将其依赖项作为参数,然后在组件实例化时由IOC容器将依赖项传递给组件,代码如下:

package com.wholesmart.ch2;

/**
 * 构造器依赖注入
 * 
 * @author dyw
 * @date 2020年7月13日
 */
public class ConstructorInjection {
	private Dependency dependency;

	public ConstructorInjection (Dependency dependency) {
		this.dependency = dependency;
	}

	@Override
	public String toString() {
		return dependency.toString();
	}
}

使用构造器注入的一个显而易见的结果就是,如果没有依赖项,就不能创建对象。

4、setter依赖注入

在setter依赖注入中,IOC容器通过JavaBean样式的setter方法注入组件的依赖项。组件的setter方法公开了IOC容器可以管理的依赖项。以下代码是典型的setter依赖注入:

package com.wholesmart.ch2;

/**
 * setter依赖注入
 * 
 * @author dyw
 * @date 2020年7月13日
 */
public class SetterInInjection {
	private Dependency dependency;

	public void setDependency(Dependency dependency) {
		this.dependency = dependency;
	}

	@Override
	public String toString() {
		return dependency.toString();
	}
}

使用setter注入的一个显而易见的好处就是,可以在没有依赖项的情况下创建对象,然后通过setter来提供依赖项。
注意:在Spring中,还支持一种属性字段注入

三、注入与查找

使用哪种类型的IOC(注入或是查找)通常不难作决定。在许多情况下,所使用的IOC类型由使用的容器确定。例如,使用EIB2.1或是更早的版本,则必须使用查找式IOC容器从JEE容器中获取EJB。在Spring中,除了初始化的bean查找,组件及其依赖项始终使用注入式IOC连接在一起。

从前面的代码中,可以清楚的看到使用注入对组件的代码没有任何影响。另一方面,依赖拉取代码必须主动获的对注册表的引用并与其交互以获取依赖项,而使用上下文依赖查找,则需要你的类实现特定的接口并手动查找所有依赖项。当使用注入时,你的类需要做的就是允许使用构造函数或setter注入依赖项。

四、setter注入和构造函数注入

当使用一个组件之前必须拥有一个依赖类的实例时,构造函数注入就特别有用了。此外,构造函数注入也有助于实现不可变对象的使用。

如果组件向容器公开了他的依赖项,并乐于提供自己的默认值,那么setter注入通常是实现此目的的最佳方法。setter注入的另一个好处是,它允许在接口上声明依赖项,尽管这种方式有时候会有所限制。假设一个带有业务方法defineMeaningOfLife()的典型业务接口,如果除了定义该方法之外还定义了一个用于注入的setter方法(例如setEncylopedia()),那么就会强制所有的实现都必须使用或是至少知道依赖项。其实我们并不需要在业务接口中定义setEncylopedia()。相反,我们可以在实现业务方法的类中定义方法,当以这种方式进行编程时,包括spring在内的所有IOC容器都可以以业务接口的形式使用组件,同时仍然提供了实现类的依赖。如下代码:

package com.wholesmart.ch2;

/**
 * 业务接口
 * 
 * @author dyw
 * @date 2020年7月13日
 */
public interface Oracle {
	/**
	 * 业务方法
	 * 
	 * @return
	 */
	String defineMeaningOfLife();
}

业务接口里我们没有提供依赖注入的任何setter方法,该接口的实现如下:

package com.wholesmart.ch2;

/**
 * 业务类实现
 * 
 * @author dyw
 * @date 2020年7月13日
 */
public class BookwormOracle implements Oracle {
	private Dependency dependency;

	@Override
	public String defineMeaningOfLife() {
		return dependency.toString();
	}

	public void setDependency(Dependency dependency) {
		this.dependency = dependency;
	}
}

正如你所看到的,BookwormOracle 类不仅实现了Oracle 接口,还定义了用于依赖注入的setter方法。
使用setter方法在接口上声明依赖项虽然是一个好处,但实际上,应尽量保持setter仅用于实现类。除非完全确定特定业务接口的所有实现都需要一个特定的依赖项,否则让每个实现类定义自己的依赖项并为业务方法保留业务接口。
一般来说,应根据使用情况选择注入类型。基于setter的注入允许在不创建新对象的情况下交换依赖项,并且还可以让类选择适当的默认值,而无需显示注入对象。当想要确保将依赖项传递给组件和设计不可变对象时,构造函数注入是一个不错的选择。

面对一些不公,我们更多想的是自身做得还不够好,我们会试着凭自己的双手努力去改变,但我们如果发现有天辛勤与汗水再也起不了作用。我们又会化作天下最难以驯服,最坚韧的暴民。