在运行时创建 DEEP 不可变对象
Create DEEP immutable object in runtime
我需要使用 Java 在运行时创建对象的不可变副本。我使用了 org.springframework.cglib.beans.ImmutableBean,它可以使用 CGLIB 创建对象的不可变副本。
但问题在于它提供了 "first-level" 不变性:它不允许更改输入对象的属性,但是它允许更改内部对象(例如获取集合和向其中添加一个元素或获取内部对象并修改它的参数等)
所以问题是:创建对象的深层(递归)不可变副本的正确方法是什么,以便也不能更改内部对象(在任何嵌套级别)?
您可以遍历对象树并使用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]");
}
}
您可以使用 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!");
我需要使用 Java 在运行时创建对象的不可变副本。我使用了 org.springframework.cglib.beans.ImmutableBean,它可以使用 CGLIB 创建对象的不可变副本。
但问题在于它提供了 "first-level" 不变性:它不允许更改输入对象的属性,但是它允许更改内部对象(例如获取集合和向其中添加一个元素或获取内部对象并修改它的参数等)
所以问题是:创建对象的深层(递归)不可变副本的正确方法是什么,以便也不能更改内部对象(在任何嵌套级别)?
您可以遍历对象树并使用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]");
}
}
您可以使用 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!");