由于HibernateProxy,无法序列化对象。

62
我从服务器得到了以下错误响应:

HTTP状态500 -

类型的异常报告

信息

描述服务器遇到一个内部错误(),阻止它完成这个请求。

例外

javax.servlet.ServletException: java.lang.UnsupportedOperationException:试图序列化 java.lang.Class:org.hibernate.proxy.HibernateProxy。忘记注册类型适配器?

根本原因

java.lang.UnsupportedOperationException:尝试序列化 java.lang.Class:org.hibernate.proxy.HibernateProxy。忘记注册类型适配器?

来自Java调试器的信息:
org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer@7632012e

我正在使用Gson将我的Java对象转换为JSON。以下是我复制粘贴的一些代码。

这是我的资源:

@Stateless
@LocalBean
@Path("/autos")
@Produces(MediaType.APPLICATION_JSON)
public class AutoResource {

    @EJB
    private CarAssembler warehouse;
    @Context
    private UriInfo uriInfo;

    @GET
    public Response allAutos() {
        // Building a context, lots of code...
        // Creating a Gson instance and configures it...

        final Auto auto = warehouse.list(context);
        final String autoJson = gson.toJson(auto);

        return Response.ok(autoJson).build();
    }
}

CarAssembler是一个调用仓库的服务。我没有在这里粘贴服务的代码。

仓库:

@Override
public Question findById(final int id, final FetchType fetchType) {

    final Auto question = getEntityManager().find(Auto.class, id);

    if (fetchType == FetchType.LAZY) {
        return auto;
    }

    Hibernate.initialize(auto.getManufacturer());
    Hibernate.initialize(auto.getAssemblyHouse());

    return auto;
}

正如您所看到的,我提供了对象的惰性和及时加载。我使用Hibernate.initialize来急切获取JPA关联。然而,问题是我该如何解决我遇到的代理错误。为什么只有AssemblyHouse仍然附加到JavaAssist,而Manufacturer没有(我已在Java调试器中看到类型)?我如何知道何时取消代理对象?我应该取消此自动化可能具有的所有关联吗?在我的代码的哪个层面?当我取消代理时,它会影响我的应用程序的性能吗?是否有其他解决方案?我从错误消息中看到我可以制作一个类型适配器。是的,我可以这样做,但是我必须为所有域对象执行此操作,以确保转换正确完成。也许当我尝试将其转换为JSON表示形式时,我的域中的其他对象也开始失败,但我不知道何时或为什么。其他对象仅仅是幸运吗?
这是我取消代理对象的方式,但我尚未实现它,因为我不知道这是好还是坏,并且在哪个层面上进行,以及我应该何时执行它。我应该一直取消代理对象吗?
public class HibernateUtilities {

    public static <T> T unproxy(T proxy) {
        if (proxy == null) {
            return null;
        }

        if (proxy instanceof HibernateProxy) {
            Hibernate.initialize(proxy);

            HibernateProxy hibernateProxy = (HibernateProxy) proxy;
            T unproxiedObject = (T) hibernateProxy.getHibernateLazyInitializer().getImplementation();

            return unproxiedObject;
        }

        return proxy;
    }
}

如您所需,以下是堆栈跟踪:

