Liferay OSGi注解(Annotation) - 使用手册(译文)

 
原文作者: DAVID H NEBINGER
 
 
当你查看Liferay 7 CE/Liferay DXP源码时,你会看到大量不同的注解。当你除此看到这些注解的时候,可能会感觉到有些无所适从。所以我想写一些引用指导来解释这些注解是什么,并且在你自己的OSGi代码中什么时候使用比较合适。
 
我们开始吧...
 
@Component
 
在OSGi世界中,这种注解用于定义“声明式服务(Declarative Service)”。声明式服务是OSGi中用于动态生成服务。它使容器中的大量的组件(component)间可以互相关联调用。
 
在Component注解中有三个组要属性:
  • immediate - 通常设置为true。用于使组件在部署后直接启动(start),不用等待其他引用或者使用懒加载(lazy startup)。
  • properties - 用设置一系列OSGi属性绑定到组件。这些属性对于当前组件是可见的,但是更重要的是它们对于其他组件也是可见的。这些组件可以帮助配置组件,也可以用于支持组件过滤。
  • service - 定义组件实现的服务。有时这个属性是可选的,但是通常是必要的,因为这样可以使组件要实现服务更明确。service的值通常是一个接口,但是也可以使用一个实体类。
什么时候需要使用@Component?只要需要在OSGi容器中使用要发布一个组件的时候,就可以使用。不是所有的类都需要是组件。只是当你需要将一个插件植入到Liferay环境中的时候才会声明一个组件。(例如,添加一个导航项、定义一个MVC command handler、覆写一个Liferay组件或者为自己的扩展框架写一个插件)。
 
@Reference
 
这个注解是和@Component相对应的。@Reference用于获取OSGi容器中其他的组件并且注入到你自己的组件中。需要注意的是,因为OSGi在做注入的工作,你只能注入OSGi组件类。@Reference注解会被非组件类忽略,并且也会组件的被子类忽略。对于所有的引用注入,都必须在@Component类中声明。
当你定义一个基类时注入了一些其他的服务,并且这个基类没有使用@Component注解(因为定义时还没有完成),@Reference注解会被非组件类忽略,这样的情况下,所有的注入都是无效的。最终,所有的setter方法和@Reference注解都需要复制到实习子类中。这样显得相当冗余。需要注意这一点。
可能@Reference最常见的属性是“unbind”属性了。在setter方法中,经常会看见@Reference(unbine="-")这样的写法。当你为setter方法使用@Reference时,OSGi使用使用这个setter方法作为组件的setter方法。unbind属性指定了当组件释放是,不需要调用任何其他的方法。也就是说,不需要为组件释放做任何的处理。多数情况下,这么做没有问题,服务器启动,OSGi绑定组件,并且使用组件一直到系统关闭。
另外一个属性是“target”,Target用于支持过滤。还记得@Component中的属性吧?通过使用target属性,可以通过特定查询语法指定一类组件。
例如:
@Reference( target = "(javax.portlet.name=" + NotificationsPortletKeys.NOTIFICATIONS + ")", unbind = "-" ) protected void setPanelApp(PanelApp panelApp) { _panelApp = panelApp; }
这段代码希望获取PanelApp组件的实例,但是它却指定了Notification portlet中的PanelApp组件。所以其他portlet中的PanelApp不符合条件,只有Notification portlet中的PanelApp才符合条件。
我们这里讨论的属性有时候会很重要,所以我会详细讨论这些属性。
Cardinality
第一个是cardinality属性,默认值是ReferenceCardinality.MANDITORY,可选值有:OPTIONAL、MULTIPLE和AT_LEAST_ONE。这些值的意义是:
  • MANDITORY - 引用必须在组件启动前可用并且注入。
  • OPTIONAL - 引用在组件启动阶段时不是必要的,在不指定组件的时候也是可以正常工作的。
  • MULTIPLE - 有多种不同的资源可以引用,组件会使用全部的方法。和OPTIONAL相似,在组件启动阶段,引用不是不要的。
  • AT_LEAST_ONE - 多种资源可以满足引用,组件会全部使用。但是在组件启动时,至少有一个资源是可用的。
Multiple选项可以让你通过引用,使用多种符合条件的方法调用。这种方式,只有在使用setter方法中@Reference注解,并且setter方法是向列表或者数组中添加内容的情况下才合理。或者可以使用ServiceTracker替换这种写法,这样你就不用亲自手动去管理列表了。
 
Opional可以使组件在启动时不必须指定某个引用。在有循环引用问题时,这种方法就会帮助你解决问题。例如:A引用B,B引用C,C引用A。如果这三个组件都设置引用为REQUIRED,所有组件都不会启动,因为它们都在等待其他组件满足条件(只有已经启动的组件才能指派给@Reference)。为引用使用optional选项就打破了这种循环,组件就可以正常启动,并且引用问题也解决了。
 
