week 7 CDI services
- .Decorators装饰器综述
拦截器是一种强大的方法在应用程序捕捉运行方法和解耦。拦截器可以拦截任何java类型的调用.
这使得拦截器适合解决事务管理,安全性,以及日记记录.
本质上说,拦截器并不知道他们截获的实际语义事件.因此,拦截器并不是很适合和系统的业务挂钩.
而本章的装饰器,则又不一样.
装饰器只截取调用某个Java接口,因此获知这个接口的所有语义连接。
decorator直接实现与业务语义操作,这也意味着装饰没有拦截器的通用性。
拦截器和修饰符,尽管在很多方面相似,是互补的。但decorator无法解决技术问题,横跨许多不同的类型。
假设我们有一个接口,代表账户:
public interface Account { public BigDecimal getBalance(); public User getOwner(); public void withdraw(BigDecimal amount); public void deposit(BigDecimal amount); }
几种不同的Bean在我们系统实现账户接口。
然而,我们有一个强制要求:任何类型的账户,交易必须由系统日志进行记录.
这就是装饰器的一个工作.
用@Decorator标注一个bean(甚至可能是一个抽象类),这样就表明此类是装饰器.
@Decorator public abstract class LargeTransactionDecorator implements Account { ... }
装饰器的装修类型实现方法,可以让他拦截他想要拦截的.
@Decorator public abstract class LargeTransactionDecorator implements Account { @Inject @Delegate @Any Account account; @PersistenceContext EntityManager em; public void withdraw(BigDecimal amount) { ... } public void deposit(BigDecimal amount); ... } }
需要注意的是,一个装饰器可能是一个抽象类. 因此,某些情况下你可能不需要去实现方法.
2.Delegate object(委托对象)
decorator有特殊的注射点,称为委托注入点(delegate injection point),
其必须有一个delegate injection point,可以是一个构造函数参数,初始化方法参数或injected field.
@Decorator public abstract class LargeTransactionDecorator implements Account { @Inject @Delegate @Any Account account; ... }
像上面这段代码,装饰器将绑定到所有实现了Account的Bean上.
如果是下面这段代码,@Foreign是我们自定义.
那么装饰器将绑定到实现了Account的Bean并且qualifiers是@Foreign的Bean上.
@Decorator public abstract class LargeTransactionDecorator implements Account { @Inject @Delegate @Foreign Account account; ... }
decorator可能调用委托对象,和拦截器调用InvocationContext.proceed() 有大致有相同的结果.但主要的区别在于装饰可以委托对象上调用任何业务方法。
@Decorator public abstract class LargeTransactionDecorator implements Account { @Inject @Delegate @Any Account account; @PersistenceContext EntityManager em; public void withdraw(BigDecimal amount) { account.withdraw(amount); if ( amount.compareTo(LARGE_AMOUNT)>0 ) { em.persist( new LoggedWithdrawl(amount) ); } } public void deposit(BigDecimal amount); account.deposit(amount); if ( amount.compareTo(LARGE_AMOUNT)>0 ) { em.persist( new LoggedDeposit(amount) ); } } }
3.Enabling decorators(启用装饰器)
默认情况下,所有装饰器都是禁用的.推荐用bean.xml进行开启.bean.xml是第一优先的.其次才是@Priority.
CDI 1.1以后的decorator可以使用@Priority开启。@Priority定义了装饰器和拦截器的优先顺序,但还是没bean.xml里直观.
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"> <decorators> <class>org.mycompany.myapp.LargeTransactionDecorator</class> </decorators> </beans>
注意:不要即在bean.xml配置又写@Priority.可能会出一些奇怪的问题.根本上,同时用这两种方式就是错误的.
- Interceptors(拦截器)
1.拦截器综述
拦截器的功能是定义在Java拦截器规范。
拦截器规范定义了三种拦截点:
- 业务方法拦截,
- 生命周期回调侦听,
- 超时拦截(EJB)方法。
在容器的生命周期中进行拦截
public class DependencyInjectionInterceptor { @PostConstruct public void injectDependencies(InvocationContext ctx) { ... } }
EJB超时时使用的拦截器
public class TimeoutInterceptor { @AroundTimeout public Object manageTransaction(InvocationContext ctx) throws Exception { ... } }
在业务上,对某一个Bean的方法进行拦截
public class TransactionInterceptor { @AroundInvoke public Object manageTransaction(InvocationContext ctx) throws Exception { ... } }
@AroundInvoke注释指定了要用作拦截器的方法,拦截器方法与被拦截的业务方法执行同一个java调用堆栈、同一个事务和安全上下文中。用@AroundInvoke注释指定的方法必须遵守以下格式:public Object XXX(javax.interceptor.InvocationContext ctx) throws Exception
下面是javax.interceptor.InvocationContext封装了客户端所调用业务方法的一些信息。
- getTarget() 指向被调用的bean实例
- getMethod() 指向被拦截的业务方法getParameters
-
package javax.interceptor; public interface InvocationContext{ public Object getTarget(); public Method getMethod(); public Ojbect[] getParameters(); public void setParameters(Object[] newArgs); public java.util.Map<String, Ojbect> getContextData(); public Object proceed() throws Exception; }
- setParameters() 设置被拦截业务方法的参数
- getContextData() 返回一个Map对象,它在整个方法调用期间都可以被访问到。位于同一个方法调用内的不同拦截器之间可以利用它来传递上下文相关的数据。
示例:
//被拦截的方法 @Interceptors(HelloInterceptor.class) public class HelloChinaBean { public String SayHello(String name) { return name +"Hello World."; } } //拦截器定义 public class HelloInterceptor { @AroundInvoke public Object log(InvocationContext ctx) throws Exception { try{ if (ctx.getMethod().getName().equals("SayHello")){ System.out.println("Holle World!!!" ); } return ctx.proceed(); }catch (Exception e) { throw e; } } }
@InterceptorBinding @Target({METHOD, TYPE}) @Retention(RUNTIME) public @interface Transactional {}
2.拦截器绑定(Interceptor bindings)
假设我们想要申明一些bean的事务。我们先要的是一个拦截器绑定类型来指定哪些bean我们要申明.
首先定义一个注解
现在我们可以很容易地指定类ShoppingCart是事务性对象:
@Transactional
public class ShoppingCart { ... }
或者我们可以指定一个
@Transactional @Interceptor public class TransactionInterceptor { @AroundInvoke public Object manageTransaction(InvocationContext ctx) throws Exception { ... } }
器可以利用依赖注入:
@Transactional @Interceptor public class TransactionInterceptor { @Resource UserTransaction transaction; @AroundInvoke public Object manageTransaction(InvocationContext ctx) throws Exception { ... } }
多个拦截器可以使用相同的拦截器绑定类型。
3.启用拦截器(Enabling interceptors) 默认情况下,所有拦截器被禁用.要使用拦截器.需要在bean.xml中进行配置,以启用.从CDI 1.1起拦截器可以使用@Priority注释为整个应用程序启用。
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"> <interceptors> <class>org.mycompany.myapp.TransactionInterceptor</class> </interceptors> </beans>
这样有2个好处:
- 拦截器比较重要,在XML中确保其确定性行为
- 它让我们在部署时启用或禁用拦截器类。
当然也可以配置启用多个拦截器
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"> <interceptors> <class>org.mycompany.myapp.SecurityInterceptor</class> <class>org.mycompany.myapp.TransactionInterceptor</class> </interceptors> </beans>
拦截器毕竟比较重要,不推荐使用@Priority启用.
在CDI中,XML配置的优先级高于@Priority.
关于@Priority可以参考下列:
public static class Interceptor.Priority
extends Object
Priorities that define the order in which interceptors are invoked. These values should be used with the Priority annotation.
Interceptors defined by platform specifications should have priority values in the range PLATFORM_BEFORE up until LIBRARY_BEFORE, or starting at PLATFORM_AFTER.
Interceptors defined by extension libraries should have priority values in the range LIBRARY_BEFORE up until APPLICATION, or LIBRARY_AFTER up until PLATFORM_AFTER.
Interceptors defined by applications should have priority values in the range APPLICATION up until LIBRARY_AFTER.
An interceptor that must be invoked before or after another defined interceptor can choose any appropriate value.
Interceptors with smaller priority values are called first. If more than one interceptor has the same priority, the relative order of these interceptor is undefined.
For example, an extension library might define an interceptor like this:
@Priority(Interceptor.Priority.LIBRARY_BEFORE+10)
@Interceptor
public class ValidationInterceptor { ... }
4.Interceptor bindings with members(拦截器注解属性)
假设我们想要添加一些额外的信息给我们的@transactional注解:
@InterceptorBinding @Target({METHOD, TYPE}) @Retention(RUNTIME) public @interface Transactional { boolean requiresNew() default false; }
CDI将使用requiresNew的值选择两个不同的拦截器,TransactionInterceptor和RequiresNewTransactionInterceptor
下面是requiresNew为true的拦截器
@Transactional(requiresNew = true) @Interceptor public class RequiresNewTransactionInterceptor { @AroundInvoke public Object manageTransaction(InvocationContext ctx) throws Exception { ... } }
如下使用:
@Transactional(requiresNew = true) public class ShoppingCart { ... }
但是如果我们只有一个拦截器,我们希望容器拦截器绑定时忽略requiresNew的值,也许这些信息只用于拦截器实现。我们可以使用@Nonbinding注释:
@InterceptorBinding @Target({METHOD, TYPE}) @Retention(RUNTIME) public @interface Secure { @Nonbinding String[] rolesAllowed() default {}; }
5.Multiple interceptor binding annotations(多重拦截器绑定注解)
通常我们使用拦截器绑定的组合类型绑定多个拦截器bean。例如,下面的声明将用于绑定TransactionInterceptor和SecurityInterceptor这2个拦截器到ShoppingCart.
@Secure(rolesAllowed="admin") @Transactional public class ShoppingCart { ... }
然而,在非常复杂的情况下,一个拦截器本身可能指定拦截器绑定类型:
@Transactional @Secure @Interceptor public class TransactionalSecureInterceptor { ... }
那么这个拦截器可以绑定到checkout() 方法,以下任何组合都可使用:
public class ShoppingCart { @Transactional @Secure public void checkout() { ... } } @Secure public class ShoppingCart { @Transactional public void checkout() { ... } } @Transactional public class ShoppingCart { @Secure public void checkout() { ... } } @Transactional @Secure public class ShoppingCart { public void checkout() { ... } }
6. Interceptor binding type inheritance(拦截器绑定类型继承)
Java语言支持注解的一个限制就是缺乏注解继承.注解应该重用内置已有的.就如同下面这段代码表达的意思
//实际没这写法
public @interface Action extends Transactional, Secure { ... }
幸运的是,CDI围绕Java没有的这个特性开展了一些工作.
我们会标注一个拦截器绑定类型,其有其他拦截器的绑定类型,(称为元注解)
表述起来有点费劲,就如同下面代码这样.
@Transactional @Secure @InterceptorBinding @Target(TYPE) @Retention(RUNTIME) public @interface Action { ... }
现在任何Bean绑定 Action这个注解 ,其实就是绑定到了@Transactional @Secure.(就是拦截器TransactionInterceptor和拦截器SecurityInterceptor). (甚至TransactionalSecureInterceptor,如果它存在.)
7.Use of @Interceptors(同时用多个拦截器)
这个注解@Interceptors是拦截器规范定义的,cdi是支持的<使用托管bean和EJB规范>.如下:
@Interceptors({TransactionInterceptor.class, SecurityInterceptor.class}) public class ShoppingCart { public void checkout() { ... } }
但缺点也很明显,不推荐使用.缺点如下:
- 拦截器在代码中是硬编码.
- 拦截器在部署时不好更改.
- 拦截器命令是非全局的——它是在类级别由拦截器的顺序列出.
因此还是使用上面CDI的使用方式比较好.
CDI高级说明以及Producer methods
1.CDI致力于松耦合,强类型.
实现松散耦合的三种方式:- 部署时候的多态选择,@alternatives
- producer methods在运行时的多态.
- 上下文相关的生命周期管理与bean生命周期解耦。
CDI提供了三个额外的重要措施,进一步松耦合:
- 在业务逻辑层用拦截器技术解耦.
- 修饰符(注解)可以用来分离一些业务问题
- 用CDI EVENT技术进行解耦事件生产者与消费者.
CDI是确确实实没String标识符,如xml配置什么的.比如Spring2.5用XML配置,其实都是字符串,以及"约定大于配置"的概念.在CDI里是没有的.CDI框架不是隐藏,而是没有.
这种方法的最明显好处就是任何IDE都可以提供自动完成,验证以及最重要的重构!(了解JPA的,可以对比安全类型的查询和JPQL.如果重构代码JPQL是非常麻烦的).
还有个好处就是你在识别不同的对象,事件,拦截器可以通过注解而不是字符串名字,这样你可以提升代码质量.
CDI鼓励开发使用注解.如
@Asynchronous,
@Secure,
@Updated,
而不是使用复合名称,
asyncPaymentProcessor,
SecurityInterceptor
DocumentUpdatedEvent.
这也符合代码大全里的一些概念.只不过不用费尽心思考虑命名了,这样更简洁高效.
注释是可重用的。他们帮助描述系统的不同部分的共同特质。他们帮助我们分类和理解代码。他们帮助我们应对常见问题的常用方法。他们使我们的代码更简洁高效.
2.高级功能Producer methods
先贴一段代码,下面都用到
import javax.enterprise.inject.Produces; @SessionScoped public class Preferences implements Serializable { private PaymentStrategyType paymentStrategy; ... @Produces @Preferred public PaymentStrategy getPaymentStrategy() { switch (paymentStrategy) { case CREDIT_CARD: return new CreditCardPaymentStrategy(); case CHECK: return new CheckPaymentStrategy(); case PAYPAL: return new PayPalPaymentStrategy(); default: return null; } } } //注入一个Producer methods @Inject @Preferred PaymentStrategy paymentStrategy;
A:Producer methods的Scope
Producer methods的默认范围是@Dependent.从上面代码我们可以思考一种场景,那就是一个用户会话中有多个PaymentStrategy对象的实例.如果想改变,我们可以在Producer方法上添加一个@SessionSciped注解.
现在,如果一个用户调用了这个Producer methods,那么返回的这个PaymentStrategy对象的实例将绑定到会话的上下文.Producer methods不会再实例化另一个出来.
@Produces @Preferred @SessionScoped public PaymentStrategy getPaymentStrategy() { ... }
注意:Producer methods不继承声明此Producer methods的Bean的Scope.
其实这里有2个不同的Bean:Producer methods(相当于一个Bean)以及声明这个生产方法的Bean.
B: Injection into producer methods
在Producer methods一开始的实例有一个潜在的问题
CreditCardPaymentStrategy 的实现使用 Java new 运算符来实例化。
private PaymentStrategyType paymentStrategy;
而producer methods应该理解为一个独立的Bean,而paymentStrategy是从Preferences 中用new实例化的.所以我们应该使用下面这种方式来使用producer methods方法.
@Produces @Preferred @SessionScoped public PaymentStrategy getPaymentStrategy(CreditCardPaymentStrategy ccps, CheckPaymentStrategy cps, PayPalPaymentStrategy ppps) { switch (paymentStrategy) { case CREDIT_CARD: return ccps; case CHEQUE: return cps; case PAYPAL: return ppps; default: return null; } }
这里会有问题,如果CreditCardPaymentStrategy 是一个@RequestScope,那这里必然是要发生错误的.因为注入的CreditCardPaymentStrategy Bean实例是request,在@SessionScoped使用前容器就会进行销毁.那么就出错了.
- producer method to @Dependent or @RequestScoped.<最好的方式>
- CreditCardPaymentStrategy 更改Scope,但这可能会影响其他的地方,不是很好.
使用@New限定符,但在CDI 1.1 @New限定符被弃用。CDI鼓励应用程序注入@Dependent范围bean。
C:Use of @New with producer methods<不推荐>
@Produces @Preferred @SessionScoped
public PaymentStrategy getPaymentStrategy(@New CreditCardPaymentStrategy ccps,
@New CheckPaymentStrategy cps,
@New PayPalPaymentStrategy ppps) {
switch (paymentStrategy) {
case CREDIT_CARD: return ccps;
case CHEQUE: return cps;
case PAYPAL: return ppps;
default: return null;
}
}
在CDI 1.1 @New限定符被弃用。CDI鼓励应用程序注入@Dependent范围bean。
D:Disposer methods
一些Procucer methods返回的对象需要显式的破坏。例如,有人需要关闭这个JDBC连接:@Produces @RequestScoped Connection connect(User user) { return createConnection(user.getId(), user.getPassword()); }
而在一个相同的类中,disposer method可以进行匹配.
void close(@Disposes Connection connection) { connection.close(); }
说明:在同一个类中,disposer method可以进行匹配类型为Connection 的Procucer methods,从而在 Procucer methods周期结束后进行jdbc的链接关闭.
import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Disposes; import javax.enterprise.inject.Produces; import javax.enterprise.inject.spi.InjectionPoint; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Resources { @PersistenceUnit private EntityManagerFactory entityManagerFactory; @Produces @RequestScoped protected EntityManager createEntityManager() { return entityManagerFactory.createEntityManager(); } //参数必须对应上面方法的返回值 protected void closeEntityManager(@Disposes EntityManager entityManager) { if ( entityManager.isOpen() ) { entityManager.close(); } } }
参考:https://my.oschina.net/zhaoqian/blog/264604
https://www.jianshu.com/p/5d2bd9369134