JavaEE中资源注入松耦合的实现 | 从开发角度看应用架构13

前言

本文仅代表作者的个人观点;

本文的内容仅限于技术探讨,不能作为指导生产环境的素材;

本文素材是红帽公司产品技术和手册;

本文分为系列文章,将会有多篇,初步预计将会有26篇。

一、CDI是个啥?

上下文和依赖注入(CDI)规范是Java EE规范中的许多从属规范之一。虽然CDI是在Java EE 6中引入的,但CDI背后的概念已经出现在各种框架中,包括Spring,Google Guice等。 Java Community Process在2009年12月以最终形式引入了Java Specification Request 299.JSR 346正式定义了Java EE 7平台的CDI。这意味着每个被认证为符合Java EE 7的应用程序服务器(例如JBoss EAP)必须本身支持上下文和依赖项注入。

CDI有两个主要部分:上下文和依赖注入。由于它与CDI有关,因此上下文指的是按数据范围定义应用程序的能力,CDI指定的依赖注入是一个过程,通过该过程,对象的实例可以以类型安全的方式自动实例化为其他应用程序对象。注入对象的特定实现的决定可以延迟到应用程序部署的时间。在其他框架中,注入基于字符串匹配。 CDI通过类型化注入改进了这一点,在编译时检查类型。包括类型安全性会在开发生命周期的早期暴露注入错误,并使调试更容易。

依赖注入(DI)的主要好处之一是应用程序组件的松耦合。例如,客户端和服务器组件松散合,因为可以将几个不同版本的服务器注入客户端。客户端使用接口,并且不知道它正在与哪个服务器通信。利用部署时注入,可以将特定对象用于不同类型的环境,例如生产和测试环境。例如,可以根据部署环境注入生产或测试数据源。

CDI类似于使用资源注入来注入资源,例如@PersistenceContext和persistence.xml文件。这两种方法都创建了由容器管理的资源依赖性,并且两者都松散地耦合应用程序组件。但是,它们在几个重要方面有所不同。因为资源注入使用JNDI名称来注入资源,所以资源注入不像CDI那样是类型安全的。 CDI是类型安全的,因为对象是基于类型实例化的。此外,CDI能够直接注入常规Java类,而资源注入不能注入常规类,而是通过JNDI名称引用资源。

二、比较EJB和CDI

区分EJB和CDI很重要,因为两个规范之间的功能有重叠。 在JBoss EAP上运行的Java EE 7应用程序中,开发人员通常会将这两种技术相互结合使用。 所有EJB都是CDI bean,因此可以访问依赖注入,并且有资格自己注入。

EJB规范建立在CDI规范的基础上,提供更多功能,区分无状态bean和有状态bean。 EJB还提供其他功能,例如并发功能,bean池,安全性以及CDI中未包含的其他功能。 在创建bean时,如果不需要EJB的功能,最好不要使用EJB。 相反,使用CDI来管理上下文和依赖注入。

三、使用依赖注入

CDI不会在Web应用程序、EJB或Java库(JAR)中自动激活,因为容器扫描每个应用程序和每个库都是低效的。要在Web应用程序中启用CDI,需要WEB-INF目录中放置名为beans.xml的空文件。对于JAR文件(包括那些包含EJB的文件),将beans.xml文件放在META-INF目录中,同样,beans.xml是空文件就可以。

bean没有特殊的声明或注释来引用CDI。但EJB需要将其类型标记为@Stateless,@ MessageDriven等的注释。

要将bean的实例注入另一个类的实例变量,请使用@Inject注释。当容器在部署时扫描带注释的类时,它会尝试查找与注释的bean类型匹配的单个bean。如果容器找到多个匹配项,则会产生不明确的依赖项错误。 @Inject批注通常用于成员声明或Java类的构造函数参数。以下是名为EmailValidator的示例实用程序类,其中包含公共checkEmail方法:

public class EmailValidator {

	public bool checkEmail(String email) {
		...
	}
   
}

要在CDI中使用此类和方法,请使用以下注入代码注入类的实例:

public class Form {
	...	@Inject
	private EmailValidator emailValidator;

	public void submitForm(){
		...
		emailValidator.checkEmail(email);
		...
	}
}

使用注入,ContainerValidator类由容器自动实例化。