Policy
下一个重要的@Reference属性是policy。Policy的值可以是ReferencePolicy.STATIC (默认)或者ReferencePolicy.DYNAMIC。他们的意义是:
  • STATIC - 只有在已经指派了引用之后,组件才能启动。在启动之后,当出现其他可用引用时,组件也不会理会。
  • DYNAMIC - 无论有没有可用的引用,组件都会启动。并且在有新的可用引用的时候,组件会使用新的引用。
引用策略控制着在组件启动后,当新的引用资源可用的时候的使用策略。总体来说,使用STATIC,组件会忽略新的引用,使用DYNAMIC,当出现新的可用引用时,组件会改变引用。
 
PolicyOption
和policy一同使用的另外一个属性是policyOption。这个属性的值可以是ReferencePolicyOption.RELUCTANT (默认) 或者 ReferencePolicyOption.GREEDY。他们的意思是:
  • RELUCTANT - 对于单一(single)的引用,新的可用引用会被忽略。对于多项(multiple)引用,新的可用引用出现的时候会被绑定。
  • GREEDY - 只要新的可用引用出现的时候,组件就会绑定这些引用。.
 
这些选项组合起来有很多种方式。
首先是默认的,ReferenceCardinality.MANDITORY + ReferencePolicy.STATIC + ReferencePolicyOption.RELUCTANT。
这样组合是组件一定要一个引用可用才能启动,并且忽略新的可用引用。这样的默认组和确保了组件的稳定性。
另外一种组合是ReferenceCardinality.OPTIONAL/MULTIPLE + ReferencePolicy.DYNAMIC + ReferencePolicyOption.GREEDY。
在这样的配置中,组件在缺少服务引用的时候也会正常使用,但是组件在使用过程中允许新的引用和引用变更,在新的引用可用时,组件会主动的绑定引用服务。
也会有其他的组合,但是在设置的时候需要了解这样的设置对组件的影响。毕竟在你声明引用的时候你要了解如何是组件工作。你需要考虑到组件在没有引用的时候如何响应,如果服务不可用的时候组,件是否需要停止工作。在你考虑问题的时候不信要考虑理想情况,也要考虑到特殊情况,例如重新部署、卸载、服务差距和组件的容错性。如果这些问题都能解决,那么组件就是比较理想的。
最后,回想一下,什么时候需要使用@Reference注解?就是你需要从OSGi环境中向你的组件注入服务的时候。这些服务可以是你自己的服务,也可以是OSGi容器中其他模块的服务。记住,@Reference只能在OSGi组件中使用,但是你可以使你的类通过@Component注解变成组件。
 
@BeanReference
这个是一个Liferay注解,用于向Spring bean中注入一个Liferay core中的引用。
 
@ServiceReference
这是一个Liferay注解,用于向组件中注入一个引用,这个引用是来自于Spring Extender module bean。
 
稍等!三种引用注解?我需要用哪一种?
我们来解释一下这三种引用注解。根据我的经验来判断,多数情况下,你需要使用@Reference注解。Liferay core Spring beans和Spring Extender module beans同样也是暴露在OSGi容器中的,所以@Reference在多数情况下是没问题的。
如果在使用@Reference的时候,服务没有被注入,并且返回null,这就意味着你可能需要使用其他的引用注解了。选择哪种注解原则不难:如果bean是在Liferay core中,就是用@BeanReference;反之,但是如果是在Spring Extender module中,就是用@ServiceReference注解。注意,无论是bean或者service注解都会要求你的组件使用Spring Extender。如何引用依赖,参考任何使用ServiceBuilder的服务模块,查看build.gradle和bnd.bnd,用同样的方法修改你自己的模块。
 
@Activate
@Activate注解是对应Spring的InitializingBean接口。它声明了组件在启动是要调用的方法。在Liferay源码中,你会看到它会被三个主要的方法使用:
@Activate 
protected void activate() { 
    ... 
} 

@Activate 
protected void activate(Map<String, Object> properties) {
     ... 
} 

@Activate 
protected void activate(BundleContext bundleContext, Map<String, Object> properties) {
    ... 
} 
也有其他的方法使用这个注解,只要在源码中搜索@Activate就可以找到很多不同的用例了。除了无参数的activate方法,其他的都依赖于OSGi所注入的值。注意properties映射实际是从你的OSGi的Configuration Admin服务取得的。
 
什么时候需要使用@Activate呢?只要你需要在组件启动后、被使用前需要做一些初始化操作的时候就可以使用。例如,我曾经设置Quartz计划任务,验证数据库实体的时候使用过。
 
@Deactivate
@Dactivate注解是和@Ativate注解相反的,它定义了组件在停用是需要调用的方法。
 
@Modified
@Modified注解定义了当组件被改变时需要调用的方法。特别是在标记@Reference了的方法被改变的时候。在Liferay源码中,@Modified注解经常是和标记@Activate的方法绑定在一起的,这样同一个方法就能同时处理启动和改变了。
 
@ProviderType
@ProviderType是BND中使用的,通常是在考虑将head包含的复杂情况时使用。长话短说,@ProviderType是被BND使用,用来定义在实现类中可以指派的OSGi manifest的版本。并且它尝试限制组件版本范围的使用。
 
