0%

Java依赖注入规范:JSR330

Java World似乎总会出现一些接口规范,这样做的好处是可以面向接口编程,可以在实现了该接口的产品/组件之间自由切换,避免被厂商绑架。 本文要介绍的JSR330:Dependency Injection for Java,Java依赖注入规范。

依赖关系

在面向对象编程中,我们常常处理依赖。比如ClassA依赖ClassB,通常需要:

1
2
3
4
5
6
ClassA a = new ClassA();
ClassB b = new ClassB();

a.setB(b);
a.xxx();
……

大量这样的依赖处理会导致高耦合度,并且由于通过硬编码组织对象和资源,代码不具有灵活性。

DI和IoC

一种更好的处理方式是 依赖注入 。比如上面的例子中:

  1. 通过某种方式声明“ClassA 依赖 ClassB”
  2. 使用ClassA时,有某种机制自动创建ClassB并将其 注入 到ClassA

这里面,“某种机制”需要一个容器来实现。这个容器叫做”IoC(Inversion of Control)容器”。
之所以叫做“控制反转”,是说不在对象中直接控制,而是由容器控制创建对象、为对象注入其他对象和资源等行为。

IoC是一种思想,实际上我们遇到的大多数“容器”都有对其内容的控制功能。

对于前一节的例子,IoC容器可能会有这样一个处理过程:

  1. 创建ClassA
  2. 分析ClassA的依赖项,得出其依赖ClassB
  3. 创建ClassB
  4. 将ClassB注入到ClassA

之后,我们可以直接从容器中获取创建并组装好的ClassA对象,无需任何处理即可使用。

关于DI和IoC,可以参考Martin Fowler的经典文章:《Inversion of Control Containers and the Dependency Injection pattern》

依赖的描述

在DI和IoC的历史上,Spring功不可没。可以说,Spring使得DI和IoC称为Java应用开发的主流方式。

2004年3月,Spring 1.0
使用外部配置文件(xml)描述对象之间的依赖关系。

2004年10月,JDK1.5开始支持注解(Annotations)语法。

2007年3月, Google Guice 1.0发布,使用annotations作为依赖描述的方式。

2007年11月,Spring 2.5也开始支持annotation。

JSR330

随着各种IoC容器的出现,依赖的描述方式也五花八门。为了规范和统一,JCP(Java Community Process)于2009年10月发布了
JSR330

JSR330在javax.inject中规定了依赖注入的标准注解(Annotations)。包括:

  • @Inject : 标记为“可注入”。可用于构造器(constructors), 方法(methods)或字段(fields)
  • @Qualifier : 限定器
  • @Scope : 标记作用域
  • @Named : 基于 String 的限定器
  • @Singleton : 标记为单例

JSR330只规定了依赖注入的描述,对于容器实现未作要求。目前 Spring 、Guice 、Eclipse e4等常用框架已经开始兼容该规范。
JSR-299(Contexts and Dependency Injection for Java EE platform,参考实现 Weld )在依赖注入上也使用该规范。

比如,定义两个接口:

1
2
3
4
5
6
7
8
9
10
11
interface MessageRenderer {
public void render();

public void setMessageProvider(MessageProvider provider);

public MessageProvider getMessageProvider();
}

interface MessageProvider {
public String getMessage();
}

很明显,一个MessageRenderer依赖一个MessageProvider。

在实现类中,可以使用依赖注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class StandardOutMessageRenderer implements MessageRenderer {

@Inject
@Named("messageProvider")
private MessageProvider messageProvider = null;

public void render() {
if (messageProvider == null) {
throw new RuntimeException(
"You must set the property messageProvider of class:"
+ StandardOutMessageRenderer.class.getName());
}

System.out.println(messageProvider.getMessage());
}

public void setMessageProvider(MessageProvider provider) {
this.messageProvider = provider;
}

public MessageProvider getMessageProvider() {
return this.messageProvider;
}

}


class ConfigurableMessageProvider implements MessageProvider {

private String message = "Default message";

public ConfigurableMessageProvider() {
}

@Inject
@Named("message")
public ConfigurableMessageProvider(String message) {
this.message = message;
}

public void setMessage(String message) {
this.message = message;
}

public String getMessage() {
return message;
}

}

其中,ConfigurableMessageProvider的构造函数中依赖一个String类型的参数。

spring实现

Spring 3.0开始支持JSR330。下面的例子中,使用spring的classpath scanning功能
(从spring2.5开始支持),
会自动组装bean。

1
2
3
4
5
6
7
8
9
10
11
12
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

<context:component-scan base-package="demo"/>
<bean id="message" class="java.lang.String">
<constructor-arg value="You are running JSR330!"/>
</bean>
</beans>
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {

GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:jsr330.xml");
ctx.refresh();
MessageRenderer renderer = ctx.getBean("messageRenderer",
MessageRenderer.class);
renderer.render();
ctx.close();
}