[#|2012-11-22T17:17:13.285+0100|WARNING|glassfish3.1.2|javax.enterprise.system.container.web.com.sun.enterprise.web|_ThreadID=71;_ThreadName=Thread-8;|StandardWrapperValve[javax.ws.rs.core.Application]: PWC1406: Servlet.service() for servlet javax.ws.rs.core.Application threw exception java.lang.UnsupportedOperationException: 尝试序列化java.lang.Class: org.hibernate.proxy.HibernateProxy。忘记注册类型适配器了吗? at com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:64) at com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:61) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.ArrayTypeAdapter.write(ArrayTypeAdapter.java:93) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:96) at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:60) at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195) at com.google.gson.Gson.toJson(Gson.java:586) at com.google.gson.Gson.toJson(Gson.java:565) at com.google.gson.Gson.toJson(Gson.java:520) at com.myapp.AutoResource.produceAuto(AutoResource.java:48) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at org.glassfish.ejb.security.application.EJBSecurityManager.runMethod(EJBSecurityManager.java:1052) at org.glassfish.ejb.security.application.EJBSecurityManager.invoke(EJBSecurityManager.java:1124) at com.sun.ejb.containers.BaseContainer.invokeBeanMethod(BaseContainer.java:5388) at com.sun.ejb.EjbInvocation.invokeBeanMethod(EjbInvocation.java:619) at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:800) at com.sun.ejb.EjbInvocation.proceed(EjbInvocation.java:571) at com.sun.ejb.containers.interceptors.SystemInterceptorProxy.doAround(SystemInterceptorProxy.java:162) at com.sun.ejb.containers.interceptors.SystemInterceptorProxy.aroundInvoke(SystemInterceptorProxy.java:144) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:861) at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:800) at com.sun.ejb.containers.interceptors.InterceptorManager.intercept(InterceptorManager.java:370) at com.sun.ejb.containers.BaseContainer.__intercept(BaseContainer.java:5360) at com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java

1
你还记得当你学习如何构建应用程序时,有人向你解释过分层的概念吗?当你在同一个类中混合表示层、业务逻辑和数据库逻辑时,就会出现这种情况(这些注释会泄漏逻辑到你的类中,它们并不像你想象的那么透明)。 - Augusto
2
@Augusto 你认为这些代码片段都来自同一个类吗? - Andreas
我并不完全相信你的问题出在“Manufacturer”上。尝试使用final String autoJson = gson.toJson(auto, Auto.class);进行序列化。如果还是不行,能否把堆栈跟踪信息发出来? - Flavio
这是调试器中唯一类型为HibernateProxy的类,这就是问题所在。当我从序列化中排除此属性时,它也能很好地工作。 - Andreas
从堆栈跟踪中看起来,您在层次结构较低的数组中遇到了一些问题。您能验证一下是什么问题吗? - Flavio
说实话,我不知道那与此有什么关系。 - Andreas
7个回答

97

您可以使用自定义的TypeAdapter来避免手动取消代理所有内容。

/**
 * This TypeAdapter unproxies Hibernate proxied objects, and serializes them
 * through the registered (or default) TypeAdapter of the base class.
 */
public class HibernateProxyTypeAdapter extends TypeAdapter<HibernateProxy> {

    public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
        @Override
        @SuppressWarnings("unchecked")
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            return (HibernateProxy.class.isAssignableFrom(type.getRawType()) ? (TypeAdapter<T>) new HibernateProxyTypeAdapter(gson) : null);
        }
    };
    private final Gson context;

    private HibernateProxyTypeAdapter(Gson context) {
        this.context = context;
    }

    @Override
    public HibernateProxy read(JsonReader in) throws IOException {
        throw new UnsupportedOperationException("Not supported");
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    @Override
    public void write(JsonWriter out, HibernateProxy value) throws IOException {
        if (value == null) {
            out.nullValue();
            return;
        }
        // Retrieve the original (not proxy) class
        Class<?> baseType = Hibernate.getClass(value);
        // Get the TypeAdapter of the original class, to delegate the serialization
        TypeAdapter delegate = context.getAdapter(TypeToken.get(baseType));
        // Get a filled instance of the original class
        Object unproxiedValue = ((HibernateProxy) value).getHibernateLazyInitializer()
                .getImplementation();
        // Serialize the value
        delegate.write(out, unproxiedValue);
    }
}

要使用它,您必须先注册:

GsonBuilder b = new GsonBuilder();
...
b.registerTypeAdapterFactory(HibernateProxyTypeAdapter.FACTORY);
...
Gson gson = b.create();

请注意,这将递归地初始化对象层次结构中的每个代理; 但由于您必须序列化整个数据,因此无论如何都应该这样做。

它是如何工作的?

GSON包含许多TypeAdapterFactory实现,用于各种类型(原始类型、常见类型,如StringDate,列表,数组...)。 每个工厂被问询是否能够序列化某个Java类型(create方法的参数是TypeToken而不是Class,以便捕获有关泛型类型的可能信息,Class没有) 。 如果工厂能够序列化/反序列化类型,则使用TypeAdapter实例进行响应; 否则,它会使用null响应。

