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 一种更好的处理方式是 依赖注入 。比如上面的例子中:
通过某种方式声明“ClassA 依赖 ClassB”
使用ClassA时,有某种机制自动创建ClassB并将其 注入 到ClassA
这里面,“某种机制”需要一个容器来实现。这个容器叫做”IoC(Inversion of Control)容器”。 之所以叫做“控制反转”,是说不在对象中直接控制,而是由容器控制创建对象、为对象注入其他对象和资源等行为。
IoC是一种思想,实际上我们遇到的大多数“容器”都有对其内容的控制功能。
对于前一节的例子,IoC容器可能会有这样一个处理过程:
创建ClassA
分析ClassA的依赖项,得出其依赖ClassB
创建ClassB
将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(); }