`

Hibernate Annotation driven equals and hashCode

阅读更多

The following implementation of equals, hashcode and toString is using the concept of one or more business keys defined by annotations.The annotation @BusinessKey can be applied with an include/exclude filter on field or method level.

Enumeration for include/exclude filter:

public enum Method {
    ALL, NONE, EQUALS, HASH_CODE, TO_STRING
}

Business key annotation:

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

@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.FIELD, ElementType.METHOD })
public @interface BusinessKey {
    Method[] include() default Method.ALL;
    Method[] exclude() default Method.NONE;
}

Implementation of equals, hashCode and toString using cached reflection:

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

public class BeanUtils {
    private static Map<String, List<AccessibleObject>> cache = new Hashtable<String, List<AccessibleObject>>();

    private BeanUtils() {}

    public static boolean equals(Object obj1, Object obj2) {
        if (obj1 == obj2) {
            return true;
        }

        if (obj2 == null || obj2.getClass() != obj1.getClass()) {
            return false;
        }

        EqualsBuilder builder = new EqualsBuilder();

        for (AccessibleObject ao : getAccessibleObjects(obj1, 1)) {
            try {
                if (ao instanceof Field) {
                    builder.append(((Field) ao).get(obj1), ((Field) ao).get(obj2));
                } else {
                    builder.append(((java.lang.reflect.Method) ao).invoke(obj1, (Object[]) null), ((java.lang.reflect.Method) ao).invoke(obj2, (Object[]) null));
                }
            } catch (Exception e) {}
        }

        return builder.isEquals();
    }

    public static int hashCode(Object obj) {
        HashCodeBuilder builder = new HashCodeBuilder();

        for (AccessibleObject ao : getAccessibleObjects(obj, 2)) {
            try {
                if (ao instanceof Field) {
                    builder.append(((Field) ao).get(obj));
                } else {
                    builder.append(((java.lang.reflect.Method) ao).invoke(obj, (Object[]) null));
                }
            } catch (Exception e) {}
        }

        return builder.toHashCode();
    }

    public static String toString(Object obj) {
        ToStringBuilder builder = new ToStringBuilder(obj, ToStringStyle.SHORT_PREFIX_STYLE);

        for (AccessibleObject ao : getAccessibleObjects(obj, 4)) {
            try {
                if (ao instanceof Field) {
                    builder.append(((Field) ao).getName(), ((Field) ao).get(obj));
                } else {
                    builder.append(((java.lang.reflect.Method) ao).getName(), ((java.lang.reflect.Method) ao).invoke(obj, (Object[]) null));
                }
            } catch (Exception e) {}
        }

        return builder.toString();
    }

    private static List<AccessibleObject> getAccessibleObjects(Object obj, int filter) {
        Class<?> clazz = obj.getClass();

        String name = clazz.getName() + filter;

        if (!cache.containsKey(name)) {
            List<AccessibleObject> aos = new ArrayList<AccessibleObject>();

            do {
                Field[] fields = clazz.getDeclaredFields();

                for (Field field : fields) {
                    BusinessKey bk = field.getAnnotation(BusinessKey.class);
                    if (bk != null && (filter(bk) & filter) == filter) {
                        field.setAccessible(true);
                        aos.add(field);
                    }
                }

                java.lang.reflect.Method[] methods = clazz.getDeclaredMethods();

                for (java.lang.reflect.Method method : methods) {
                    BusinessKey bk = method.getAnnotation(BusinessKey.class);
                    if (bk != null && (filter(bk) & filter) == filter) {
                        method.setAccessible(true);
                        aos.add(method);
                    }
                }

                clazz = clazz.getSuperclass();
            } while (clazz != null);

            Collections.sort(aos, new AccessibleObjectComparator());

            cache.put(name, aos);
        }

        return cache.get(name);
    }

    private static int filter(BusinessKey bk) {
        int filter = 0;

        for (Method method : bk.include()) {
            switch (method) {
            case ALL:
                filter = filter | 7;
                break;
            case EQUALS:
                filter = filter | 1;
                break;
            case HASH_CODE:
                filter = filter | 2;
                break;
            case TO_STRING:
                filter = filter | 4;
                break;
            }
        }

        for (Method method : bk.exclude()) {
            switch (method) {
            case ALL:
                filter -= filter & 7;
                break;
            case EQUALS:
                filter -= filter & 1;
                break;
            case HASH_CODE:
                filter -= filter & 2;
                break;
            case TO_STRING:
                filter -= filter & 4;
                break;
            }
        }

        return filter;
    }

    private static class AccessibleObjectComparator implements Comparator<AccessibleObject> {
        public int compare(AccessibleObject o1, AccessibleObject o2) {
            boolean o1IsField = o1 instanceof Field;
            boolean o2IsField = o2 instanceof Field;

            if (!o1IsField && o2IsField) {
                return 1;
            } else if (o1IsField && !o2IsField) {
                return -1;
            }

            if (o1IsField) {
                return ((Field) o1).getName().compareTo(((Field) o2).getName());
            } else {
                return ((java.lang.reflect.Method) o1).getName().compareTo(((java.lang.reflect.Method) o2).getName());
            }
        }
    }
}

 

Example of usage in a JPA annotated bean:

@Entity
public class User {
    private Long id;
    private String username;
    private byte[] password;
    private Set<Role> roles = new TreeSet<Role>();

    protected User() {}

    public User(String username, String password) {
        this.username = username;
        setPassword(password);
    }

    @BusinessKey(include = Method.TO_STRING)
    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }

    protected void setId(Long id) {
        this.id = id;
    }

    @BusinessKey
    @Column(nullable = false, unique = true)
    public String getUsername() {
        return username;
    }

    public User setUsername(String username) {
        this.username = username;
        return this;
    }

    @BusinessKey
    @Column(length = 32, nullable = false)
    public byte[] getPassword() {
        return password;
    }

    public void setPassword(String password) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            this.password = md.digest(password.getBytes());
        } catch (NoSuchAlgorithmException e) {}
    }

    @ManyToMany
    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
    
    @Override
    public boolean equals(Object obj) {
        return BeanUtils.equals(this, obj);
    }

    @Override
    public int hashCode() {
        return BeanUtils.hashCode(this);
    }

    @Override
    public String toString() {
        return BeanUtils.toString(this);
    }    
}

If you don't mind to use a superclass in your beans the following superclass can be added as a convenience:

public abstract class Bean {
    @Override
    public boolean equals(Object obj) {
        return BeanUtils.equals(this, obj);
    }

    @Override
    public int hashCode() {
        return BeanUtils.hashCode(this);
    }

    @Override
    public String toString() {
        return BeanUtils.toString(this);
    }
}
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics