我正在使用JAXB从XSD文件创建Java对象。 我正在创建不可变的包装器来隐藏JAXB生成的对象(之前我更新了JAXB对象以实现不可变接口并将接口返回给客户端。但是后来意识到更改自动生成的类是不好的,因此使用包装器)
目前我正在将这些不可变的包装器返回给客户端应用程序。 是否有任何选项使自动生成的类不可变,并避免创建不可变的包装器的额外工作。 鼓励使用其他方法。
- 谢谢
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;
}
}
JAXB可以使用非公共的构造函数/方法,因此唯一可行的方法是将无参数构造函数和setter设置为受保护的,从而得到“伪不可变”对象。
每次手动编写JAXB注释类时,我都选择这种方法,但您也可以检查是否适用于生成的代码。
在将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());
}
}