自动删除,我们要作的事有三件
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 <key,value> format. For example, when implement toString() for an {@code Item},
* ItemId may be enough for displaying:
*
* <pre>
* public String toString() {
* return "itemId=" + this.getItemId();
* }
* </pre>
*
* But for <code>Recordable</code>, you need to backup more attributes:
*
* <pre>
* public void record(Map<String, Object> content) {
* content.put("UPDATER", getUpdater().getUserID());
* content.put("CREATOR", getCreator().getUserID());
* content.put("CATEGORY", getCategory().getName());
* content.put("CATEGORY1", getCategory1().getName());
* content.put("CATEGORY2", getCategory2().getName());
* StringBuffer uomBuffer = new StringBuffer();
* for (UnitOfMeasure uom : this.getUnitOfMeasure()) {
* if (uom != null) {
* uomBuffer.append(",");
* uomBuffer.append(uom.getUom());
* }
* }
* content.put("UOM", 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 <property name,getter value >
* <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是很合适的。