使用JAXB创建不可变对象

16

我正在使用JAXB从XSD文件创建Java对象。 我正在创建不可变的包装器来隐藏JAXB生成的对象(之前我更新了JAXB对象以实现不可变接口并将接口返回给客户端。但是后来意识到更改自动生成的类是不好的,因此使用包装器)

目前我正在将这些不可变的包装器返回给客户端应用程序。 是否有任何选项使自动生成的类不可变,并避免创建不可变的包装器的额外工作。 鼓励使用其他方法。

  • 谢谢
5个回答

33
根据JSR-133(Java 1.5依赖项),您可以使用反射来设置未初始化的final变量。因此,您可以在私有构造函数中将其初始化为null,并且无需任何XMLAdapter即可清洁地使用JAXB + immutable。
示例来自https://test.kuali.org/svn/rice/sandbox/immutable-jaxb/,此内容取自Blaise博客上的评论http://blog.bdoughan.com/2010/12/jaxb-and-immutable-objects.html#comment-form_584069422380571931
package blog.immutable;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="customer")
@XmlAccessorType(XmlAccessType.NONE)
public final class Customer {

    @XmlAttribute
    private final String name;

    @XmlElement
    private final Address address;

    @SuppressWarnings("unused")
    private Customer() {
        this(null, null);
    }

    public Customer(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public Address getAddress() {
        return address;
    }

}

1
这应该被标记为最佳答案,它可以节省我们为每个不可变对象编写适配器类和映射类的工作,是一个非常轻量级的解决方案。谢谢! - Nicole Finnie

10

1
这是问题的正确答案,不确定为什么其他答案得到更高的投票。 - Krzysztof Krasoń

2

JAXB可以使用非公共的构造函数/方法,因此唯一可行的方法是将无参数构造函数和setter设置为受保护的,从而得到“伪不可变”对象。

每次手动编写JAXB注释类时,我都选择这种方法,但您也可以检查是否适用于生成的代码。


1

它实际上是在说你需要一个适配器(这是一个JAXB构造)。是的,你需要创建三个类(不可变的、可变的和适配器),但你仍然可以完成它。 - Alessandro Santini
当然可以 - 但是这三个类所需的工作量与编写包装器的工作量相同。 - artbristol
我反对你的说法“没有对不可变对象提供本地支持”,而不是努力等价的事实 :) - Alessandro Santini
@AlessandroSantini 说得好。希望未来的版本能够让它变得更加轻松一些。 - artbristol

-3

在将Bean返回给客户端之前,您可以为其创建代理。您需要javassist从类中创建代理(从接口创建代理可以直接使用Java SE完成)。

然后,如果调用以“set”开头的方法,则可以抛出异常。

这里有一个可重用的类,其中包含一个可以包装“任何”POJO的方法:

import java.lang.reflect.Method;

import javassist.util.proxy.MethodFilter;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.Proxy;
import javassist.util.proxy.ProxyFactory;

public class Utils {

 public static <C> C createInmutableBean(Class<C> clazz, final C instance)
        throws InstantiationException, IllegalAccessException {
    if (!clazz.isAssignableFrom(instance.getClass())) {
        throw new IllegalArgumentException("given instance of class "
                + instance.getClass() + " is not a subclass of " + clazz);
    }
    ProxyFactory f = new ProxyFactory();
    f.setSuperclass(clazz);
    f.setFilter(new MethodFilter() {
        public boolean isHandled(Method m) {
            // ignore finalize()
            return !m.getName().equals("finalize");
        }
    });
    Class c = f.createClass();
    MethodHandler mi = new MethodHandler() {
        public Object invoke(Object self, Method m, Method proceed,
                Object[] args) throws Throwable {
            if (m.getName().startsWith("set")) {
                throw new RuntimeException("this bean is inmutable!");
            }

            return m.invoke(instance, args); // execute the original method
                                                // over the instance
        }
    };
    C proxy = (C) c.newInstance();

    ((Proxy) proxy).setHandler(mi);
    return (C) proxy;
 }
}

以下是一个编程示例代码。让 Employee 成为您的 bean:

public class Employee{
  private String name="John";
  private String surname="Smith";
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getSurname() {
    return surname;
  }
  public void setSurname(String surname) {
    this.surname = surname;
  }
};

这里有一个测试用例,展示了您可以为POJO创建代理,使用其getter方法,但无法使用其setter方法。

@Test
public void testProxy() throws InstantiationException, IllegalAccessException{
    Employee aBean = new Employee();

    //I can modify the bean
    aBean.setName("Obi-Wan");
    aBean.setSurname("Kenobi");

    //create the protected java bean with the generic utility
    Employee protectedBean = Utils.createInmutableBean(Employee.class, aBean);

    //I can read
    System.out.println("Name: "+protectedBean.getName());
    System.out.println("Name: "+protectedBean.getSurname());

    //but I can't modify
    try{
        protectedBean.setName("Luke");
        protectedBean.setSurname("Skywalker");
        throw new RuntimeException("The test should not have reached this line!");
    }catch(Exception e){
        //I should be here
        System.out.println("The exception was expected! The bean should not be modified (exception message: "+e.getMessage()+")");
        assertEquals("Obi-Wan", protectedBean.getName());
        assertEquals("Kenobi", protectedBean.getSurname());
    }
}

7
这是人类有史以来想出的最糟糕的主意。这到底对谁有益?谁想要那些会不时抛出运行时异常的类?原帖作者想要的是通过设计实现不可变类,而不是通过恐惧、不确定性和怀疑来实现。 - DeejUK
我同意你的观点。静态隐藏setter将是更好的解决方案。我的解决方案尝试创建包装器,而不改变生成的bean并且不需要编写更多代码。此外,您可能已经在标准Java API中看到了这样的糟糕想法:http://docs.oracle.com/javase/6/docs/api/java/util/Collections.html#unmodifiableList(java.util.List),或者在Google Collections API中:https://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/collect/ImmutableList.html#set(int, E)。这两个都会抛出运行时异常。 - lipido

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接