我需要使用Java在运行时创建对象的不可变副本。我使用了org.springframework.cglib.beans.ImmutableBean,它可以使用CGLIB创建对象的不可变副本。
但问题在于它提供了第一级"不变性:它不允许更改输入对象的属性,但它允许更改内部对象(例如,获取集合并向其添加元素或获取内部对象并对其进行修改' s参数等。)
所以问题是:创建一个对象的深度(递归)不可变副本的正确方法是什么,这样一个人也无法改变内部对象(在任何嵌套级别)?
答案 0 :(得分:4)
您可以遍历对象树并使用CGLIB通过使用跳过所需方法的拦截器使每个对象不可变。但最困难的部分是确定修改对象状态的所有方法 - 对于树中的每个对象。
package ut.test;
import static org.junit.Assert.assertEquals;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import com.google.common.collect.Lists;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class MyTest {
public static class Inner {
private String data = "hello";
public Inner() {}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return data;
}
}
public static class Outer {
private List<Inner> list = Lists.newArrayList(new Inner());
public Outer() {}
public List<Inner> getList() {
return list;
}
public void setList(List<Inner> list) {
this.list = list;
}
}
public static class GetOnlyDelegatingMethodInterceptor implements MethodInterceptor {
private Object delegate;
public GetOnlyDelegatingMethodInterceptor(Object delegate) {
this.delegate = delegate;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (method.getName().startsWith("get")) {
return makeImmutable(proxy.invoke(delegate, args));
}
if (method.getName().equals("toString")) {
return proxy.invoke(delegate, args);
}
if (method.getDeclaringClass().equals(Object.class)) {
return proxy.invoke(delegate, args);
}
// you may check for other methods here
// skip all others
return null;
}
}
private static Object makeImmutable(Object obj) {
if (obj == null) {
return obj;
}
Enhancer e = new Enhancer();
e.setSuperclass(obj.getClass());
e.setCallback(new GetOnlyDelegatingMethodInterceptor(obj));
return e.create();
}
@Test
public void testImmutable() {
Outer outerImmutable = (Outer) makeImmutable(new Outer());
// this is initial state
assertEquals(outerImmutable.getList().toString(), "[hello]");
// trying to set empty list
outerImmutable.setList(new ArrayList<>());
// but it's still the same
assertEquals(outerImmutable.getList().toString(), "[hello]");
// going deeper
outerImmutable.getList().get(0).setData("bye!");
// but still no changes
assertEquals(outerImmutable.getList().toString(), "[hello]");
}
}
答案 1 :(得分:0)
您可以使用reflection-util库中的ImmutableProxy
。
示例:
public class Inner
{
private String data = "hello";
// getters and setters
}
public class Outer
{
private List<Inner> list = Arrays.asList(new Inner());
// getters and setters
}
Outer outerImmutable = ImmutableProxy.create(new Outer());
Inner firstElement = outerImmutable.getList().get(0)
// this is initial state
assertThat(firstElement.getData()).isEqualTo("hello");
// throws UnsupportedOperationException
outerImmutable.setList(…);
// throws UnsupportedOperationException
firstElement.setData("bye!");