EJB3 - Session bean
description
其實session bean是最一開始就看的, 回過頭來看再記重點有點心浮氣躁.
reference
EJB3 in Action - CH3 - Building business logic with session beans
Focal Points
session bean一定要有一個以上的interface與一個實現
一個session bean可以有多個interface, 所以當客戶端調用一個@Local的interface, 就是使用local的session bean. 使用@Remote或@WebService就是用remote或web service的session bean.
session bean一定要是concrete class, 一定要有無參构造函数, 不能是abstract或final.
session bean可以是其他session bean或POJO的subclass.
session bean的business method與lifecycle callback可定在super class或session bean class裡.
session bean的annotation的繼承是有條件的, 就是class level比方說@Stateless, @Stateful會被忽略, 不過lifecycle callback會被繼承下來.
session bean的business method name不能以ejb開頭, 比方說不能是ejbDoit()
session bean的method必須是public且不能是static或final
使用一個remote business interface要注意argument與return type必須實作Serizable
所有session bean都有的生命週期是creation / destruction
stateful bean比stateless bean又多了passivation / activation
stateless bean的lifecycle callback為@PostConstruct, @PreDestroy
stateful bean的lifecycle callback為@PostConstruct, @PreDestroy, @PostActivate, @PrePassivate
@PostConstruct, @PreDestroy比較簡單.就是container实例化session bean後调用@PostContruct, container移除session bean前會调用@PreDestroy
@PostActivate, @PrePassivate比較特別. 一旦container判斷一個stateful bean停用了而決定要暫時讓這個session bean失去效用, 這個動作叫钝化. 而container讓已經失效的stateful bean再度生效就叫激活. 所以@PostActivate就是activation後调用的method, @PrePassivate就是钝化前呼叫的method.
lifecycle callback可以是public, private, protected, package-protected
lifecycle callback主要用來替session bean準備实例化後需要的資源以及從container移除前要釋放的資源.
lifecycle callback除了放在session bean以外也可放在分開的interceptor class
stateless session bean有pool, 也就是說有一定數量的stateless session bean在container的pool中
@Stateless的定義
- 屬性有name, mappedName, description
- name屬性用來指定bean的name, 有的container用來和JNDI綁定. 如果name沒有設定就會是bean的class name.
- mappedName是vender-specific(特定于厂商), 也就是依不同container有不同的情形. 以GlassFish來說是將mappedName的值綁定到JNDI name.
@Local: stateless session bean的local interface, local interface表示這個session bean和client放在同一個JVM上執行.
@Remote: 當client存在於container外的JVM時就必須使用@Remote
- 一個@Remote interface可以繼承java.rmi.Remote
public interface TestRemote extends Remote { ... }
就算程序裡面沒寫繼承Remote, container還是會在byte code階段插入繼承Remote的動作
- 沒有程序上繼承java.rmi.Remote的好處就是不用處理java.rmi.RemoteException
- @Remote business interface有個需求就是所有的參數與回傳值都必須是Serializable, 因為這樣才能通過RMI
@WebService: 透過@WebService可讓session bean成為SOAP-based web service. 唯一要做的就是在interface上加上@WebService.
不能讓一個business interface同時@Local又@Remote或是又@WebService, 不過可以透過interface的繼承改變要使用的是@Remote session bean還是@Local的 session bean.
放session bean的lifecycle callback
可以有多個@PostConstruct, @PreDestroy (不過我試起來一個session bean就只能一個lifecycle callback有效, 頂多除了callback以外還指定interceptor, 就加上interceptor的一個lifecycle callback有效.)
lifecycle callback要符合pattern: void < METHOD >()
interceptor內的lifecycle callback要符合pattern: Object < METHOD >(InvocationContext) throws Exception, 然後記得回傳InvocationContext.proceed, 除非打算不繼續執行. (可參考EJB3 Interceptor)
session bean的lifecycle callback不可有checked exception, interceptor的則可以.
session bean的lifecycle callback不可有傳入參數, interceptor則要傳入InvocationContext, 否則有java.lang.IllegalArgumentException: wrong number of arguments
stateless session bean與stateful session bean的差別主要在於container管理的方式, stateless session bean起始之後會被container放進pool, 等client要使用的時候再從pool取出, 用完再放回pool. stateful session bean則是讓一個client擁有一個stateful session bean直到client離開或stateful session bean destroy為止. stateful session bean與client是one to one的關係.
stateful session bean需要付出代價, stateless session bean由於所有client共用session bean比較能節省資源, stateful session bean則因為與client是one to one的關係所以比較耗資源. 一旦container判斷消耗資源太多或佔用資源太久就會開始執行passivate的動作.
由於stateful session bean比較耗資源, 所以注意要在stateful session bean加上@Remove method, 當呼叫此method, container就會負責將此method destroy以節省資源.
由於stateful session bean在passivate的時候會做serialize的動作, 所以注意stateful session bean的class 成员必須實做Serializable或必須是原始类型. 否則在passivate的時候會出現例如[NRU-stateful.SimpleStatefulBean]: passivateEJB(), Exception caught 的exception, 就是因為無法serialize該object的關係. 如果要使用不須serialize的class 成员只要用transient声明該class member或在@PrePassivate把class member改成null再於@PostActivate設定回來即可.
使用stateful session bean的方式幾乎和stateless session bean的方式幾乎一樣, 唯一不一樣的是stateful session bean的business interface只能使用@Local與@Remote而不能用@WebService. 因為SOAP-based web Service本來就不是stateful因此無法使用.
stateful session bean的生命週期中有重要的一點就是container在destroy一個passivate的時候會先將該stateful session bean先activate再passivate.
@Stateful(name="SimpleStatefulBean", mappedName="ejb/SimpleStatefulBean")
public class SimpleStatefulBean implements SimpleStatefulRemote {
private Logger logger = Logger.getLogger("SimpleStatefulBean");
private byte[] b = new byte[100000];
{
for ( int i = 0; i < b.length; i++ ) {
b[i] = (byte) 100;
}
}
public String simpleShow() {
return this + ":This is simple show" + b;
}
@PostConstruct
public void postConstruct() {
logger.info("create " + this);
}
@PreDestroy
public void preDestroy() {
logger.info("destroy " + this);
}
@PostActivate
public void postActivate() {
logger.info("activate " + this);
}
@PrePassivate
public void prePassivate() {
logger.info("passivate " + this);
}
@Remove
public void remove() {
logger.info("remove " + this);
}
}
放interceptor的lifecycle callback
@Stateless
@Interceptors(value={SimpleInterceptor.class})
public class SimpleStatelessBean implements SimpleStatelessLocal {
private Logger logger = Logger.getLogger("SimpleStatelessBean");
@Resource(name="TestQueueConnectionFactory")
private ConnectionFactory connectionFactory;
@Resource(name="jms/TestQueueDestination")
private Destination destination;
public String simpleShow() {
try {
Connection conn = connectionFactory.createConnection();
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer messageProducer = session.createProducer(destination);
TextMessage message = session.createTextMessage();
message.setText("This is text message");
messageProducer.send(message);
messageProducer.close();
session.close();
conn.close();
} catch (JMSException ex) {
throw new RuntimeException( ex );
}
return this + ":This is simple show";
}
}
public class SimpleInterceptor {
Logger logger = Logger.getLogger("SimpleStatefulBeanInterceptor");
@PostConstruct
public void onCreate(InvocationContext ic) {
try {
logger.info("create " + this);
ic.proceed();
} catch (Exception ex) {
Logger.getLogger(SimpleInterceptor.class.getName()).log(Level.SEVERE, null, ex);
}
}
@AroundInvoke
public Object aroundInvoke(InvocationContext ctx) throws Exception {
logger.info(ctx + " is invoked.");
return ctx.proceed();
}
@PreDestroy
public void onDestroy(InvocationContext ic) {
try {
logger.info("destroy " + this);
ic.proceed();
} catch (Exception ex) {
Logger.getLogger(SimpleInterceptor.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
stateful bean的@PrePassivate, @PostActivate也可放interceptor
@Interceptors(value={SimpleInterceptor.class})
@Stateful(name="SimpleStatefulBean", mappedName="ejb/SimpleStatefulBean")
public class SimpleStatefulBean implements SimpleStatefulRemote {
private byte[] b = new byte[100000];
{
for ( int i = 0; i < b.length; i++ ) {
b[i] = (byte) 100;
}
}
public String simpleShow() {
return this + ":This is simple show" + b;
}
@Remove
public void remove() {
Logger.getLogger("SimpleStatefulBean").info("remove " + this);
}
}
public class SimpleInterceptor {
@PostConstruct
public void onCreate(InvocationContext ic) {
try {
Logger.getLogger(SimpleInterceptor.class.getName()).info("create " + this);
ic.proceed();
} catch (Exception ex) {
Logger.getLogger(SimpleInterceptor.class.getName()).log(Level.SEVERE, null, ex);
}
}
@PostActivate
public void onActivate(InvocationContext ic) {
try {
Logger.getLogger(SimpleInterceptor.class.getName()).info("activate " + this);
ic.proceed();
} catch (Exception ex) {
Logger.getLogger(SimpleInterceptor.class.getName()).log(Level.SEVERE, null, ex);
}
}
@PrePassivate
public void onPassivate(InvocationContext ic) {
try {
Logger.getLogger(SimpleInterceptor.class.getName()).info("passivate " + this);
ic.proceed();
} catch (Exception ex) {
Logger.getLogger(SimpleInterceptor.class.getName()).log(Level.SEVERE, null, ex);
}
}
@AroundInvoke
public Object aroundInvoke(InvocationContext ctx) throws Exception {
Logger.getLogger(SimpleInterceptor.class.getName()).info(ctx + " is invoked.");
return ctx.proceed();
}
@PreDestroy
public void onDestroy(InvocationContext ic) {
try {
Logger.getLogger(SimpleInterceptor.class.getName()).info("destroy " + this);
ic.proceed();
} catch (Exception ex) {
Logger.getLogger(SimpleInterceptor.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
@EJB用來注入session bean到client code. @EJB有幾個屬性: name, beanInterface, beanName. name用來指定JNDI name, beanName則是當一個interface有兩個實作時用來決定要注入哪個實作.
使用@EJB注入的時候如果沒有指定JNDI name, container就會用interface name當成JNDI name注入.
如果要注入同個interface不同的實作可透過指定JNDI name或beanName
@Stateless(name="SimpleStatelessBean1")
public class SimpleStatelessBean1 implements SimpleStatelessLocal { ... }
@Stateless(name="SimpleStatelessBean2")
public class SimpleStatelessBean2 implements SimpleStatelessLocal { ... }
public class SimpleStatelessServlet extends HttpServlet {
@EJB(beanName="SimpleStatelessBean1")
private SimpleStatelessLocal simpleStatelessLocal1;
@EJB(beanName="SimpleStatelessBean2")
private SimpleStatelessLocal simpleStatelessLocal2;
...
}
可注入stateless bean或stateful bean到其他的stateful bean. 但不能注入stateful bean到stateless bean, 因為這樣stateful session bean就會被所有client分享.
注入stateful bean到另一個stateful bean時, 一旦持有注入的stateful bean destroy了, 被持有的stateful bean也會一起destroy.
如果不用stateful bean可將狀態放在DB中或放在server side的檔案或放HttpSession, 不過要注意清除不必要的資源.
儲存conversation state的時候要注意儲存的值愈小愈好, 例如primitive.
stateful bean要記得定義@Remove method.
調整server到stateful bean效能最佳的狀態, 小心頻繁的passivate / activate造成效能變差太多.