php cdi
在Enterprise Java的早期,创建了许多框架,每个框架都有非常明确的技术目标。 不幸的是,这些很快变得超载,并塞满了很多功能,无法满足每一个需求。 CDI专家组旨在通过严格关注依赖项注入的核心问题并定义一个非常强大的扩展机制来在其之上实现附加功能,来避免CDI规范出现此问题。 CDI扩展机制Swift流行起来,并且已经有许多高质量的扩展可用。
此机制称为“便携式CDI扩展”,因为它基于指定明确的SPI,每个经过认证的独立CDI容器和EE 6服务器都必须支持该SPI。 因此,在一个CDI容器上编写的CDI扩展也将在所有其他容器上运行:JBoss Weld,Apache OpenWebBeans,Caucho Resin CanDI,IBM WebSphere 8,Oracle Glassfish 3,Oracle Weblogic 12c或Apache TomEE。
由于这些扩展的实现是可移植的,与供应商无关的方式,因此可以与所有CDI提供程序实现一起重用。 诸如Apache MyFaces CODI和JBoss Seam 3之类的项目提供了“重要的”,通常需要的扩展的集合,从而使CDI用户可以轻松地使用他们所需的扩展来扩展其应用程序,而不必考虑核心CDI的实现。
CDI扩展如何工作?
CDI扩展机制提供了将自定义代码挂接到CDI容器的生命周期中的方法。 因此,要在我们自己的CDI扩展中利用此功能,我们必须了解CDI容器生命周期本身的基本知识。 我们将首先为您提供此过程的快速概述,并在本文后面详细介绍这些细节。
CDI容器执行的第一个任务是加载所有CDI扩展。 接下来,还将扫描JAR文件中包含的具有META-INF / beans.xml标记文件的所有类。 对于每个这些类, 将基于给定的类构造 AnnotatedType 。 该对象包含有关已处理类的所有注释,构造函数,方法,字段等的元数据。 可以通过扩展名修改此信息。 稍后,CDI容器将在用于管理上下文对象的Bean <T>实例中公开此元数据。
在扫描完所有类并验证了所有约束之后,容器将启动所有可用上下文。
为工作计划程序编写CDI扩展
作业调度服务的集成示例将重点介绍在CDI环境中利用扩展机制所必需的内容。 在本文中,我们想通过编写CDI扩展来完成构建这种调度程序集成所需的步骤。
创建CDI扩展就像编写一个实现接口 javax.enterprise.inject.spi.Extension 的类一样简单 。 此类将包含我们扩展的功能。 我们将在后面的章节中看到此类如何与CDI容器进行交互。
容器将通过Java ServiceLoader 机制 获取我们的扩展 。 只需 在JAR的META-INF / services文件夹中 放置一个名为 javax.enterprise.inject.spi.Extension 的文件 (与接口相同的名称),并确保该文件包含扩展实现的标准名称。 容器将在运行时查找类的名称,并通过默认构造函数实例化它(因此该类需要一个默认构造函数)。
在深入研究扩展的内容之前,我们将首先研究Quartz(要与之集成的调度程序),以及在没有扩展的情况下如何使用Quartz。
用Quartz安排工作的经典方法
Quartz是一种开源作业调度服务。 用户定义“作业”,并让石英知道何时应通过所谓的“触发器”执行这些作业。 然后,调度程序本身将按照给定的规则运行这些作业。
在Servlet容器中使用Quartz的最简单方法可能是使用 Quartz提供 的 QuartzInitializerServlet 。 这个启动时加载的servlet自动初始化调度程序,并添加第二个servlet,它实际调度作业。 清单1显示了该servlet中的一个典型代码块,该代码块将名为 MyJob 的作业调度为 每十分钟运行一次。
清单1
import static org.quartz.JobBuilder.*;2
import static org.quartz.TriggerBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
...
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler scheduler = schedFact.getScheduler();
JobDetail job = newJob(MyJob.class)
.withIdentity("myJob")
.build();
Trigger trigger = newTrigger()
.withIdentity("myTrigger")
.withSchedule(cronSchedule("0 0/10 * * * ?"))
.forJob("myJob")
.build();
scheduler.scheduleJob(job, trigger);
这只是用于计划一项工作的大量代码(并且仍然缺少该工作的代码),但对于简单的用例和少数变化不大的工作来说,它就可以正常工作。
一旦作业变得更加复杂,就会出现一个问题:作业将不会意识到CDI容器,这意味着它们将无法使用CDI提供的功能。 对于非常简单的作业,这将是没有问题的,但是一旦用户想要 在其中一个作业或使用CDI的服务中使用 简单的 @Inject , 就不会立即削减它 。 为了使CDI提供的所有功能可用于工作,需要某种集成,而实现此目的的最佳方法是编写扩展。
以CDI方式安排作业
让我们看看扩展程序中使用的作业如何:
- 该作业实现 java.lang.Runnable 接口,而不是Quartz提供的Job接口。 这样,代码不是特定于Quartz的,并且我们的扩展程序使用的调度程序可以在以后轻松切换。 调度程序将 根据我们定义 的调度调用 此Runnable 的 run() 方法。
- 在这些作业中可以使用CDI功能,例如注入或拦截器。
- 一个 @Scheduled 注释会告诉我们的扩展,这个类定义了必须安排一个工作,它的时间表是什么(清单2)。
清单2
@Scheduled(“0 0/10 * * * ?”)
public class MyJob implements Runnable {
@Inject
private MyService service;
@Override
public void run() {
service.doSomething();
}
}
这就是传达与前面代码示例相同的信息所需要的全部。
对于此示例,@ Scheduled 是用于设置计划的简单注释:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scheduled {
String value(); //the job’s schedule
}
更改此批注以启用更高级的功能(例如通过动态配置设置计划表而不是在工作源中对其进行硬编码)将是微不足道的。 批注仅需要包含必要的信息,以使我们的扩展程序决定如何配置作业。 现在是时候看看扩展本身。
创建CDI扩展
CDI扩展类实现 javax.enterprise.inject.spi.Extension 接口。 这只是一个标记接口,因此本身未定义任何方法。 容器将在容器生命周期内触发一系列CDI事件,而不是使用在接口中静态定义的一组固定方法。 扩展可以通过为此类事件定义观察者方法来与CDI容器进行交互。 观察者方法是一种非静态的非最终方法,它具有 @Observes 参数注解以及观察到的事件的类型。 它不仅可以通过这种方式从CDI容器中收集信息,而且还可以通过其他方式收集信息。 它也可以修改此信息,甚至可以将新信息发送回容器。 根据经验,也可以在扩展中以编程方式执行通过CDI批注进行的所有操作。 您可以非常轻松地添加或修改范围,拦截器,装饰器, InjectionPoints ,生产者方法, ObserverMethods 等。已定义的容器生命周期事件是:
- 之前的Bean发现
- ProcessAnnotatedType
- ProcessInjectionTarget 和 ProcessProducer
- ProcessBean 和 ProcessObserverMethod
- AfterBean发现
- 部署后验证
- 关机前
本系列的第一篇文章介绍了如何观察自定义CDI事件。 扩展可以以完全相同的方式观察容器系统事件。 若要查找每个具有 @Scheduled 批注的 类, 扩展名应遵守 ProcessAnnotatedType 事件。 在引导过程中,将为容器扫描的每个类触发此系统事件。 容器初始化完成后,可以为找到的任何此类注释创建并启动Quartz作业和Quartz触发器(清单3)。
public void scheduleJob(@Observes ProcessAnnotatedType pat) {
AnnotatedType t = pat.getAnnotatedType();
Scheduled schedule = t.getAnnotation(Scheduled.class);
if (schedule == null) {
//no scheduled job, ignoring this class
return;
}
Class<Runnable> jobClass
= t.getJavaClass().asSubclass(Runnable.class);
if (jobClass == null) {
LOG.error("Can't schedule job " + t);
return;
}
JobDetail job = newJob(CdiJob.class)
.usingJobData(CdiJob.JOB_CLASS_NAME, jobClass.getName())
.build();
Trigger trigger = newTrigger()
.withSchedule(cronSchedule(schedule.value()))
.build();
scheduler.scheduleJob(job, trigger);
}
在扫描过程开始之前触发 的 BeforeBeanDiscovery 事件 的观察者 可以用来初始化调度程序。
public void initScheduler(@Observes BeforeBeanDiscovery event) {
scheduler = StdSchedulerFactory.getDefaultScheduler();
}
调度程序也必须启动。 这可以在 AfterDeploymentValidation 事件观察器中完成。 在容器确认没有部署问题后,将触发该事件,因此所有作业都将在此时进行安排。 另外, BeanManager 可以存储在Extension中以供以后使用(例如,在 CdiJob 类中)。
public void startScheduler(@Observes AfterDeploymentValidation event,
BeanManager bm) {
beanManager = bm;
try {
scheduler.start();
LOG.info(“Started scheduler.”);
} catch (SchedulerException se) {
throw new RuntimeException(se);
}
}
最后, 在 BeforeShutdown 事件 的观察者中调用调度程序 的 shutdown() 方法 。
public void shutdownScheduler(@Observes BeforeShutdown event) {
try {
scheduler.shutdown(true);
} catch (SchedulerException se) {
throw new RuntimeException(se);
}
}
下面的类完成所需的工作,以便将我们定义的Runnable(我们的实际作业)的CDI管理实例作为Quartz作业运行。
它从Quartz配置中提取作业类。
在调用 run() 方法之前,它从 BeanManager 接收实际的CDI管理的实例 。
在 run() 方法返回之后,实例被销毁(清单4)。
我们的扩展程序设置了所有计划的作业以使用此类,因此所有这些操作都以对用户完全透明的方式发生。 请记住,诸如 RequestContext 和 SessionContext之 类的上下文 对于该作业不处于活动状态(既没有活动的请求也没有会话)。 如果注入的服务因为它们依赖于像 RequestScoped EntityManager之 类的bean而需要这些上下文 ,则可以 在作业的 @PostConstruct 和 @PreDestroy 方法中 启动和停止上下文 。 目前,这是特定于容器的,但是Apache DeltaSpike项目正在进行中,以与供应商无关的方式使之成为可能。
public class CdiJob implements org.quartz.Job {
public final static String JOB_CLASS_NAME = “CDI_JOB_CLASS_NAME”;
@Override
public void execute(JobExecutionContext context)
throws JobExecutionException {
JobDataMap jobData = context.getJobDetail().getJobDataMap();
String className = jobData.getString(JOB_CLASS_NAME);
Class<Runnable> jobClass;
try {
jobClass = Class.forName(className).asSubclass(Runnable.class);
} catch (ClassNotFoundException e) {
throw new JobExecutionException(e);
}
BeanManager bm = QuartzExtension.getBeanManager();
Set<Bean<?>> jobBeans = bm.getBeans(jobClass);
Bean<?> jobBean = bm.resolve(jobBeans);
CreationalContext c = bm.createCreationalContext(jobBean);
Runnable job = (Runnable) bm.getReference(jobBean, Runnable.class, c);
try {
job.run();
} finally {
jobBean.destroy(job, c);
}
}
}
摘要
CDI最重要的功能之一就是可以创建扩展。 通过这种机制,可以以可移植的,独立于供应商的方式扩展CDI,以提供新功能(如自定义范围),实现特定于应用程序的功能(如从外部数据库加载配置)或将其他技术集成到类似CDI中。时尚。
作者信息:
Ronald Steininger是维也纳工业大学工业软件研究组(INSO)的高级软件工程师。 在过去的六年中,他与Java一起在富客户端和Web应用程序上工作。 自2009年底以来,他和他的同事在所有项目中都使用CDI和Java EE6。他将业余时间用于研究有关校园管理系统软件体系结构的硕士论文。
Arne Limburg是德国奥尔登堡Open Knowledge GmbH的企业架构师。 他是企业环境中经验丰富的开发人员,架构师和培训师,并且经常参与Android开发。 他经常在这些领域的会议上演讲并举办研讨会。 他还是开源项目的积极参与者,例如,作为Apache OpenWebBeans的提交者以及JPA Security的发起者和项目负责人。
Mark Struberg是一位拥有20多年编程经验的软件架构师。 他自1996年以来一直从事Java的工作,并积极参与Java和Linux领域的开源项目。 他是Apache软件基金会的成员,并担任Apache OpenWebBeans,MyFaces,Maven,OpenJPA,BVal,DeltaSpike和其他项目的PMC和委员会。 他还是积极致力于该规范的CDI专家组成员。 Mark在维也纳工业大学的工业软件研究组(INSO)工作。
本文最初发表在Java Tech Journal:CDI中。 有关CDI性质的更多文章,请在此处下载该问题
翻译自: https://jaxenter.com/tutorial-cdi-extension-programming-104575.html
php cdi