依赖注入是Java(以及许多其他编程语言)中广泛使用的软件设计模式,用于实现控制反转 。 它提高了可重用性,可测试性,可维护性,并有助于构建松耦合的组件。 如今,依赖注入是将Java对象连接在一起的事实上的标准。
诸如Spring或Guice之类的各种Java框架可以帮助实现依赖注入。 从Java EE 6开始,还有一个正式的Java EE API用于依赖关系注入: 上下文和依赖关系注入 (CDI)。
我们使用依赖注入来注入服务,存储库,与域相关的组件,资源或配置值。 但是,以我的经验,依赖注入也可以用来注入域对象,这常常被忽略。
一个典型的例子是在Java许多应用程序中获取当前登录用户的方式。 通常,我们最终会向登录用户询问某些组件或服务。
此代码看起来可能类似于以下代码片段:
public class SomeComponent {@Injectprivate AuthService authService;public void workWithUser() {User loggedInUser = authService.getLoggedInUser();// do something with loggedInUser}
}
此处,将AuthService实例注入SomeComponent。 SomeComponent的方法现在使用AuthService对象来获取已登录用户的实例。
但是,除了注入AuthService之外,我们还可以将登录用户直接注入SomeComponent中。
可能看起来像这样:
public class SomeComponent {@Inject@LoggedInUserprivate User loggedInUser;public void workWithUser() {// do something with loggedInUser}
}
在这里,User对象直接注入SomeComponent中,不需要AuthService实例。 如果存在多个类型为User的(托管)bean,则使用自定义批注@LoggedInUser来避免冲突。
Spring和CDI都可以进行这种类型的注入(并且配置实际上非常相似)。 在下一节中,我们将看到如何使用Spring注入域对象。 在此之后,我将描述使用CDI进行相同操作所需的更改。
使用Spring进行域对象注入
要注入上面示例中所示的域对象,我们只需要做两个小步骤。
首先,我们必须创建@LoggedInUser批注:
import java.lang.annotation.*;
import org.springframework.beans.factory.annotation.Qualifier;@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface LoggedInUser {}
请注意@Qualifier批注,它将@LoggedInUser变成自定义限定符。 如果有多个相同类型的bean可用,Spring会使用限定符来避免冲突。
接下来,我们必须在我们的Spring配置中添加一个bean定义。 我们在这里使用Spring的Java配置,也可以使用xml配置来完成。
@Configuration
public class Application {@Bean@LoggedInUser@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)public User getLoggedInUser() {// retrieve and return user object from server/database/session}
}
在getLoggedInUser()内部,我们必须检索并返回当前登录用户的实例(例如,通过从第一个代码片段中询问AuthService)。 使用@Scope,我们可以控制返回对象的范围。 最佳范围取决于域对象,并且在不同的域对象之间可能有所不同。 对于代表登录用户的User对象, 请求或会话范围将是有效的选择。 通过用@LoggedInUser注释getLoggedInUser(),我们告诉Spring应该在每次注入用户类型为@LoggedInUser的bean时使用此bean定义。
现在,我们可以将登录用户注入其他组件:
@Component
public class SomeComponent {@Autowired@LoggedInUserprivate User loggedInUser;...
}
在这个简单的示例中,实际上不需要限定符注释。 只要只有一个类型为User的bean定义可用,Spring可以按类型注入已登录的用户。 但是,在注入域对象时,很容易发生您具有相同类型的多个bean定义。 因此,使用附加的限定符注释是一个好主意。 限定词凭借其描述性名称也可以充当文档(如果命名正确)。
简化Spring bean定义
注入许多域对象时,最终有可能在bean配置中一遍又一遍地重复作用域和代理配置。 在这种情况下,可以在自定义注释上使用Spring注释。 因此,我们可以简单地创建自己的@SessionScopedBean批注,以代替@Bean和@Scope:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public @interface SessionScopedBean {}
现在我们可以简化bean的定义:
@Configuration
public class Application {@LoggedInUser@SessionScopedBeanpublic User getLoggedInUser() {...}
}
Java EE和CDI
CDI的配置几乎相同。 唯一的区别是我们必须用javax.inject和CDI注释替换Spring注释。
因此,@LoggedInUser应该使用javax.inject.Qualifier进行注释,而不是org.springframework.beans.factory.annotation.Qualifier进行注释(请参阅: 使用Qualifiers )。
Spring bean定义可以用CDI Producer方法代替。 可以使用适当的CDI范围注释代替@Scope。
在注入点,可以将Spring的@Autowired替换为@Inject。
请注意,Spring还支持javax.inject注释。 如果将javax.inject依赖项添加到Spring项目中,则还可以使用@Inject和@ javax.inject.Qualifier。 这样做实际上是一个好主意,因为它可以减少Java代码中的Spring依赖关系。
结论
我们可以使用自定义注释和作用域bean将域对象注入到其他组件中。 注入域对象可以使您的代码更易于阅读,并且可以导致更清晰的依赖关系。 如果仅注入AuthService来获取登录用户,则实际上取决于登录用户而不是AuthService。
不利的一面是,它使您的代码更牢固地依赖于依赖注入框架,该框架必须为您管理bean范围。 如果要保持在Dependency Injection容器之外使用类的能力,则可能会遇到问题。
哪种类型的域对象适合注入在很大程度上取决于您正在处理的应用程序。 好的候选对象是您经常使用的领域对象,它不依赖于任何方法或请求参数。 当前登录的用户是可能通常适合注入的对象。
- 您可以在GitHub上找到所示示例的源代码。
翻译自: https://www.javacodegeeks.com/2014/10/injecting-domain-objects-instead-of-infrastructure-components.html