论坛首页 Java企业应用论坛

(C3)Tapestry IoC:Tapestry IoC Services

浏览 2272 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-05-24  
本人翻译目的是用来学习Tapestry5的,共享出来希望大家批评指正。计划持续翻译。
chinajavawolf  
Tapestry IoC 服务
 
服务由两个主要部分组成:一个服务的接口和一个服务的实现。
 
这个服务接口是如何服务的将通过注册库的其余部分被表现。因为获得分发的通常是一个代理,你不能期盼强制转换一个服务对象到实现类(你将看到的是ClassCastException)。换句话说,你应该关心确保你的服务接口是完善的,因为Tapestry IoC有效的将你从后门隔离,例如强制转换(cast)后门。
 
Tapestry不知道如何实例化和配置你的服务;这要靠你在服务构建器方法内提供的代码实现。
 
  1. package org.example.myapp.services;   
  2.     
  3. public class MyAppModule   
  4. {   
  5.  public static Indexer build()   
  6.  {   
  7.     return new IndexerImpl();   
  8.  }   
  9. }   
 
这里这个服务接口是Indexer(它大概在org.example.myapp.services包内,因为这里没有引入(import)它)。Tapestry IoC不知道IndexerImpl类(Indexer服务的实现),但它知道build()方法。
 
这是Tapestry IoC的主要改进之一:我们不用尝试在XML中描述或标住所有不同的可能方法来创建服务;那些事情最好在Java代码中明确。一个简单的情况(如这里) ,我们将很难做到外部配置(XMLJava标注中)比“new IndexerImpl()”更简短。
 
对于更复杂和更现实的情况,比如通过构造器注入依赖,或做更重要的工作(比如注册最新创建的服务给通过其他服务发布的事件),Java代码是最直接简单,灵活,可扩展和易读的方法。
 