HibernateProxyTypeAdapter.FACTORY验证type是否实现了HibernateProxy ; 在这种情况下,它返回一个HibernateProxyTypeAdapter实例进行序列化。 当需要对实际对象进行序列化时,将调用write方法; 适配器提取底层对象的原始类型,并要求GSON获取原始类型的标准TypeAdapter,通常是一个ReflectiveTypeAdapter

然后它检索原始类的实例,而不是直接使用代理。这是必要的,因为ReflectiveTypeAdapter直接访问字段,而不是使用getter;访问代理对象的字段不起作用,并且是经典的Hibernate陷阱

作为可能的性能改进,委托TypeAdapter应在create方法中获取。我发现在代理Class上调用getSuperclass()似乎会产生原始基类。然后代码可以变成:

public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
    @Override
    @SuppressWarnings("unchecked")
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        return (HibernateProxy.class.isAssignableFrom(type.getRawType())
                ? (TypeAdapter<T>) new HibernateProxyTypeAdapter((TypeAdapter)gson.getAdapter(TypeToken.get(type.getRawType().getSuperclass()))) 
     : null);
    }
};
private final TypeAdapter<Object> delegate;

private HibernateProxyTypeAdapter(TypeAdapter<Object> delegate) {
    this.delegate = delegate;
}

@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public void write(JsonWriter out, HibernateProxy value) throws IOException {
    if (value == null) {
        out.nullValue();
        return;
    }
    delegate.write(out, ((HibernateProxy) value).getHibernateLazyInitializer()
            .getImplementation());
}

我在代码中添加了一些注释和(有点冗长的)解释! - Flavio
3
这是一个相当古老的答案,但我想留言。当我传递单个对象时,这段代码会导致堆栈溢出异常。对于序列化List类型,它可以正常工作。 - christopher
3
谢谢你的回答,但它会导致堆栈溢出错误! - Shady Mohamed Sherif
感谢@Flavio!!!! 对于那些遇到堆栈溢出的人:请检查您的可序列化类中是否有双向关系。一年前我制作restful web服务时,解析具有双向关联的类时遇到了堆栈溢出问题。这些循环引用导致了溢出。 - joninx
提供 java.lang.StackOverflowError - Madushan Perera
显示剩余6条评论

4

看到你提到使用急切加载仍然出现错误,那么问题可能不是Hibernate,而可能是GSON实现上的。我认为在创建JSON时需要一个Type,不确定是否已注册,但可能类似于以下内容:

public String autosToJson(Auto autos) {  
    GsonBuilder gsonBuilder = new GsonBuilder();
    Gson gson = gsonBuilder.registerTypeAdapter(Auto.class, new AutoAdapter()).create();
    return gson.toJson(autos);
}   

那么您只需要创建一个适配器类,比如:
public class AutoAdapter implements JsonSerializer<Auto> {
  @Override
  public JsonElement serialize(Auto auto, Type type, JsonSerializationContext jsc) {
    JsonObject jsonObject = new JsonObject();
    jsonObject.addProperty("auto_id", auto.getId());
    jsonObject.addProperty("auto_name", auto.getAutoName());
    jsonObject.addProperty("auto__manufacture_date", auto.getManufactureDate().toString());
    return jsonObject;      
  }
}

错误在于尽管调用了Hibernate.initialize(auto.getManufacturer());,但getter返回的Manufacturer对象仍然是一个HibernateProxy。我在调试应用程序时看到了这一点。然而另一个对象不是HibernateProxy - Andreas
我所需要的是能够单独添加属性并在需要时进行转换。谢谢! - paralaks

2
通常情况下,您不希望通过服务将域对象以XML/JSON形式暴露出来,因为往往需要创建DTO,因为实体不符合消费者的需求。即使现在符合了,经过数据库的内部重构后,明天也可能不符合。所以如果遇到这种麻烦,我的建议是立即创建DTO。顺便说一下,您甚至可以使用结果转换器或创建视图并将Hibernate实体映射到这些视图来在Hibernate级别上创建DTO。
另一个技巧是使用Dozer将所需字段复制到其他类中(实际上是相同的类,但没有代理)。
还有一点需要注意的是,您正在使用Gson,它访问您的字段而不是访问器,这使得无法使用Hibernate代理,因为它将尝试访问代理本身的字段,这些字段始终为null

