使用JAXB创建不可变对象

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

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

  • 谢谢

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

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

这是一个可重用的类,其方法可以包装“任何”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 createInmutableBean(Class 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; } } 

这是一个示例代码。 让员工成为你的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()); } } 

从JSR-133(Java 1.5依赖)开始,您可以使用reflection来设置未初始化的最终变量。 所以你可以在私有构造函数中初始化为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#带注释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; } } 

您可以使用这些XJC编译器插件直接生成不可变类:

JAXB可以使用非公共构造函数/方法,因此唯一可行的方法是保护no-arg构造函数和setter,最后得到“伪不可变”对象。

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

基于Blaise Doughan的博客文章http://blog.bdoughan.com/2010/12/jaxb-and-immutable-objects.html (他对JAXB了解很多),看起来没有对不可变对象的原生支持,所以你的包装器对象是必要的。