一、EL(SPEL)
EL
1.概述;
EL是JSP内置的表达式语言,用以访问页面的上下文以及不同作用域中的对象 ,
取得对象属性的值,或执行简单的运算或判断操作。EL在得到某个数据时,会自动进行数据类型的转换。
使用EL表达式输出数据时,如果有则输出数据,如果为null则什么也不输出。
2.语法:
a.EL表达式总是放在{}中,而且前边有一个$作为前缀:${}
b.获取对象的属性值可以直接通过“对象.属性名”:${user.name};
注意:这里的属性名是get和set方法对应的属性值,并不是对象中的变量名。
c.获取对象的属性也可以通过“对象[“属性名”]”:${user["name"]}
d.获取Map中属性时可以以直接通过属性的key:${map.key},${map[key]}
e.在指定域中获取属性:
在EL表达式中如果我们直接使用属性名如:${user},它将会在四个域中由小到大依次查找。
顺序:pageScope、requestScope、sessionScope、applicationScope。
也可以指定从哪个域中获取:
${ pageScope .user }:当前页面
${requestScope.user}:当前请求
${sessionScope.user}:当前会话
${sessionScope.user}:当前应用
3.EL中包含11个隐含对象,这些对象可以在EL表达式中直接使用:
a.pageContext,和JSP中的pageContext功能一样
b.请求域:pageScope/requestScope/sessionScope/applicationScope
c.请求参数,参数对象主要用于获取get或post请求中的参数:
param:获取指定的请求参数,${param.username}
paramValues:获取请求参数数组,如:${paramValues.sport[1]}
d.其他:header/headerValues/initParam/cookie
4.EL支持数学运算和逻辑运算:
a.加减乘除:${17+5} => 22
b.取余%或mod:${17%5} => 2
c.比较运算符>,<,==,!=,<=,>=,eq,ne,lt,gt,le,ge:${3>5}或${3 gt 5} =>false
d.逻辑比较 &&或and,!或not,||或or,empty:${!true} => false
SPEL
1.Spring框架的表达式语言(简称SpEL):是一个支持运行时查询和操作对象图的强大的表达式语言。
SpEL 为 bean 的属性进行动态赋值提供了便利.
2.语法:SpEL 使用 #{…} 作为定界符,所有在大框号中的字符都将被认为是 SpEL。
3.运用范围:
a. 对 bean 进行引用,调用属性值:#{book.name}
b.调用方法以及引用对象中的属性
引用方法:#{dog.run()},引用静态方法:#{T(java.lang.Math).PI}
引用对象的属性:#{user.name}
c.计算表达式的值
加减乘除:#{counter.total + 40},#{T(java.lang.Math).PI * 2}
加号作为字符串连接符:#{user.name + ' ' + user.address}
比较运算符(>,<,=,>=,<=,==,lt,gt,eq,le,ge):
#{counter.total == 100},#{counter.total le 1000}
if-else条件判断,三元运算符:
#{user.name=='Tom' ? 'Jess'}
d.正则表达式的匹配(matches)
#{user.name matches '^[a-zA-Z0-9_-]{4,16}$'}
e.字面量的表示:
#{5},#{89.7},#{1e4},#{false}
可使用单/双引号作为字符串表达符号:#{'Chuck'},#{"Chuck"}
二、Decorator:
1.Decorator 设计模式的特点:
Decorator 设计模式正如毛胚房的装修,不会改变原毛胚房的基本框架,只是增加新的外观、功能等,且随着时间的推移,可以不断的实施装修工程:增加新的家具、根据心情换换新鲜的墙纸等等。在面向对象的程序设计中,扩展系统的原有功能也可以采用继承、组合的方式。继承也不会改变毛胚房(父类),但是由于装修工程的复杂和很多不可预测的改变,比如不同墙纸和地板样式的组合数量简直无法想想,难道我们要为每一种组合都定义一个子类吗?显然这是不现实的,即通过继承的方式来应对未来的功能和外观改变通常是吃力不讨好的事情。组合的方式也不可取,因为这要求不断的修改父类的结构,相当于对毛胚房大动干戈,房屋的可维护性和可靠性就大大降低了。
让我们回顾一下设计模式的重要原则:Classes should be open for extenstion, but closed for modification。Decorator 设计模式很好的诠释了这个原则。
2.CDI 对 Decorator 设计模式的支持:
Decorator 设计模式虽然降低了需求变更对软件开发的影响,但是通过层层包装,即层层 new 操作创建对象的方式不够优雅。CDI 容器可以管理组件的生命周期,在大部分情况下我们无须通过 new 操作创建所需要的对象。CDI 中的 Decorator/Delegate 注解很大程度上简化了 Decorator 设计模式的代码编写量,比如实现上面相同的功能,借助于 CDI,就无须 RoomDecorator 这个抽象类了,所有的 Decorator 类直接实现 Room 接口并使用注解声明为 Decorator 即可
3.总结:
Decorator 设计模式简单而精巧,它其实是 Unix 哲学的体现:每一个应用程序都尽力做好自己,然后通过应用程序之间的协作完成更复杂的任务,正如 shell 的管道符的作用。从复杂应用程序框架设计的角度看,Decorator 设计模式也降低了模块之间的耦合度,而 CDI 更进了一步,借助于容器和类型安全的组件模型,简化了 Decorator 模式的应用,同时消除了某些潜在的运行时异常,也就是说,CDI 之上的 Decorator 设计模式能够帮助构建更加安全的复杂应用。
三、Interceptor
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 { ... }
2.拦截器绑定(Interceptor bindings)
假设我们想要申明一些bean的事务。我们先要的是一个拦截器绑定类型来指定哪些bean我们要申明.
首先定义一个注解
@InterceptorBinding
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Transactional {}
2.拦截器实现(Implementing interceptors)
我们实际上要实现提供了这种事务管理方面的拦截器,所以我们需要做的是创建一个标准的拦截,并配上@Interceptor和@transactional注解.
@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.
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的使用方式比较好.
四、Producer
CDI是为解耦而生.如Spring主要用途是AOP.IOC(DI),而CDI除了DI外,AOP功能也是有的.从实际使用上来看,CDI比Spring功能更丰富,更灵活,其代价也是有的,学习成本相对spring较高.
1.CDI致力于松耦合,强类型.
实现松散耦合的三种方式:
- 部署时候的多态选择,@alternatives
- producer methods在运行时的多态.
- 上下文相关的生命周期管理与bean生命周期解耦。
这些技术使客户端和服务器的松散耦合服务。客户端不再是紧密地绑定到一个接口的一个实现,也不需要管理生命周期实现。这种方法允许有状态的对象当作服务交互。松散耦合使系统更具活力。在以前,框架总是牺牲了类型安全(尤其是通过使用XML描述符,Spring2.5)。
CDI提供了三个额外的重要措施,进一步松耦合:
- 在业务逻辑层用拦截器技术解耦.
- 修饰符(注解)可以用来分离一些业务问题
- 用CDI EVENT技术进行解耦事件生产者与消费者.
第二个CDI是强类型的.无论是依赖关系的信息,拦截器,修饰符的Bean,以及CDI event的生产者,消费者等的信息全部都是类型安全的.由编译器进行验证.
CDI是确确实实没String标识符,如xml配置什么的.比如Spring2.5用XML配置,其实都是字符串,以及"约定大于配置"的概念.在CDI里是没有的.CDI框架不是隐藏,而是没有.
这种方法的最明显好处就是任何IDE都可以提供自动完成,验证以及最重要的重构!(了解JPA的,可以对比安全类型的查询和JPQL.如果重构代码JPQL是非常麻烦的).
还有个好处就是你在识别不同的对象,事件,拦截器可以通过注解而不是字符串名字,这样你可以提升代码质量.
CDI鼓励开发使用注解.如
@Asynchronous,
@Secure,
@Updated,
而不是使用复合名称,
asyncPaymentProcessor,
SecurityInterceptor
DocumentUpdatedEvent.
这也符合代码大全里的一些概念.只不过不用费尽心思考虑命名了,这样更简洁高效.
注释是可重用的。他们帮助描述系统的不同部分的共同特质。他们帮助我们分类和理解代码。他们帮助我们应对常见问题的常用方法。他们使我们的代码更简洁高效.
2.高级功能Producer methods
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使用前容器就会进行销毁.那么就出错了.
这是个问题,所以我们有3种处理方案.
- producer method to @Dependent or @RequestScoped.<最好的方式>
- CreditCardPaymentStrategy 更改Scope,但这可能会影响其他的地方,不是很好.
使用@New限定符,但在CDI 1.1 @New限定符被弃用。CDI鼓励应用程序注入@Dependent范围bean。
C:Use of @New with producer methods<不推荐>
Consider the following producer method:
@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; } }
这将会创建一个新的CreditCardPaymentStrategy依赖实例,传递到生产方法,依赖对象不会被摧毁,直到会话结束。
在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(); } } }
结束.