如何自动备份删除的对象(2)

自动删除,我们要作的事有三件

 1) 如何从要删除的Domain对象中获得需要备份的信息M

 2) 这个信息M如何持久化

 3) 持久化的数据如何查询。

 

后两者其实有多种选择,比如可以持久化到文件、数据库或者数据库+文件,随着持久化介质的不同,查询机制也不一样。这里首先讨论第一个方面。

为了作一个通用的框架,希望能通过meta data的方式简化收集信息的过程。那么就有三种方式可供选择:interface,annotation && XML,这也是目前广泛应用的各种框架如Spring,Hibernate等提供的方式。这其中,interface可以实现比较复杂的逻辑,annotation使用相对简便,而XML是最没有倾入的。手上的项目暂时不考虑移植,所以先实现了interface+annotation方式

 

首先来看annotation, 我定义个一个叫Record的Annotation。

 

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * <code>Record</code> is an annotation used on bean object to specify that bean can be recorded automatically at some
 * stage (for example, deletion). Generally speaking, the bean can produce a <code>java.util.Map</code> and record
 * itself into it. The key of that map is property name, and the value is the object gotten by getter.
 * <p>
 * You may use <code>Record</code> on class or method level, but any <code>Record</code> on a method which is not getter
 * will be ignored. And, if an object is not marked as <code>Record</code> on class level, all <code>Record</code>
 * annotations on method level will be <b>IGNORED</b> either, e.g. it will not be recorded automatically at all. So,
 * make sure to use this annotation on class level.
 * <p>
 * You can specify <code>ignore</code> when using <code>Record</code>. On class level, <code>@Record(ignore=true)</code>
 * means all getters will be ignored unless it is marked as <code>@Record</code> explicitly, vice versa. On method
 * level, <code>@Record</code> impact its host only. The default value of <code>ignore</code> is false.
 * <p>
 * If the bean implements <code>Recordable</code>, you are able to edit the <code>java.util.Map</code> returned by
 * <code>Record</code>.
 * 
 * @author JINLO
 * @see Recordable
 * @since 3.1
 */
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.TYPE })
public @interface Record {
    boolean ignore() default false;
}

    以及一个叫Recordable的interface

 

import java.util.Map;

/**
 * <code>Recordable</code> is used to specify a Object can be recorded at some stage(for example,deletion), a object can
 * record itself by implements <code>void record(Map<String, Object> content)</code> function.
 * 
 * @author JINLO
 * @since 3.1
 * @see Record
 */
public interface Recordable {
    /**
     * Write information you want to backup. This function is something like {@code toString()} function but with
     * slightly difference.
     * <p>
     * {@code toString()} is designed for display purpose, but {@code record} is designed for backup, and the backup
     * information is organized by &lt;key,value&gt; format. For example, when implement toString() for an {@code Item},
     * ItemId may be enough for displaying:
     * 
     * <pre>
     * public String toString() {
     *     return &quot;itemId=&quot; + this.getItemId();
     * }
     * </pre>
     * 
     * But for <code>Recordable</code>, you need to backup more attributes:
     * 
     * <pre>
     * public void record(Map&lt;String, Object&gt; content) {
     *     content.put(&quot;UPDATER&quot;, getUpdater().getUserID());
     *     content.put(&quot;CREATOR&quot;, getCreator().getUserID());
     *     content.put(&quot;CATEGORY&quot;, getCategory().getName());
     *     content.put(&quot;CATEGORY1&quot;, getCategory1().getName());
     *     content.put(&quot;CATEGORY2&quot;, getCategory2().getName());
     *     StringBuffer uomBuffer = new StringBuffer();
     *     for (UnitOfMeasure uom : this.getUnitOfMeasure()) {
     *         if (uom != null) {
     *             uomBuffer.append(&quot;,&quot;);
     *             uomBuffer.append(uom.getUom());
     *         }
     *     }
     *     content.put(&quot;UOM&quot;, uomBuffer.toString().substring(2));
     * }
     * </pre>
     * 
     * @param writer
     */
    void record(Map<String, Object> content);
}
 

 

     这里说明一下,Record是希望放在Class或者Getter上的。一旦一个Class被加上Record,缺省记录所有的可读属性,除非在某个Getter上声明 Record( ignore=true)。当然也可以把Class设为Record (ignore=true),这样缺省不会记录任何属性,除非某一个Getter被声明为Record。这样对String,primitive type或boxing type,不需要太多标注就可以搞定一个Class了。

 

     Recordable用来实现自定义抓取过程,把需删除的对象信息记录在一个Map中传给record方法。比如对于Order对象的orderItems,如果你只想记录这个Order有多少Item,而不想记录具体的Item,就可以这么写

 

@Record
class Order implements Recordable{
    //orderNo will be recorded 
    public String getOrderNo(){
        //..
    
    }
    
    @Record(ignore=true)
    public List<OrderItem> getOrderItems(){
        //...
    }
    
    void record(Map<String, Object> content){
        content.put("itemSize", getOrderItems.size());
    }
    
}
 

    当然,还要有一个相应的工具类来处理annotation和interface

 

 

import java.lang.reflect.Method;

/**
 * A helper class for <code>Record</code> annotation. It contains information retrieved by reflection which can be used
 * to record a class. This class may be cached for performance consideration
 * 
 * @author JINLO
 * @see Record
 * @since 3.1
 * 
 */
public class RecordDescriptor {
    public static class RecordMethod {
        public RecordMethod(Method readMethod, String propertyName) {
            if (readMethod == null) {
                throw new IllegalArgumentException("readMethod is null");
            }

            if (propertyName == null) {
                throw new IllegalArgumentException("propertyName is null");
            }

            this.readMethod = readMethod;
            this.propertyName = propertyName;
        }