1

是的,您可以一直取消代理,如果它有一个HibernateProxy(不会序列化),它将被删除并替换为实际的底层实现,或者它将保留类并给您一个实现。我认为您的解决方案应该可以正常工作。请注意,我并不经常使用Hibernate,但这对我来说很有意义。

另一方面,您可能会更信任Hibernate,但更简单的方法可能是:

Hibernate.getClass(obj);

这个解决方案不应该给你实现/初始化的类,只提供类或者函数:

HibernateProxyHelper.getClassWithoutInitializingProxy(superClass)

我认为后者可能会返回超类,因此您可以从Hibernate.getClass(obj)开始;

另外:

public static <T> T initializeAndUnproxy(T entity) {
    if (entity == null) {
        throw new 
           NullPointerException("Entity passed for initialization is null");
    }

    Hibernate.initialize(entity);
    if (entity instanceof HibernateProxy) {
        entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer()
                .getImplementation();
    }
    return entity;
}

以上代码来自:将Hibernate代理转换为真实对象,其中的变量名称可能更好,因为它们不意味着实体始终是代理。此外,它会抛出异常来警告您,但这取决于您是否想要异常。
当然,您也可以摆脱延迟加载,但我认为这不是最好的解决方案。

那么,取消代理对象是否因某种原因导致严重的性能问题?我是否应该始终取消存储库中的所有对象的代理以防止遇到此问题? - Andreas
这是一个很好的问题,肯定需要Hibernate工作人员来回答。话虽如此,我的观点是:这取决于你需要序列化多少次(即它被调用的频率)。有多少对象需要被实例化(你经常序列化的对象中有多少是代理、懒加载)。最坏的情况是,它不会比在创建时实例化所有对象更糟糕。 - msj121
如果你真的需要优化,你可以返回 Hibernate.getClass(obj),这将是一个类对象,而不是实例化的对象。 - msj121
然而,急切加载似乎并不会取消代理对象,因为我已经使用了 Hibernate.initialize(..) - Andreas

1
另一种选择是使用@Expose注释仅创建您想要序列化为JSON的字段。这样,如果这种序列化对您足够了,您就可以避免Hibernate代理的序列化。
在Hibernate实体中注释您的字段:
@Column(name = "my_field", nullable = false)
@Expose
private String myField;

然后以以下方式调用您的JSON序列化:

public String toJSON(YourEntity entity) {
    Gson gson = new GsonBuilder()
            .excludeFieldsWithoutExposeAnnotation()
            .create();

    return gson.toJson(entity);
}

显然,此方法仅在需要序列化非Hibernate代理字段的情况下有用。但对于这种情况,它是一种简单的解决方案,无需创建自定义TypeAdapter。

1
尝试通过ObjectMapper进行解析,如下:

final Auto auto = warehouse.list(context);
final String autoJson = new ObjectMapper().writeValueAsString(auto);

return Response.ok(autoJson).build();

0
当我遇到这个问题时,我偶然看到了这篇文章,它为我的情况指明了正确的方向。我意识到我不需要序列化整个实体,特别是因为我已经标记了一些字段为延迟加载。所以我试图找到一种方法来跳过这些字段,而 ExclusionStrategy 就是那个神奇的方法。这似乎解决了我的问题。
public class ExcludeProxiedFields implements ExclusionStrategy{

    @Override
    public boolean shouldSkipField(FieldAttributes fa) {
        return fa.getAnnotation(ManyToOne.class) != null ||
           fa.getAnnotation(OneToOne.class) != null  ||
           fa.getAnnotation(ManyToMany.class) != null  ||
           fa.getAnnotation(OneToMany.class) != null ;
    }

    @Override
    public boolean shouldSkipClass(Class<?> type) {
        return false;
    }   
}

然后我将这个类应用到GsonBuilder中,就像这样:

Gson gson = new GsonBuilder().setExclusionStrategies(new ExcludeProxiedFields()).create();

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