自动构建(Autobuilding
 
Tapestry IoC也可以自动构建你的服务。自动构建是一个使用容器注册服务的替代方法
 
每个模块可以有一个选择,静态bind()方法被传递一个ServiceBinder。服务可以通过“绑定”一个服务实现的服务接口注册给容器:
 
  1. package org.example.myapp.services;    
  2. import org.apache.tapestry.ioc.ServiceBinder;   
  3.     
  4. public class MyAppModule   
  5. {   
  6.  public static void bind(ServiceBinder binder)   
  7.  {   
  8.     binder.bind(Indexer.class, IndexerImpl.class);   
  9.  }   
  10. }   
 
你可以反复调用bind()方法,以注册更多的服务。
 
你可能会问,“哪个更好,一个构建器方法对每个服务,或者bind()方法对这个模块?”对于那些只是使用一个简单的依赖实例化一个实例的简单服务,binding要好于building。
 
很多时候,构建一个服务不只是实例化一个类。通常新的服务(例如)将被作为一个监听器注册给一些其他的服务。在其他场合,服务的实现在运行时被产生。这服务构建器方法最有用的地方。
 
在框架的演变中,服务构建器方法是最先应用的,自动构建后来被附加上来,它的灵感来自精练的GuiceIoC容器。
 
服务Ids
 
每个服务都有一个唯一的服务id.
 
当使用一个服务构建器方法时,这个服务id是这个服务接口的简单的名字。
 
这可以通过追加服务id给方法名来替换,在“build”后面,例如:
 
  1. public static Indexer buildFileSystemIndexer(@InjectService("FileSystem") FileSystem fileSystem)   
  2.  {   
  3.      . . .   
  4.  }  
 
这里,服务id是“FileSystemIndexer”不是“Indexer”。
 
对于自动构建服务,当服务被绑定时,服务id可以被指定:
 
  1. binder.bind(Indexer.class, IndexerImpl.class).withId("FileSystemIndexer");  
 
注入依赖
 
相当不太可能的是你的服务将能够在完全真空的情况下运转。它将有其他的依赖。
 
依赖通过三种方法中的一种被提供给服务:
1.       作为服务构建器方法的参数
2.       作为服务实现类的构造器的参数(适用于自动构建服务)
3.       作为服务模块构建器的构造器参数(缓存在实例变量中)。
 
例如,让我们假定当Indexer执行的时候需要控制一个JobScheduler,并且FileSystem访问文件和存储indexes。
 
  1. public static Indexer build(JobScheduler scheduler, FileSystem fileSystem)   
  2. {   
  3.    IndexerImpl indexer = new IndexerImpl(fileSystem);   
  4.         
  5.    scheduler.scheduleDailyJob(indexer);   
  6.         
  7.    return indexer;   
  8. }  
 
这里我们已经标注了服务构建器方法的参数以确定什么服务注入给那个参数。
 
这是一个当你想要使用服务构建器方法而非仅仅绑定实现类的服务接口时的例子:因为我们想要做额外的事,在这种情况下,使用scheduler注册新的indexer服务。
 
注意我们不调用那些服务构建器方法。。。我们仅仅是“告知”我们需要这个指定的服务。Tapestry IoC将提供必需的代理,并且当我们开始在那些代理上调用方法时,将确保整个服务,包括它的拦截器和他的依赖,准备运行。此外,这发生在线程安全方式内。
 
如果有不止一个实现了JobScheduler接口或FileSystem接口的服务时会放生什么?你将看到一个运行时异常,因为Tapestry不能够解决它作为一个单独的服务。在这点上,必需消除关联在这个服务接口和一个服务间的歧义。一个方法是使用InjectService 标注:
  1.  public static Indexer build(@InjectService("JobScheduler")   
  2. JobScheduler scheduler,   
  3. @InjectService("FileSystem")    
  4. FileSystem fileSystem)   
  5.  {   
  6.     IndexerImpl indexer = new IndexerImpl(fileSystem);         
  7.     scheduler.scheduleDailyJob(indexer);         
  8.    return indexer;   
  9.  }  
 
如果你发现你自己注入相同的依赖在多个服务构建器(或服务修饰器)方法内,你可以缓存依赖注入在你的模块中,通过定义一个构造器。这将在你的模块中减少副本。
 
为Autobuilt服务注入依赖
 
由于autobuild服务,没有服务构建器方法在哪个指定的注入内。
 
改为注入发生在实现类的构造器上。
  1. package org.example.myapp.services;   
  2.     
  3. import org.apache.tapestry.ioc.annotations.InjectService;   
  4.     
  5. public class IndexerImpl implements Indexer   
  6. {   
  7.  private final FileSystem _fileSystem;   
  8.     
  9.  public IndexerImpl(@InjectService("FileSystem") FileSystem fileSystem)   
  10.  {   
  11.     _fileSystem = fileSystem;   
  12.  }   
  13. }  
如果类有多个构造器,有最多参数的构造器将被调用。
 
注意我们如何为我们的依赖使用final字段域。这通常是一个好主意。这些服务将常常执行在一个多线程环境内,比如web应用,并且使用使用构造器内的final字段域确保这个字段域将依照Java内存模式(Java Memory Model)被正确公布。
 
在传递给构造器中的服务(比如前面的 JobScheduler)内传递this,不是一个好主意。
 
  1. package org.example.myapp.services;   
  2.     
  3. import org.apache.tapestry.ioc.annotations.InjectService;   
  4.     
  5. public class IndexerImpl implements Indexer   
  6. {   
  7.  private final FileSystem _fileSystem;   
  8.     
  9.  public IndexerImpl(@InjectService("FileSystem") FileSystem fileSystem,   
  10.     
  11.  @InjectService("JobScheduler") JobScheduler scheduler)   
  12.  {   
  13.     _fileSystem = fileSystem;   
  14.        
  15.     scheduler.scheduleDailyJob(this); // Bad Idea   
  16.  }   
  17. }   
 
理解为什么这是一个坏主义要陷入在Java内存模式内部漫长曲折的细节内。简单的说就是其他线程应该终止调用IndexerImpl实例上的方法,并且它的字段域(即使他们是final的,即使他们看起来已经被设置)应该不被初始化。
 
定义服务范围
 
当服务被实现被实例话后,每个服务都有一个可控的生命周期。在生命周期内有两个构建:“singleton”和“perthread”,但可以被添加更多的。
 
服务生命周期通过附加在一个构建器方法上的@Scope标注被指定。当这个标住没有出现时,默认的范围,“singleton”被使用。
1.         单例(singleton)
大多数服务使用默认的范围,“singleton”。当这个服务被首次引用时,使用这个范围的代理被创建。通过引用,我们预定任何通过名字被请求的服务内的情形,比如用@InjectService标注在服务构建器方法上或通过使用来自外面容器的Registry API。
 
在任何情况下,当服务接口上的方法被调用时服务代理将只创建服务实现。直到后来这个服务被认为是“有效的”。当第一个方法被调用,服务构建器方法被调用,那么任何服务的修饰发生。这个构造发生,称为“实现(realization)”,只发生一次。
 
你应该明白当你写服务的时候,你的代码必须是线程安全的;你定义的任何服务都可以被多个线程同时调用。实际上这是非常少见的问题,因为大多数服务需要输入,使用本地变量,然后调用方法在其他服务上,没有使用非final实例变量。在服务实现中的少数实例变量通常引用其他Tapestry IoC服务。
 
2.单线程(perthread
单线程服务范围的存在主要是为了帮助多线程servlet应用,尽管它有其他的应用。
 
使用单线程,服务代理将委派给一个与当前线程组合在一起的本地服务实例。在同一个代理上调用方法,在两个不同服务实例上的方法最终将被调用,每个都被保留在它们自己的线程内。
 
当一个服务需要保留请求的特定状态时,这是有用的,比如从HttpServletRequest中萃取信息(在web应用中)。默认的单例模式将不能在这样的多线程环境下工作。使用单线程在选择的服务上允许状态被隔离对那些服务。因为派发发生在代理内,你可以视这个服务为全局的,象任何其他的一样。
 
你将看到你的服务构建器方法不只一次被调用。它被调用在单线程服务被使用的每个线程里。
 
在请求的结束时,注册库的cleanupThread()方法被调用;它将为当前线程丢弃任何单线程服务实现。
 
警告:Tapestry IoC中的一个通用技术是用一个服务构建器方法注册一个核心服务实现作为一个事件监听器和一些事件中心服务(event hub service)。使用非单例对象,这可能引起很多问题;事件中心将保留一个对单线程实例的引用,即使在单线程实例已经被清除后(通过内部代理丢弃)。简单的输入,这是一个避免的方式。很大程度上,单线程服务应该是具体数据的简单持有者对于线程或请求,不应该与注册库中的其他服务有过于复杂的关系。
 
定义自动构建服务范围
定义一个自动构建服务范围可以有两个选择。
 
服务实现类可以包括@Scope标注。这通常是指定范围的首选方法。
 
另外,可以在绑定服务时指定范围。
  1. bind(MyServiceInterface.class, MyServiceImpl.class).scope("perthread");  
 
渴望加载服务
 
服务通常只是在需要的时候被创建(上面讨论的每个范围)。
 
这是可以被轻微扭转的,通过添加一个 EagerLoad 标注给服务构建器方法,Tapestry将在注册库第一次被创建时实例化这个服务。
 
这将引起服务构建器方法被调用,任何服务修饰器方法也被调用。
 
这个特性被用在一个服务管理一个资源时,比如线程,一旦应用启动就需要被创建。另一个通用的例子是一个服务用来通过第二个服务监听事件生产;第一个服务需要被创建然后开始监听,在任何它的服务方法被调用之前(这通常会触发这个服务的实例)。
 
许多服务可以用@EagerLoad标注; 服务被创造的命令不被定义。
 
对于单线程生命周期,服务构建器方法将不被调用(这不会放生直到一个服务方法被调用),但是对于这个服务的修饰器将被调用。
 
渴望加载自动构建服务
 
当使用服务范围时,有两个选择可以标识一个自动构建服务应该被渴望加载。
 
服务实现类可以包括@EagerLoad标注。
 
你也可以在绑定服务时明确指定渴望加载。
 
  1. bind(MyServiceInterface.class, MyServiceImpl.class).eagerLoad();  
 
注入资源
 
除了注入服务外,Tapestry将切断参数类型以允许其他内容被注入。
  •   java.lang.String: 服务的唯一id
  • org.apache.commons.logging.Log:对哪个服务日志可以发生
  •  java.lang.Class: 被要构造的服务实现的服务接口
  • ServiceResources: 访问其他服务
 
这些情况不需要标注
 
同样参考服务配置为可被注入的资源的附加特殊情形。
例如:
 
  1. public static Indexer build(String serviceId, Log serviceLog,    
  2.     JobScheduler scheduler, FileSystem fileSystem)   
  3. {   
  4.    IndexerImpl indexer = new IndexerImpl(serviceLog, fileSystem);   
  5.         
  6.    scheduler.scheduleDailyJob(serviceId, indexer);   
  7.   
  8.    return indexer;   
  9. }   
参数顺序是完全无关的。他们可以放在你喜欢的最前面或最后面或者是分散。
 
当你想要在运行时确认一个服务依赖名时,注入在ServiceResources内是唾手可得的。然而,通常情况(服务依赖id是在编译那知道的),它更容易使用@InjectService 标注。
 
Log的名字(当为服务配置logging设置时用到)由模块名和服务id通过圆点分隔组合而成,就像
“org.example.myapp.MyModule.Indexer”。
 
此外,ServiceResources包括一个autobuild()方法以允许你更容易触发类的构造,包括依赖。因而前面的例子可以被重写成这样:
  1. public static Indexer build(ServiceResources resources, JobScheduler jobScheduler)   
  2. {   
  3.    IndexerImpl indexer = resources.autobuild(IndexerImpl.class);   
  4.         
  5.    scheduler.scheduleDailyJob(resources.getServiceId(), indexer);   
  6.   
  7.    return indexer;   
  8. }  
 
使用与自动构建服务完全相同的方法工作,除了服务实现构造器的参数被照顾外,要优于服务构建器方法参数。
 
@InjectService标注将优先接管这些资源。
 
如果@InjectService标注没有出现,并且参数类型不能完全匹配一个资源类型,那么对象注入发生。对象注入将找到正确的对象来注入基于许多(可扩展的)因素,包括参数类型和所有在参数上的附加标注。
 
有时,你将在资源类型和对象注入之间有所冲突。例如, 下面就不会按照期望的工作:
 
  1. public static Indexer build(String serviceId, Log serviceLog,    
  2.     JobScheduler scheduler, FileSystem fileSystem,   
  3.     @Value("${index-alerts-email}")   
  4.     String alertEmail)   
  5. {   
  6.    IndexerImpl indexer = new IndexerImpl(serviceLog, fileSystem, alertEmail);   
  7.         
  8.    scheduler.scheduleDailyJob(serviceId, indexer);   
  9.   
  10.    return indexer;   
  11. }  
 
不能工作的原因是String类型总是获得服务id作为一个资源(作为serviceId参数)。为了获得它来工作,我们需要关掉这个对于alertEmail参数的资源注入。Inject标注作了什么:
  1. public static Indexer build(String serviceId, Log serviceLog,    
  2.     JobScheduler scheduler, FileSystem fileSystem,   
  3.     @Inject @Value("${index-alerts-email}")   
  4.     String alertEmail)   
  5. {   
  6.    IndexerImpl indexer = new IndexerImpl(serviceLog, fileSystem, alertEmail);   
  7.         
  8.    scheduler.scheduleDailyJob(serviceId, indexer);   
  9.   
  10.    return indexer;   
  11. }  
 
这里,alertEmail参数将收到配置的 alerts email(更多的语法查看符号(symbols)文档)而不是服务id.。
 
内建服务(Builtin Service)
 
Tapestry IOC 模块内的几个服务时“内建”的;在TapestryIOCModule类内没有服务构建器方法。
ServiceId
Service Interface
ClassFactory
LogSource
RegistryShutdownHub
ThreadCleanupHub
 
参考这些服务的每一个JavaDoc以确定在什么情况下你将需要使用他们。
 
相互依赖服务
Tapestry IoC的proxy-based方法的好处之一是即时生产实例以自动支持互相依赖服务。例如,假设Indexer和FileSystem需要彼此直接对话。通常,这将引起“先有鸡还是先有蛋”问题:即先创建哪个?
With Tapestry IoC, this is not even considered a special case:
使用Tapestry IoC,这甚至不被认为是一个特殊情况:
  1. public static Indexer build(JobScheduler scheduler, FileSystem fileSystem)   
  2. {   
  3.    IndexerImpl indexer = new IndexerImpl(fileSystem);   
  4.   
  5.    scheduler.scheduleDailyJob(indexer);   
  6.   
  7.    return indexer;   
  8. }   
  9.       
  10. public static build(Indexer indexer)   
  11. {   
  12.    return new FileSystemImpl(indexer);   
  13. }   
 
这里,Indexer和FileSystem是相互依赖的。最终,他们中的一个或其他将被创建。。。让我们假定它是FileSystem。buildFileSystem()构建器方法将被调用,并且对于Indexer的代理将被传递进去。FileSystemImpl构造器内,Indexer服务的一个方法将被调用,在那个点上,builderIndexer()方法被调用。它将收到代理给FileSystem服务。
 
如果顺序反过来,像Indexer在FileSystem前被创建,所有的内容仍然同样工作。
 
这方法可能是非常强大的:我已经(HLS)用它打破无法测试的代码片为两个互相依赖二等份, 每个都可是可被测试的个体。
 
异常对于这一规则是一个在构造期间依赖它本身的服务。 这能够发生在当构建的服务尝试在被建造的服务上启动一个方法的时候(间接地, 透过其他的服务)时。当服务实现的构造器在被获准进入它的服务依赖上启动方法的时候,这能发生, 或当服务构建器方法本身做一样事的时候。 这实际上是一个非常少有的情况和困难的举例说明。
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics