CDI(Weld)基础<4>Scopes and contexts

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>
如果这个参数没有去设置,会有下列情况:
  1. CDI Conversation Filter配置好,那么conversation context会在这个过滤器中进行即时加载.
  2. 如果在全局有cdi event事件的一个观察者observer来观察@Initialized(ConversationScoped.class) or @Destroyed(ConversationScoped.class) 事件,那么conversation context会即时加载.
  3. 此外,就都是会延迟加载.

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是一个单例如:

  1. 有singleton bean实现 writeResolve()和readReplace()---是java serialization规范定义的.
  2. 确保客户端仅仅保留一个瞬态singleton bean的引用.
  3. 在使用的时候用 @Inject Instance<X> instance;的方式来注入.
  4. 最好的解决方案是用@ApplicationScoped.
  5. 其实这个伪范围更多的用来避免使用默认伪范围@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;

}



calculator;注入一个@ConversationScoped的实例。该newCalculator注入了新的实例,与被绑定到PaymentCalc,和PaymentCalc一个生命周期。

你可能感兴趣的:(CDI(Weld)基础<4>Scopes and contexts)