使用Byte-Buddy监控类属性变化
文章目录
公司的ORM框架在Model类属性变化时候会记录变化的属性,然后在持久化时候只更新有变化的字段。这个功能之前是通过CGLib实现的,但是cglib在2019年之后就不再维护了,之前的代码也已经无法兼容JDK17和JDK21,所以我决定使用Byte-Buddy来重新实现这个功能。
代码如下:
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import lombok.var;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.NamingStrategy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatchers;
import org.jetbrains.annotations.NotNull;
/**
* 监听类的属性变化
*
* @author p_x_c
*/
@Slf4j
public abstract class ObservableEntity implements Serializable {
private static final long serialVersionUID = -705252046123130932L;
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
/**
* 是否有属性变化
*/
@Getter
protected Boolean hasChanged = false;
/**
* 有变化的属性名称
*/
@Getter
protected Set<String> changedProperties = Sets.newHashSet();
/**
* 创建类
*
* @param clazz 类Class
* @param defaultValues 默认属性
* @param listeners 属性变化时的监听器
* @return 类实例
*/
@SneakyThrows
public static <T> T create(Class<T> clazz, Map<String, Object> defaultValues, PropertyChangeListener... listeners) {
T instance = new ByteBuddy()
.with(new NamingStrategy.AbstractBase() {
@Override
protected @NotNull String name(@NotNull TypeDescription superClass) {
return Objects.requireNonNull(superClass.getPackage())
.getName() + ".bytebuddy." + superClass.getSimpleName();
}
})
.subclass(clazz)
.method(ElementMatchers.nameStartsWith("set"))
// .method(ElementMatchers.isSetter())
.intercept(MethodDelegation.to(SetterInterceptor.class))
.make()
.load(clazz.getClassLoader())
.getLoaded()
.getConstructor()
.newInstance();
if (instance instanceof ObservableEntity) {
var observableEntity = (ObservableEntity) instance;
for (PropertyChangeListener listener : listeners) {
observableEntity.addPropertyChangeListener(listener);
}
// 设置默认属性值
for (Map.Entry<String, Object> entry : defaultValues.entrySet()) {
val fieldName = entry.getKey();
var fieldValue = entry.getValue();
var field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
// Wrap collections
fieldValue = observableEntity.wrapCollection(fieldValue, fieldName);
field.set(instance, fieldValue);
// 手动触发属性变化事件
observableEntity.firePropertyChange(fieldName, null, fieldValue);
}
}
return instance;
}
/**
* 创建类,默认打印属性变化
*
* @param clazz 类Class
* @return 类实例
*/
public static <T> T create(Class<T> clazz) {
return create(clazz, Maps.newHashMap());
}
/**
* 创建类,默认打印属性变化
*
* @param clazz 类Class
* @param defaultValues 类的默认值
* @return
*/
public static <T> T create(Class<T> clazz, Map<String, Object> defaultValues) {
return create(clazz, defaultValues, evt -> log.debug("Property changed: " + evt.getPropertyName() + " Old value: " + evt.getOldValue() + " New value: " + evt.getNewValue()));
}
/**
* 添加属性变化监听器
*
* @param listener
*/
private void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
/**
* 包装集合类型的属性,防止List和Map属性变动监听不到
*
* @param value 现有集合对象
* @param propertyName 属性名
* @param <T>
* @return
*/
@SuppressWarnings("unchecked")
private <T> T wrapCollection(T value, String propertyName) {
if (value instanceof List) {
return (T) new ObservableList<>((List<?>) value, pcs, propertyName);
} else if (value instanceof Map) {
return (T) new ObservableMap<>((Map<?, ?>) value, pcs, propertyName);
}
return value;
}
/**
* 触发属性变化事件
*
* @param propertyName 属性名
* @param oldValue 旧值
* @param newValue 新值
*/
private void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
pcs.firePropertyChange(propertyName, oldValue, newValue);
changedProperties.add(propertyName);
hasChanged = true;
}
/**
* set方法拦截器
*/
public static class SetterInterceptor {
@RuntimeType
public static Object intercept(
@This Object obj, // 注入被拦截的目标对象。
@Origin Method method, // 目标方法
@SuperCall Callable<?> zuper, // 调用目标方法,必不可少哦
@AllArguments Object[] args // 注入目标方法的全部参数
) throws Exception {
val sourceName = method.getName()
.substring(3);
val propertyName = Character.toLowerCase(sourceName.charAt(0)) + sourceName.substring(1);
var getter = obj.getClass()
.getMethod("get" + sourceName);
val oldValue = getter.invoke(obj);
val returnValue = zuper.call(); // 调用目标方法
var newValue = getter.invoke(obj);
if (!Objects.equals(oldValue, newValue)) {
// Wrap collections
if (newValue instanceof List || newValue instanceof Map) {
newValue = ((ObservableEntity) obj).wrapCollection(newValue, propertyName);
val optional = Arrays.stream(obj.getClass()
.getMethods())
.filter(x -> x.getName()
.equals("set" + sourceName))
.findAny();
if (optional.isPresent()) {
optional.get()
.invoke(obj, newValue);
}
}
((ObservableEntity) obj).firePropertyChange(propertyName, oldValue, newValue);
}
return returnValue;
}
}
/**
* 包装List
*
* @param <E>
*/
static class ObservableList<E> extends AbstractList<E> {
private final List<E> list;
private final PropertyChangeSupport pcs;
private final String propertyName;
public ObservableList(List<E> list, PropertyChangeSupport pcs, String propertyName) {
this.list = list;
this.pcs = pcs;
this.propertyName = propertyName;
}
@Override
public E get(int index) {
return list.get(index);
}
@Override
public E set(int index, E element) {
E oldValue = list.set(index, element);
pcs.firePropertyChange(propertyName, oldValue, element);
return oldValue;
}
@Override
public void add(int index, E element) {
list.add(index, element);
pcs.firePropertyChange(propertyName, null, element);
}
@Override
public E remove(int index) {
E oldValue = list.remove(index);
pcs.firePropertyChange(propertyName, oldValue, null);
return oldValue;
}
@Override
public int size() {
return list.size();
}
}
/**
* 包装Map
*
* @param <K>
* @param <V>
*/
static class ObservableMap<K, V> extends AbstractMap<K, V> {
private final Map<K, V> map;
private final PropertyChangeSupport pcs;
private final String propertyName;
public ObservableMap(Map<K, V> map, PropertyChangeSupport pcs, String propertyName) {
this.map = map;
this.pcs = pcs;
this.propertyName = propertyName;
}
@Override
public V put(K key, V value) {
V oldValue = map.put(key, value);
pcs.firePropertyChange(propertyName, oldValue, value);
return oldValue;
}
@Override
public V remove(Object key) {
V oldValue = map.remove(key);
pcs.firePropertyChange(propertyName, oldValue, null);
return oldValue;
}
@Override
public @NotNull Set<Entry<K, V>> entrySet() {
return map.entrySet();
}
}
}
使用方式:
@Data
@Accessors(chain = true)
public class Person extends ObservableEntity {
private String name;
private int age;
private List<String> hobbies;
private Map<String, String> attributes;
public static void main(String[] args) {
Map<String, Object> defaultValues = new HashMap<>();
defaultValues.put(
"name", "Default Name");
defaultValues.put("age", 25);
defaultValues.put("hobbies", Lists.newArrayList("a"));
defaultValues.put("attributes", Maps.newHashMap());
Person person = Person.create(Person.class, defaultValues);
System.out.println(person.getClass()
.getName());
Person person2 = Person.create(Person.class);
System.out.println(person.getChangedProperties());
System.out.println(person.getHasChanged());
System.out.println(person2.getHasChanged());
System.out.println(person2.getChangedProperties());
person2.setName("ccc");
System.out.println(person2.getChangedProperties());
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
System.out.println("Hobbies: " + person.getHobbies());
System.out.println("Attributes: " + person.getAttributes());
person.setName("Alice");
person.setAge(30);
person.setHobbies(Lists.newArrayList("Swimming"));
System.out.println("Hobbies: " + person.getHobbies());
person.getHobbies()
.add("Pingpong"); // This will trigger a property change event
System.out.println("Hobbies: " + person.getHobbies());
person.getHobbies()
.set(0, "x");
person.setAttributes(Maps.newHashMap());
System.out.println("Attributes: " + person.getAttributes());
person.getAttributes()
.put("c", "c");
System.out.println("Hobbies: " + person.getHobbies());
person.getHobbies()
.addAll(Lists.newArrayList("xx"));
System.out.println("Hobbies: " + person.getHobbies());
person.getHobbies()
.clear();
System.out.println("Hobbies: " + person.getHobbies());
System.out.println(person.getChangedProperties());
System.out.println(person.getHasChanged());
System.out.println(person2.getHasChanged());
System.out.println(person2.getChangedProperties());
}
}
文章作者 pengxiaochao
上次更新 2024-05-30
许可协议 不允许任何形式转载。