Qualifiers是一个自定义注释,它是在一个类注入一个bean的时候定义的。它解决的问题是:一个被注入的bean方法接口可能存在多个实现类。 当发生这种类型的模糊注入时,容器无法选择要注入的实现。 限定符允许用户创建自定义限定符注释以指示容器应使用哪个实现,从而解决了这种歧义。

使用以下模板定义Qualifiers:

@Qualifier

@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface qualifier-name { }

例如,以下是创建注释@SlowBike的限定符:

@Qualifier@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER}) 
public @interface SlowBike { }

创建该限定符后,使用@SlowBike注释类头:

@SlowBikepublic class Moped extends Bike implements Vehicle<Motorized> {

现在,当bean注入Vehicle接口并使用SlowBike注释时,容器会自动实例化Moped类实现的实例:

public class Inventory {  
  @Inject    @SlowBike
    private Bike myBike;
...
}

默认情况下,所有CDI bean都有一个限定符。 如果未指定,则限定符为@Default。 此外,如果使用@Default批注明确注释bean,则在注入点未指定其他限定符时,该bean将用作默认实现。

四、使用Producers

使用CDI的一个主要优点是:代码编译的时候,不会去实例化被注入的对象。Producer提供了在运行时,使用可定制逻辑来确定如何做出这些决策的实现决策的能力。 生成器是生成可注入对象的方法或对象属性。Produces的优点是可以使非bean对象可注入。

@Produces注释可以附加到以下对象:

  • Managed beans
  • Primitives, such as int and long
  • Parameterized types such as Set<String>
  • Raw types such as Set

以下是使用@Produces注释并注入PaymentStrategy的方法的示例:

@Produces @Preferred public PaymentStrategy getStrategicPaymentStrategy() { ... } 
public class CalculateInterestBean {
...
  @Inject @Preferred PaymentStrategy strategy;
...

前面的示例显示了在方法声明中使用的Producers。 在Producers方法上使用限定符来区分可用于注入的对象类型是很常见的。 组合限定符和生成器允许开发人员提供多个生成器方法,然后使用带有限定符的模糊注入来区分应该使用哪个生成器方法。 在前面的示例中,使用限定符@Preferred注入对象。

使用@Produces在Java类中注释属性时,可以将该属性注入任何托管bean中的属性。 这对于声明和使用Java EE资源(例如数据源和记录器)非常有用。 与生产者方法一样,生产者字段通常使用限定符进行注释。

@Produces @AccountsDB Connection connection = new Connection(userId, password);

五、实验展示:依赖注入

实验流程:

  1. 在一个package中创建一个接口,定义一个方法:

2. 创建两个类,以便对接口进行方法实现:

(1)AllCaps: 所有输入的内容,都按照大写输出

(2)TitleCase:所有输入的内容,第一个字母大写输出

3.创建一个Qulifier:Title。TitleCase类注入这个Qulifier。Title这个Qulifier是个伪接口。

4.PersonService通过CDI进行注入:

(1)@Inject :注入AllCaps的类,所有输入的内容,都按照大写输出

(2)@Inject @Title:注入TitleCase类,所有输入的内容,第一个字母大写输出

首先通过JBDS导入已经已经存在的maven项目:

使用方法sanitizeName(String name)创建名为NameUtil.java的新接口。

将以下代码添加到sanitizeName(String name)方法的新接口:

保存退出。

在com.redhat.com.training.util包中创建两个新类,每个类都实现NameUtil接口。

更新类头以实现NameUtil接口:

将鼠标悬停在AllCaps类名称上,然后单击添加未实现的方法以创建sanitizeName(String name)方法并删除错误。 生成的代码如下所示:

保存。

在JBDS左窗格的Project Explorer选项卡中,右键单击dependency-injection→Java Resources→src / main / java→com.redhat.training.util,然后单击New→Class。

输入TitleCase作为类的名称,然后单击Finish

更新类头以实现NameUtil接口:

return Name.substring(0,1).toUpperCase() + Name.substring(1);

将@PostConstruct方法添加到每个新的实用程序bean中,该实用程序bean打印一个日志语句以在注入bean时声明。

在AllCaps.java类中,添加以下@PostConstruct方法和javax.annotation.PostConstruct导入:

import javax.annotation.PostConstruct;
@PostConstructpublic void printTitle(){
    System.out.println("***TitleCase PostConstruct***");}

在TitleCase.java中:

import javax.annotation.PostConstruct;...

@PostConstructpublic void printTitle(){
    System.out.println("***TitleCase PostConstruct***");}

将NameUtil接口注入PersonService.java类,并在将名称持久保存到数据库之前使用sanitizeName(String name)方法。

在JBDS左窗格的Project Explorer选项卡中,单击dependency-injection→Java Resources→src / main / java→com.redhat.training.ejb并展开它。 双击PersonService.java文件。

在类头之后,添加以下代码以将NameUtil接口注入PersonService类:

在将Person持久化到数据库并输出名称之前,将以下行添加到hello()方法以清理名称输入:

出现告警:

创建一个新的限定符并使用实用程序类上的限定符来解决模糊注入点。

在JBDS左窗格的Project Explorer选项卡中,右键单击dependency-injection→Java Resources→src / main / java→com.redhat.training.util,然后单击New→Other

在com.redhat.training.util.TitleCase类中,将限定符添加到类头中:

返回PersonService.java并注意之前警告不再存在。

使用Maven通过运行以下命令在JBoss EAP上部署应用程序:

通过浏览器访问应用:

输入名字,返回是hello+名字+时间 的拼接,输出是所有字母大写,调用的是AllCaps的类:

因为没有在Name Util的注入点上指定限定符,所以使用具有@Default注释的bean。

在EAP服务器日志中,请注意AllCaps类的post构造方法输出仅在单击submit后但在实际使用对象的方法之前发生:

更新NameUtil注入以使用@Title限定符。

单击PersonService.java文件以在编辑器窗格中编辑PersonService类。

添加以下行以更改NameUtil的实现并导入com.redhat.training.util.Title:

重新编译与部署:

可以看出,输出是第一个字母大写。调用的是TitleCase类:

在EAP服务器日志中,请注意打印TitleCase类的post-construct方法输出:

魏新宇

