Hibernate.org Community Documentation

- 上一页
- 下一页

## 第 14 章 拦截器与事件（Interceptors and events）

应用程序能够响应 Hibernate 内部产生的特定事件是非常有用的。这样就允许实现某些通用的功能以及允许对 Hibernate 功能进行扩展。

## 14.1. 拦截器（Interceptors）

`Interceptor` 接口提供了从会话（session）回调（callback）应用程序（application）的机制， 这种回调机制可以允许应用程序在持久化对象被保存、更新、删除或是加载之前，检查并（或）修改其 属性。一个可能的用途，就是用来跟踪审核（auditing）信息。例如：下面的这个`拦截器`，会在一个实现了 `Auditable` 接口的对象被创建时自动地设置 `createTimestamp` 属性，并在实现了 `Auditable` 接口的对象被更新时，同步更新 `lastUpdateTimestamp` 属性。

你可以直接实现 `Interceptor` 接口，也可以（最好）继承自 `EmptyInterceptor`。

    package org.hibernate.test;

    import java.io.Serializable;

    import java.util.Date;

    import java.util.Iterator;

    import org.hibernate.EmptyInterceptor;

    import org.hibernate.Transaction;

    import org.hibernate.type.Type;

    public class AuditInterceptor extends EmptyInterceptor {

        private int updates;

        private int creates;

        private int loads;

        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) {

            if ( entity instanceof Auditable ) {

                loads++;

            }

            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 afterTransactionCompletion(Transaction tx) {

            if ( tx.wasCommitted() ) {

                System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads);

            }

            updates=0;

            creates=0;

            loads=0;

        }

    }

拦截器可以有两种：`Session` 范围内的和 `SessionFactory` 范围内的。

当使用某个重载的 SessionFactory.openSession() 使用 `Interceptor` 作为参数调用打开一个 session 的时候，就指定了 `Session` 范围内的拦截器。

    Session session = sf.openSession( new AuditInterceptor() );

`SessionFactory` 范围内的拦截器要通过 `Configuration` 中注册，而这必须在创建 `SessionFactory` 之前。在这种情况下，给出的拦截器会被这个 `SessionFactory` 所打开的所有 session 使用了；除非 session 打开时明确指明了使用的拦截器。`SessionFactory` 范围内的拦截器，必须是线程安全的。确保你没有保存 session 专有的状态，因为多个 session 可能并发使用这个拦截器。

    new Configuration().setInterceptor( new AuditInterceptor() );

## 14.2. 事件系统（Event system）

如果需要响应持久层的某些特殊事件，你也可以使用 Hibernate3 的事件框架。该事件系统可以用来替代拦截器，也可以作为拦截器的补充来使用。

基本上，`Session` 接口的每个方法都有相对应的事件。比如 `LoadEvent`，`FlushEvent`，等等（查阅 XML 配置文件的 DTD，以及 `org.hibernate.event` 包来获得所有已定义的事件的列表）。当某个方 法被调用时，Hibernate `Session`会生成一个相对应的事件并激活所 有配置好的事件监听器。系统预设的监听器实现的处理过程就是被监听的方法要做的（被监听的方法所做的其实仅仅是激活监听器，“实际”的工作是由监听器完成的）。不过，你可以自由地选择实现 一个自己定制的监听器（比如，实现并注册用来处理处理 `LoadEvent` 的 `LoadEventListener` 接口）， 来负责处理所有的调用 `Session` 的 `load()` 方法的请求。

监听器应该被看作是单例（singleton）对象，也就是说，所有同类型的事件的处理共享同一个监听器实例，因此监听器不应该保存任何状态（也就是不应该使用成员变量）。

用户定制的监听器应该实现与所要处理的事件相对应的接口，或者从一个合适的基类继承（甚至是从 Hibernate 自带的默认事件监听器类继承，为了方便你这样做，这些类都被声明成 non-final 的了）。用户定制的监听器可以通过编程使用 `Configuration` 对象 来注册，也可以在Hibernate的 XML 格式的配置文件中进行声明（不支持在 Properties 格式的配置文件声明监听器）。下面是一个用户定制的加载事件（load event）的监听器：

    public class MyLoadListener implements LoadEventListener {

        // this is the single method defined by the LoadEventListener interface

        public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)

                throws HibernateException {

            if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {

                throw MySecurityException("Unauthorized access");

            }

        }

    }

你还需要修改一处配置，来告诉 Hibernate，除了默认的监听器，还要附加选定的监听器。

    <hibernate-configuration>

        <session-factory>

            ...

            <event type="load">

                <listener class="com.eg.MyLoadListener"/>

                <listener class="org.hibernate.event.def.DefaultLoadEventListener"/>

            </event>

        </session-factory>

    </hibernate-configuration

    >

或者，你可以通过编程的方式来注册它：

    Configuration cfg = new Configuration();

    LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };

    cfg.EventListeners().setLoadEventListeners(stack);

通过在XML配置文件声明而注册的监听器不能共享实例。如果在多个 `<listener/>` 节点中使用 了相同的类的名字，则每一个引用都将会产生一个独立的实例。如果你需要在多个监听器类型之间共享 监听器的实例，则你必须使用编程的方式来进行注册。

为什么我们实现了特定监听器的接口，在注册的时候还要明确指出我们要注册哪个事件的监听器呢？ 这是因为一个类可能实现多个监听器的接口。在注册的时候明确指定要监听的事件，可以让启用或者禁用对某个事件的监听的配置工作简单些。

## 14.3. Hibernate 的声明式安全机制

通常，Hibernate 应用程序的声明式安全机制由会话外观层（session facade）所管理。现在，Hibernate3允许某些特定的行为由 JACC 进行许可管理，由 JAAS 进行授权管理。本功能是一个建立在事件框架之上的可选的功能。

首先，你必须要配置适当的事件监听器（event listener），来激活使用 JAAS 管理授权的功能。

    <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"/>

注意，`<listener type="..." class="..."/>` 只是 `<event type="..."><listener class="..."/></event>` 的简写，对每一个事件类型都必须严格的有一个监听器与之对应。

接下来，仍然在 `hibernate.cfg.xml` 文件中，绑定角色的权限：

    <grant role="admin" entity-name="User" actions="insert,update,read"/>

    <grant role="su" entity-name="User" actions="*"/>

这些角色的名字就是你的 JACC provider 所定义的角色的名字。

版权 © 2004 Red Hat, Inc.

- 上一页第 13 章 事务和并发
- 上一级
- 起始页
- 下一页第 15 章 批量处理（Batch processing）