應(yīng)用程序能夠響應(yīng)Hibernate內(nèi)部產(chǎn)生的特定事件是非常有用的。這樣就允許實現(xiàn)某些通用的功能以及允許對Hibernate功能進行擴展。
Interceptor接口提供了從會話(session)回調(diào)(callback)應(yīng)用程序(application)的機制,這種回調(diào)機制可以允許應(yīng)用程序在持久化對象被保存、更新、刪除或是加載之前,檢查并(或)修改其屬性。一個可能的用途,就是用來跟蹤審核(auditing)信息。例如:下面的這個攔截器,會在一個實現(xiàn)了Auditable接口的對象被創(chuàng)建時自動地設(shè)置createTimestamp屬性,并在實現(xiàn)了Auditable接口的對象被更新時,同步更新lastUpdateTimestamp屬性。
package org.hibernate.test;
import java.io.Serializable;
import java.util.Date;
import java.util.Iterator;
import org.hibernate.Interceptor;
import org.hibernate.type.Type;
public class AuditInterceptor implements Interceptor, Serializable {
private int updates;
private int creates;
public void onDelete(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
// do nothing
}
public boolean onFlushDirty(Object entity,
Serializable id,
Object[] currentState,
Object[] previousState,
String[] propertyNames,
Type[] types) {
if ( entity instanceof Auditable ) {
updates++;
for ( int i=0; i < propertyNames.length; i++ ) {
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
currentState[i] = new Date();
return true;
}
}
}
return false;
}
public boolean onLoad(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
return false;
}
public boolean onSave(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
if ( entity instanceof Auditable ) {
creates++;
for ( int i=0; i<propertyNames.length; i++ ) {
if ( "createTimestamp".equals( propertyNames[i] ) ) {
state[i] = new Date();
return true;
}
}
}
return false;
}
public void postFlush(Iterator entities) {
System.out.println("Creations: " + creates + ", Updates: " + updates);
}
public void preFlush(Iterator entities) {
updates=0;
creates=0;
}
...
}
創(chuàng)建會話(session)的時候可以指定攔截器。
Session session = sf.openSession( new AuditInterceptor() );
你也可以使用Configuration來設(shè)置一個全局范圍的攔截器。
new Configuration().setInterceptor( new AuditInterceptor() );
如果需要響應(yīng)持久層的某些特殊事件,你也可以使用Hibernate3的事件框架。該事件系統(tǒng)可以用來替代攔截器,也可以作為攔截器的補充來使用。
基本上,Session接口的每個方法都有相對應(yīng)的事件。比如LoadEvent,FlushEvent,等等(查閱XML配置文件的DTD,以及org.hibernate.event包來獲得所有已定義的事件的列表)。當(dāng)某個方法被調(diào)用時,Hibernate Session會生成一個相對應(yīng)的事件并激活所有配置好的事件監(jiān)聽器。系統(tǒng)預(yù)設(shè)的監(jiān)聽器實現(xiàn)的處理過程就是被監(jiān)聽的方法要做的(被監(jiān)聽的方法所做的其實僅僅是激活監(jiān)聽器,“實際”的工作是由監(jiān)聽器完成的)。不過,你可以自由地選擇實現(xiàn)一個自己定制的監(jiān)聽器(比如,實現(xiàn)并注冊用來處理處理LoadEvent的LoadEventListener接口),來負責(zé)處理所有的調(diào)用Session的load()方法的請求。
監(jiān)聽器應(yīng)該被看作是單例(singleton)對象,也就是說,所有同類型的事件的處理共享同一個監(jiān)聽器實例,因此監(jiān)聽器不應(yīng)該保存任何狀態(tài)(也就是不應(yīng)該使用成員變量)。
用戶定制的監(jiān)聽器應(yīng)該實現(xiàn)與所要處理的事件相對應(yīng)的接口,或者從一個合適的基類繼承(甚至是從Hibernate自帶的默認事件監(jiān)聽器類繼承,為了方便你這樣做,這些類都被聲明成non-final的了)。用戶定制的監(jiān)聽器可以通過編程使用Configuration對象來注冊,也可以在Hibernate的XML格式的配置文件中進行聲明(不支持在Properties格式的配置文件聲明監(jiān)聽器)。下面是一個用戶定制的加載事件(load event)的監(jiān)聽器:
public class MyLoadListener extends DefaultLoadEventListener {
// this is the single method defined by the LoadEventListener interface
public Object onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
throws HibernateException {
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
throw MySecurityException("Unauthorized access");
}
return super.onLoad(event, loadType);
}
}
你還需要修改一處配置,來告訴Hibernate以使用選定的監(jiān)聽器來替代默認的監(jiān)聽器。
<hibernate-configuration>
<session-factory>
...
<listener type="load" class="MyLoadListener"/>
</session-factory>
</hibernate-configuration>
看看用另一種方式,通過編程的方式來注冊它。
Configuration cfg = new Configuration();
cfg.getSessionEventListenerConfig().setLoadEventListener( new MyLoadListener() );
通過在XML配置文件聲明而注冊的監(jiān)聽器不能共享實例。如果在多個<listener/>節(jié)點中使用了相同的類的名字,則每一個引用都將會產(chǎn)生一個獨立的實例。如果你需要在多個監(jiān)聽器類型之間共享監(jiān)聽器的實例,則你必須使用編程的方式來進行注冊。
為什么我們實現(xiàn)了特定監(jiān)聽器的接口,在注冊的時候還要明確指出我們要注冊哪個事件的監(jiān)聽器呢?這是因為一個類可能實現(xiàn)多個監(jiān)聽器的接口。在注冊的時候明確指定要監(jiān)聽的事件,可以讓啟用或者禁用對某個事件的監(jiān)聽的配置工作簡單些。
通常,Hibernate應(yīng)用程序的聲明式安全機制由會話外觀層(session facade)所管理?,F(xiàn)在,Hibernate3允許某些特定的行為由JACC進行許可管理,由JAAS進行授權(quán)管理。本功能是一個建立在事件框架之上的可選的功能。
首先,你必須要配置適當(dāng)?shù)氖录O(jiān)聽器(event listener),來激活使用JAAS管理授權(quán)的功能。
<listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/>
<listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/>
<listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>
<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>
接下來,仍然在hibernate.cfg.xml文件中,綁定角色的權(quán)限:
<grant role="admin" entity-name="User" actions="insert,update,read"/>
<grant role="su" entity-name="User" actions="*"/>
這些角色的名字就是你的JACC provider所定義的角色的名字。