1. Scope types
CDI的特点之一是高可扩展性.比如你可以自己定义一个Scope.如下:@ScopeType
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface ClusterScoped {}
这样使用
@ClusterScoped
public class SecondLevelCache { ... }
当然我们要定义一个Context 对象去实现这个Scope.这个就属于一个框架方面的任务.
但更多的时候我们是使用 cdi built-in scopes.
2.Built-in scopes
@RequestScoped@SessionScoped
@ApplicationScoped
@ConversationScoped
Managed beans的 @SessionScoped 和 @ConversationScoped 必须是可序列化的.就是必须要 implements Serializable.因为他们要为为容器钝化HTTP会话(passivates the HTTP session).(其实我是全部都implements Serializable的...= =!)
@RequestScoped,@SessionScoped,@ApplicationScoped这3个就不再说了.主要说@ConversationScoped.
3.@ConversationScoped
ConversationScope其实功能上和传统的SessionScope是有点像的,但不一样的是SessionBean是不能手动控制的.也就是说会话结束,你的SessionBean才会销毁,所以用的时候很谨慎.但Conversation则可以手动进行开启,删除.如果@ConversationScoped不开启,相当于@RequestScoped.如果开启了,相当于一个可以让开发者能手动删除的@SessionScoped.一:Conversation的基本使用,Conversation timeout
如下代码.@ConversationScoped生命周期的控制,就在这2个方法中.
timeout需要对应场景来设置.不要忽略!
isTransient是用来判断是否开启了Conversation.
这2个方法都是必须使用的.
@Named
@ConversationScoped
public class StudentBean implements Serializable {
private static final long serialVersionUID = 353361676959311121L;
@Inject
Conversation conversation;
//开启会话模式
public void beginConversation() {
if ( conversation.isTransient() )
{
conversation.begin();
//过期时间20分钟.
conversation.setTimeout(1200000);
}
}
//关闭会话模式
@PreDestroy
public void endConversation() {
if ( !conversation.isTransient() )
{
conversation.end();
}
}
}
如果2个@ConversationScoped. A中@inject了B.那么B的会话周期也是属于A来控制.B不需要开启会话.直接依赖A的周期.但A销毁B也销毁.
下面是JBOSS weld给的一个例子:import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.persistence.PersistenceContextType.EXTENDED;
@ConversationScoped
@Stateful
public class OrderBuilder {
private Order order;
private @Inject Conversation conversation;
private @PersistenceContext(type = EXTENDED) EntityManager em;
@Produces public Order getOrder() {
return order;
}
public Order createOrder() {
order = new Order();
conversation.begin();
return order;
}
public void addLineItem(Product product, int quantity) {
order.add(new LineItem(product, quantity));
}
public void saveOrder(Order order) {
em.persist(order);
conversation.end();
}
@Remove
public void destroy() {}
}
4.Conversation传播---cid
如果是POST请求,cid会自动传播.如果是GET请求,需要加cid参数.
CDI规范保留cid这个参数名.cid是一个conversation的唯一标识符.页面EL表达式为:javax.enterprise.context.conversation。
<a href="/addProduct.jsp?cid=#{javax.enterprise.context.conversation.id}">Add Product</a>
或者JSF中更常用的
<h:link outcome="/addProduct.xhtml" value="Add Product">
<f:param name="cid" value="#{javax.enterprise.context.conversation.id}"/>
</h:link>
5.CDI Conversation filter
会话管理会有异常。
javax.enterprise.context.NonexistentConversationException(如:页面传递的cid不存在引发)
javax.enterprise.context.BusyConversationException(长时间运行Conversation的并发请求)
这个时候是需要使用 CDI Conversation filter 来进行处理.
public class MyFilter implements Filter {
...
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (BusyConversationException e) {
response.setContentType("text/plain");
response.getWriter().print("BusyConversationException");
}
}
...
如果要他工作,要在web.xml中定义:
<filter-mapping>
<filter-name>My Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CDI Conversation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
当然上面这个是官方的文档,我真想一口盐汽水喷死他们,我是完全走不通,用他描述的这种方案.后续自己写了个.
得知,conversation的两个异常是
- import javax.enterprise.context.BusyConversationException;
- import javax.enterprise.context.NonexistentConversationException;
或者是org.jboss.weld.context.NonexistentConversationException或org.jboss.weld.context.BusyConversationException
但他们是继承关系,一样的处理.
1.我可以按照cid是否存在进行处理,或者更深的处理,
2.我可以根据URL进行不同的页面处理.
package org.credo.scope.conversation;
import java.io.IOException;
import javax.enterprise.context.BusyConversationException;
import javax.enterprise.context.NonexistentConversationException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// try {
// chain.doFilter(request, response);
// } catch (org.jboss.weld.context.NonexistentConversationException e) {
// System.out.println("weld NonexistentConversationException"+e.getMessage());
// response.setContentType("text/plain");
// response.getWriter().print("NonexistentConversationException");
// }catch (NonexistentConversationException e) {
// System.out.println("NonexistentConversationException"+e.getMessage());
// response.setContentType("text/plain");
// response.getWriter().print("NonexistentConversationException");
// }
// catch (Exception e) {
// System.out.println("Exception:"+e.getMessage());
// response.getWriter().print("NonexistentConversationException");
// }
if (request.getParameter("cid")!=null) {
try {
System.out.println("1");
chain.doFilter(request, response);
throw new RuntimeException("Expected exception not thrown");
} catch (ServletException e) {
Throwable cause = e.getCause();
System.out.println("2");
while (cause != null) {
System.out.println("e.getCause():"+e.getCause());
if (e.getCause() instanceof NonexistentConversationException) {
System.out.println("3");
response.setContentType("text/html");
response.getWriter().print("NonexistentConversationException thrown properly\n");
HttpServletResponse res = (HttpServletResponse) response;
res.sendRedirect("/credo-jsf/guest.jsf");
// FIXME WELD-878
// response.getWriter().print("Conversation.isTransient: " + conversation.isTransient());
return;
}
cause = e.getCause();
}
throw new RuntimeException("Unexpected exception thrown");
}
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
}
}
<filter>
<filter-name>CDI Conversation Filter</filter-name>
<filter-class>org.credo.scope.conversation.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CDI Conversation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
官方给的过滤器示例,我试了好几个小时完全行不通.查遍google表示没看到行得通相关的处理.有高手知道,请教下.
6.Lazy and eager conversation context initialization
conversation上下文在初始化的时候可以是延迟或者即时.<待测试学习>
延迟
当初始化是延迟加载的时候,conversation context无论是transient或者是长运行(就是conversation.begin或者没begin
),它只是在@ConversationScoped Bean第一次访问的时候初始化conversation上下文.在此,cid 参数是可读以及conversation是恢复的.
如果没有conversation的状态是可访问的,那么conversation上下文可能没去处理所有的请求.
注意.如果一个问题发生在延迟加载的初始化阶段.那么这个conversation会抛出一个BusyConversationException or NonexistentConversationException 异常.
即时
当初始化是即时的,那么这个conversation上下文会在预定义的的时间进行初始化.在任意请求之前,处理listener前,filter or servlet 是唤醒的或者.如果这个CDI Conversation Filter 配置好了,那么会执行这个filter.
配置conversation 上下文初始化的方式使用如下代码进行配置.
<context-param>
<param-name>org.jboss.weld.context.conversation.lazy</param-name>
<param-value>true</param-value>
</context-param>
如果这个参数没有去设置,会有下列情况:
- CDI Conversation Filter配置好,那么conversation context会在这个过滤器中进行即时加载.
- 如果在全局有cdi event事件的一个观察者observer来观察@Initialized(ConversationScoped.class) or @Destroyed(ConversationScoped.class) 事件,那么conversation context会即时加载.
- 此外,就都是会延迟加载.
7.The singleton pseudo-scope
除了四个内建的范围,CDI还支持两个伪范围.第一个就是singleton,使用@singleton标注.
注意: 四个内建的范围都是在package javax.enterprise.context下的,而@singleton是在package javax.inject下的.
这里的singleton和其他地方的singleton是不同的,或者说cdi的singleton是有问题的.
你可以看到他是在inject下的,这个scope是伪范围.Bean与@singleton没有代理对象范围.
现在,如果这个singleton Bean是一个简单的,不可变的,可序列化的对象,就如同一个字符串,数字,日期.我们可以不用太在乎它通过序列化进行复制.然后这使得这个singleton Bean并不是一个真正的单例.
但仍然有几种办法确保singleton bean是一个单例如:
- 有singleton bean实现 writeResolve()和readReplace()---是java serialization规范定义的.
- 确保客户端仅仅保留一个瞬态singleton bean的引用.
- 在使用的时候用 @Inject Instance<X> instance;的方式来注入.
- 最好的解决方案是用@ApplicationScoped.
- 其实这个伪范围更多的用来避免使用默认伪范围@Dependent.
8.The dependent pseudo-scope
如果一个Bean未使用任何范围注解.(4内置+1 singleton pseudo-scope).那么scope type就是@Dependent.如下代码
public class Calculator { ... }
他的scope就是@Dependent.
@Dependent的Bean严重依赖注入他的Bean.注入他的Bean是@RequestScoped,那么他就是@RequestScoped.注入他的Bean是@SessionScoped,那么他就是@SessionScoped.
注意:在JSF不要使用@Dependent范围的Bean的相关EL表达式. If you need to access a bean that really has to have the scope @Dependent from a JSF page, inject it into a different bean, and expose it to EL via a getter method.
@Dependent Bean 并不需要一个代理对象。客户端直接引用它的实例。
CDI可以很容易地得到一个@Dependent bean,即使该bean已经被声明为其他一些范围类型的Bean。
9.The @New qualifier
警告:@New qualifier在CDI 1.1已被弃用.CDI建议使用@Dependent scoped.
@New可以使得我们能获取一个指定类的依赖对象.
@Inject @New Calculator calculator;
这个类必须是一个有效的Bean或者EJB Session Bean,但是不需要启用.
即使这个Bean已经申明了不同的范围类型.例如:
@ConversationScoped
public class Calculator { ... }
下面则是注入得到Calculator 的不同实例.
public class PaymentCalc {
@Inject Calculator calculator;
@Inject @New Calculator newCalculator;
}