它的重点是用来确定接口的改变,通过为实现类限制的版本,会强制实现类来根据接口来进行更新。
 
什么时候使用@ProviderType呢?其实,并不一定需要使用。你可以看见,在ServiceBuilder生成的代码里面已经被使用了。我在这里提到这个注解的原因并不是因为你一定要使用它,而是满足你的好奇心。
 
@ImplementationClassName
这个是Liferay中为ServiceBuilder实体接口使用的注解。它定义了在service module中用来实现接口的类。
这个你并以需要使用,只是为了让你知道它是做什么的的。
 
@Transactional
这是另一个ServiceBuilder服务接口使用的注解。它定义了服务方法的事务需求。
这个方法在你开发的时候也用不到。
 
@Indexable
@Indexable用来修饰会使搜索索引更新你的方法,特别是ServiceBuilder中的add、update和delete实体的方法。
你可以为你的实现增、删、改的方法使用@Indexable注解。如果实体关联com.liferay.portal.kernel.search.Indexer 相关的实现方法,那么实体就可以被索引。
 
@SystemEvent
@SystemEvent注解是在ServiceBuilder生成的代码中被可能生成系统事件的方法使用的。系统事件和staging、LAR和导入导出处理等相关。例如,当一个web conteng被删除,这个就会生成一个SystemEvent记录。在staging环境中,当“Publish to Live”原型是,删除SystemEvent确保了相关联的web content在live站点中也被删除。
需要什么时候使用@SystemEvent注解呢?说实话,我也不知道。在我10年的经验中,我从来没有需要生成SystemEvent记录的时候,也从来没更改过Liferay发布和LAR处理。如果任何人有相关经验使用@SystemEvent注解的话,我很愿意侧耳恭听。
 
@Meta
OSGi有一个基于XML的系统用于为Configuration Admin定义配置详细信息。BND项目的通过使用@Meta注解,可以使BND根据配置接口中使用这个注解的方法生成配置文件。
 
重要提示:一定要在bnd.bnd文件中添加下行代码才能使用@Meta注解:
-metatype: *
如果没添加的话,在生成XML配置文件的时候,是不会用到@Meta注解的。
 
@Meta.OCD
这个注解用于“Object Class Definition”方面的配置详细信息。这个注解用于为在接口层为类的定义提供id,名字和语言等详细信息。
什么时候使用这个注解呢?当为组件定义一个Configuration Admin接口时,并且这个接口是在Control Panel -> System Setting有一个配置选项的时候使用。
 
注意@Meta.OCD属性包含了本地化的设置。这样就可以使用resource bundle来翻译配置名称、详细配置信息和@ExtendedObjectClassDefinition分类了。
 
@Meta.AD
这个注解用于“Attribute Definition”,用于定义配置中表单项层面的定义。注解用于为表单项提供ID、名称、说明、默认值和其他详细信息。
 
什么时候使用这个注解?就是在需要为System Setting中的配置提供更多关于配置项目细节的时候使用。
 
@ExtendedObjectClassDefinition
这个是Liferay的注解,用于定义配置的类型(就是在System Setting中上面显示的那些分类)和配置的范围。
 
范围可以是下面的值:
  • SYSTEM - 整个系统中的全局配置,在生个系统中只有一个配置。
  • COMPANY - Company级,每个portal实例中只可以有一个配置。
  • GROUP - Group(站点)级,每个站点中只可以有一个配置。
  • PORTLET_INSTANCE - 这个和portlet实例参数类似,每个portlet实例中都会有一分配置。
什么时候要用到这个注解呢?每次用到@Meta.OCD注解的时候,都需要用到一次@ExtendedObjectClassDefinition注解来定义配置的分类。
 
@OSGiBeanProperties
这是一个Liferay的注解,用来定义OSGi组件属性,这些属性用来注册Spring bean为一个OSGi组件。你会在ServiceBuilder模块中见到这个注解被使用,将Spring bean暴露给OSGi容器。记着,ServiceBuilder依然是基于Spring的(SpringExtender),这个注解就是用来将Spring bean注册为OSGi组件用的。
 
什么时候会用到这个注解呢?如果你在你自己的模块中使用了Spring Extender来使用Spring,并且你想将Spring bean在OSGi容器中暴露为组件(这么做可以使其他模块使用这个Spring bean),这个时候你就需要使用这个注解了。
 
我没有在这里讨论更多的细节,因为这个注解可以在javadoc中被大量的找到。查看这个例子:  https://github.com/liferay/liferay-portal/blob/master/portal-kernel/src/com/liferay/portal/kernel/spring/osgi/OSGiBeanProperties.java
 
总结
 
这就是我在Liferay 7 CE / Liferay DXP中见到的所有注解了。希望这些信息可以帮到你的Liferay开发。如果你发现本文没有涉及的注解或者希望了解更多细节,请随时向原文博客或者在这里提问。