        private Method readMethod;

        private String propertyName;

        public Method getReadMethod() {
            return readMethod;
        }

        public String getPropertyName() {
            return propertyName;
        }

        public int hashCode() {
            return readMethod.hashCode() + 31 * propertyName.hashCode();
        }

        public boolean equals(Object o) {
            if (o == this) {
                return true;
            }

            if (o == null) {
                return false;
            }

            if (o instanceof RecordMethod) {
                RecordMethod target = (RecordMethod) o;
                return this.readMethod.equals(target.readMethod) && this.propertyName.equals(target.propertyName);
            }

            return false;
        }

        public String toString() {
            return "readMethod=" + readMethod + ",propertyName=" + propertyName;
        }
    }

    public RecordDescriptor(Class<?> referenceClass, boolean enable, RecordMethod[] getters) {
        this.referenceClass = referenceClass;
        this.enable = enable;
        this.getters = getters;
    }

    private Class<?> referenceClass;

    private boolean enable;

    private RecordMethod[] getters;

    /**
     * The reference class this <code>RecordDescriptor</code> describes
     * 
     * @return
     */
    public Class<?> getReferenceClass() {
        return referenceClass;
    }

    /**
     * Is the reference class marked <code>Record</code>
     * 
     * @return
     */
    public boolean isEnable() {
        return enable;
    }

    /**
     * Get all get methods should be recorded of reference class, including <code>Method</code> and propertyName
     * 
     * @return empty array if no valid get method
     */
    public RecordMethod[] getGetters() {
        return getters;
    }

}
 

 

import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.oocl.wos.frm.domain.record.RecordDescriptor.RecordMethod;
import com.oocl.wos.frm.utils.BeanUtils;

/**
 * Some utilities for implement <code>Recode</code>
 * 
 * @author JINLO
 * @since 3.1
 */
public class RecordUtils {
    private final static RecordUtils instance = new RecordUtils();

    private static Map<Class<?>, RecordDescriptor> descriptorCache = new HashMap<Class<?>, RecordDescriptor>();

    private final static RecordMethod[] RECORD_METHOD_ARRAY = new RecordMethod[] {};

    private final static RecordDescriptor NULL_DESCRIPTOR = new RecordDescriptor(null, false, RECORD_METHOD_ARRAY);

    public final static RecordUtils instance() {
        return instance;
    }

    private RecordUtils() {

    }

    /**
     * Get all <code>RecordeDescriptor</code> for given class, this function will cache result for performance
     * consideration.
     * 
     * @param clz
     * @return
     */
    public synchronized RecordDescriptor getRecordDescriptors(Class<?> clz) {
        if (clz == null) {
            throw new NullPointerException("can not get read methods of class <null>");
        }
        RecordDescriptor result = descriptorCache.get(clz);
        if (result == null) {
            Record classAnnotation = clz.getAnnotation(Record.class);
            if (classAnnotation != null) {
                List<RecordMethod> readProperties = new ArrayList<RecordMethod>();
                for (PropertyDescriptor readableProperty : BeanUtils.instance().getReadableProperties(clz)) {
                    Record methodAnnotation = readableProperty.getReadMethod().getAnnotation(Record.class);
                    boolean ignore = (methodAnnotation == null) ? classAnnotation.ignore() : methodAnnotation
                            .ignore();

                    if (!ignore) {
                        readProperties.add(new RecordMethod(readableProperty.getReadMethod(), readableProperty
                                .getName()));
                    }
                }

                result = new RecordDescriptor(clz, true, readProperties.toArray(RECORD_METHOD_ARRAY));
            } else {
                result = NULL_DESCRIPTOR;
            }
            descriptorCache.put(clz, result);
        }
        return result;
    }

    /**
     * Record an object into Map in two steps:
     * <ol>
     * <li>If it is marked with<code>Record</code>, all valid get methods on that object will be recorded in a format
     * like &lt;property name,getter value &gt;
     * <li>Then, if it implements <code>Recordable</code>, the map filled in above setp will be passed to {@code
     * Recordable.record(Map<String, Object>)} function, process in a customized way.
     * </ol>
     * 
     * @param obj
     * @return null - obj is null or obj should not be recorded
     */
    public Map<String, Object> toContent(Object obj) {

        if (obj == null) {
            return null;
        }
        Map<String, Object> result = null;
        RecordDescriptor delDesc = getRecordDescriptors(obj.getClass());
        if (delDesc.isEnable()) {
            result = new HashMap<String, Object>();
            for (RecordMethod getter : delDesc.getGetters()) {
                String name = getter.getPropertyName();
                Object value = BeanUtils.instance().executeReadMethod(obj, getter.getReadMethod());
                result.put(name, value == null ? null : value.toString());
            }
        }

        if (obj instanceof Recordable) {
            Recordable dr = (Recordable) obj;
            if (result == null) {
                result = new HashMap<String, Object>();
            }
            dr.record(result);
        }

        return result;
    }

}

 

    RecordUtils类使用了apache的BeanUitls,并且在自己内部缓存了每个类的RecordDescriptor对象,以避免过多的反射。

 

     至此,对于任意obj,只要调用

Map<String,Object> content=RecordUtils.toContent(obj);

    就可以得到其中所有的信息了,下面所要作的就是将这些信息持久化而已,这将在下一篇描述。

 

    采用interface+annotation的方法,我们成功的将“如何记录自己”这件事交给了对象自身。这已经有了点rich domain的意味。相对来说 rich domain意味着更好/更符合直觉的控制,对于record这种与对象自身紧密相关的行为,采用rich domain是很合适的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值