  • "大魏分享"运营者、红帽资深解决方案架构师
  • 专注开源云计算、容器及自动化运维在金融行业的推广
  • 拥有MBA、ITIL V3、Cobit5、C-STAR、TOGAF9.1(鉴定级)等管理认证。
  • 拥有红帽RHCE/RHCA、VMware VCP-DCV、VCP-DT、VCP-Network、VCP-Cloud、AIX、HPUX等技术认证。

原文发布于微信公众号 - 大魏分享(david-share)

原文发表时间:2018-08-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JAVA技术zhai

干货:Java并发编程系列之synchronized(一)

28470
来自专栏爱撒谎的男孩

Spring MVC处理异常

注意:使用SimpleMappingExceptionResolver处理异常时,不可以使用@ExceptionHandler!

36250
来自专栏JMCui

Netty 系列四(ChannelHandler 和 ChannelPipeline).

    先来整体的介绍一下这篇博文要介绍的几个概念(Channel、ChannelHandler、ChannelPipeline、ChannelHandlerC...

13120
来自专栏ImportSource

Spring Boot处理REST API错误的正确姿势

如何正确的处理API的返回信息,让返回的错误信息提供更多的含义是一个非常值得做的功能。 默认一般返回的都是难以理解的堆栈信息,然而这些信息也许对于API的客户...

628130
来自专栏竹清助手

浅谈Linux磁盘修复e2fsck命令

检查 /dev/mapper/VolGroup00-LogVol02 是否有问题,如发现问题便自动修复:

37020
来自专栏haifeiWu与他朋友们的专栏

阿里 RPC 框架 DUBBO 初体验

最近研究了一下阿里开源的分布式RPC框架dubbo,楼主写了一个 demo,体验了一下dubbo的功能。

41820
来自专栏java、Spring、技术分享

记一次unable to create new native thread错误处理过程

unable to create new native thread,看到这里,首先想到的是让运维搞一份线上的线程堆栈(可能通过jstack命令搞定的)。...

1.2K10
来自专栏Spark学习技巧

锁机制-java面试

何为同步?JVM规范规定JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和mo...

40160
来自专栏互联网大杂烩

Java锁与并发

保护临界区资源不会被多个线程同时访问时而受到破坏。通过锁,可以让多个线程排队。一个一个地进入临界区访问目标对象,使目标对象的状态总是保持一致。

12820
来自专栏公众号_薛勤的博客

史上最全面的Spring Boot配置文件详解

Spring Boot在工作中是用到的越来越广泛了,简单方便,有了它,效率提高不知道多少倍。Spring Boot配置文件对Spring Boot来说就是入门和...

2.1K20

扫码关注云+社区

领取腾讯云代金券