59

我从服务器收到以下错误响应。

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 Debugger中看到了类型)。我如何知道何时取消代理对象?我应该取消代理该汽车可能具有的所有关联吗?在我的代码的哪一层?当我取消代理时,它会影响我的应用程序的性能吗?还有其他解决方案吗?我从错误消息中看到我可以制作类型适配器。是的,我可以,但我必须对所有域对象执行此操作,以确保正确完成转换。当我尝试将域中的其他对象也转换为 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|警告|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 javax.ws.rs.core.Application 的 Servlet.service()
抛出异常 java.lang.UnsupportedOperationException: 试图
序列化 java.lang.Class:org.hibernate.proxy.HibernateProxy。忘记
注册一个类型适配器?
    在 com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:64)
    在 com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:61)
    在 com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    在 com.google.gson.internal.bind.ArrayTypeAdapter.write(ArrayTypeAdapter.java:93)
    在 com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    在 com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
    在 com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195)
    在 com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    在 com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
    在 com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195)
    在 com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    在 com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
    在 com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195)
    在 com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    在 com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:96)
    在 com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:60)
    在 com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    在 com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
    在 com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195)
    在 com.google.gson.Gson.toJson(Gson.java:586)
    在 com.google.gson.Gson.toJson(Gson.java:565)
    在 com.google.gson.Gson.toJson(Gson.java:520)
    在 com.myapp.AutoResource.produceAuto(AutoResource.java:48)
    在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    在 java.lang.reflect.Method.invoke(Method.java:601)
    在 org.glassfish.ejb.security.application.EJBSecurityManager.runMethod(EJBSecurityManager.java:1052)
    在 org.glassfish.ejb.security.application.EJBSecurityManager.invoke(EJBSecurityManager.java:1124)
    在 com.sun.ejb.containers.BaseContainer.invokeBeanMethod(BaseContainer.java:5388)
    在 com.sun.ejb.EjbInvocation.invokeBeanMethod(EjbInvocation.java:619)
    在 com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:800)
    在 com.sun.ejb.EjbInvocation.proceed(EjbInvocation.java:571)
    在 com.sun.ejb.containers.interceptors.SystemInterceptorProxy.doAround(SystemInterceptorProxy.java:162)
    在 com.sun.ejb.containers.interceptors.SystemInterceptorProxy.aroundInvoke(SystemInterceptorProxy.java:144)
    在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    在 java.lang.reflect.Method.invoke(Method.java:601)
    在 com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:861)
    在 com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:800)
    在 com.sun.ejb.containers.interceptors.InterceptorManager.intercept(InterceptorManager.java:370)
    在 com.sun.ejb.containers.BaseContainer.__intercept(BaseContainer.java:5360)
    在 com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:5348)
    在 com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:214)
    在 com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:89)
    在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    在 java.lang.reflect.Method.invoke(Method.java:601)
    在 com.sun.jersey.spi.container.JavaMethodInvokerFactory$1.invoke(JavaMethodInvokerFactory.java:60)
    在 com.sun.jersey.server.impl.model.method.dispatch.Abs​​tractResourceMethodDispatchProvider$ResponseOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:205)
    在 com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher.dispatch(ResourceJavaMethodDispatcher.java:75)
    在 com.sun.jersey.server.impl.uri.rules.HttpMethodRule.accept(HttpMethodRule.java:288)
    在 com.sun.jersey.server.impl.uri.rules.ResourceClassRule.accept(ResourceClassRule.java:108)
    在 com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:147)
    在 com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept(RootResourceClassesRule.java:84)
    在 com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1469)
    在 com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1400)
    在 com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1349)
    在 com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1339)
    在 com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:416)
    在 com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:537)
    在 com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:708)
    在 javax.servlet.http.HttpServlet.service(HttpServlet.java:770)
    在 org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1550)
    在 org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:281)
    在 org.apache.catalina.core.StandardContextValve.__invoke(StandardContextValve.java:175)
    在 org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java)
    在 org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:655)
    在 org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:595)
    在 org.apache.catalina.core.StandardHostValve.__invoke(StandardHostValve.java:161)
    在 org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java)
    在 org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:331)
    在 org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:231)
    在 com.sun.enterprise.v3.services.impl.ContainerMapper$AdapterCallable.call(ContainerMapper.java:317)
    在 com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:195)
    在 com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:860)
    在 com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:757)
    在 com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1056)
    在 com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:229)
    在 com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
    在 com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
    在 com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
    在 com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
    在 com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
    在 com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
    在 com.sun.grizzly.ContextTask.run(ContextTask.java:71)
    在 com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
    在 com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
    在 java.lang.Thread.run(Thread.java:722) |#]
4

6 回答 6

95

您无需手动取消代理一切,只需使用自定义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实现,适用于各种类型(原始类型、常见类型如Stringor Date、列表、数组......)。询问每个工厂是否能够序列化某个 Java 类型(参数 tocreate是 aTypeToken而不是 a Class,以便捕获有关泛型类型的可能信息,而后者Class没有)。如果工厂能够序列化/反序列化一个类型,它会响应一个TypeAdapter实例;否则它会以null.

HibernateProxyTypeAdapter.FACTORY验证类型是否实现HibernateProxy;在这种情况下,它返回一个HibernateProxyTypeAdapter用于序列化的实例。write当必须序列化实际对象时调用该方法;适配器提取底层对象的原始类型,并向 GSON 询问TypeAdapter原始类型的标准,通常为ReflectiveTypeAdapter.

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

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

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());
}
于 2012-11-23T08:44:50.353 回答
4

看起来好像您提到过急切加载时仍然存在错误,那么问题可能不在于 Hibernate,而可能在于 GSON 实现。我认为您在创建 JSON 时需要一个类型,不确定它是否已注册,但可能是这样的:

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

然后只需创建一个AdapterClass,例如:

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;      
  }
}
于 2012-11-22T13:51:54.913 回答
2

在通常情况下,您不希望您的域对象通过服务公开为 XML/JSON,通常您需要创建一个 DTO,因为您的实体不符合您的消费者的需求。即使现在可以,在对数据库进行内部重构之后,明天也无法适应。因此,如果您遇到这样的麻烦,我的建议是立即创建 DTO。顺便说一句,您甚至可以在 Hibernate 级别上创建这些 DTO,方法是使用结果转换器或创建视图并将 Hibernate 实体映射到这些视图上。

另一个技巧是使用Dozer将所需字段复制到另一个类(实际上是同一个类,但没有代理)。

并注意:您使用的是 Gson,它访问您的fields,而不是访问器,这使得它无法使用 Hibernate 代理,因为它会尝试访问代理本身的字段,这些字段始终是null.

于 2012-11-22T07:02:49.927 回答
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;
}

上面的代码是从: Converting Hibernate proxy to real object中借用的,其中的变量名可能更好,因为它们并不意味着实体始终是代理。它也会抛出一个异常来警告你,但这取决于你是否想要异常。

当然你也可以摆脱延迟加载,但我认为这不是最好的解决方案。

于 2012-11-21T20:25:30.990 回答
1

尝试通过ObjectMapper解析为

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

return Response.ok(autoJson).build();
于 2020-10-15T07:35:57.453 回答
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();
于 2017-10-23T03:49:45.613 回答