【问题标题】:How to serialize an injected bean?如何序列化注入的bean?
【发布时间】:2018-01-31 08:40:19
【问题描述】:

我想以不同的时间间隔保存注入的有状态bean的数据:更改-保存-更改-保存...我正在使用核心序列化,问题是所有字节数组都是相同的。我相信代理是序列化的,因为如果我稍后反序列化其中一个数组,我会得到 bean 的当前状态。

序列化示例未捕获 bean 中的更改:

@Stateful
@RequestScoped
public class State implements Serializable {

    private static final long serialVersionUID = 1L;

    @Inject
    StatelessBean bean; // assume it's needed

    private List<String> list = new ArrayList<>();

    public void add() {
        list.add("S");
    }
}

这是一个 JAX-RS 类:

@Stateless
@Path("t1")
public class ChickensResource {

    @Inject
    State state;

    @GET
    @Path("/test")
    public String test() {
        state.add();
        byte[] b0 = serialize(state);
        System.out.println(b0.length + " " + Arrays.toString(b0));
        state.add();
        byte[] b1 = serialize(state);
        System.out.println(b1.length + " " + Arrays.toString(b1)); // prints same as b0
        System.out.println(b0.length + " " + Arrays.toString(b0)); // prints same thing
    }

    public static <T extends Serializable> byte[] serialize(T s) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos))
        {
            oos.writeObject(s);
            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

我想要做的只是将列表保存在State 中,因为那是相关数据。我还尝试了 JSON 序列化,它给出了 IOException,但我正在尝试核心序列化。

使用 JavaEE7 和 Wildfly 10.1。

【问题讨论】:

  • 您需要将State中的bean声明更改为transient StatelessBean bean
  • 由于State@RequestScopedChickensResource.state 中注入的东西是一个代理。这就是为什么序列化总是返回相同的东西。为什么不向State 添加一个serialize() 方法来处理它自己的序列化的内部并适当地处理依赖关系?
  • @NikosParaskevopoulos 谢谢。这只发生在@RequestScope 上吗?我真正的State 对象包含一个对象图,有些是注入的,有些不是,我什么时候需要采用您的方法?另外我read 说如果注入的bean 的代理没有被序列化,那么在反序列化它们时它将保持null
  • @NikosParaskevopoulos 它似乎发生在任何范围内。那意味着作用域bean不能被序列化?
  • 这应该发生在所有“正常范围”中:请求、会话和应用程序(+任何自定义范围)。该规范谈到了钝化,它建立在序列化之上,但完全不同。规范中没有单独提及序列化。我不会以任何方式序列化 CDI bean(Java 序列化、JSON 等)。我代替你做的是让 CDI bean 将其状态保存在另一个不受 CDI 管理的对象中(即使用new 创建)并且可序列化的.

标签: java jakarta-ee serialization ejb cdi


【解决方案1】:

由于各种原因,直接序列化 CDI bean 是危险的:

  • 您可能有一个代理,而不是实际对象;该对象的依赖项也是如此
  • 序列化意味着数据将一次反序列化。但 CDI bean 由 CDI 管理,CDI 无法将反序列化对象“附加”到其托管对象集中。

但是这个问题的目的是以某种方式保存 CDI bean 的状态,以便以后可以恢复。这可以通过使用另一个保存 CDI bean 状态的对象来完成。这个其他对象不由 CDI 管理,即使用new 创建,并且是可序列化的。每个需要保持其状态的 CDI bean 都有一对 setState(state)/getState() 方法——它们甚至可以是接口的一部分。您可能希望每个对象也将setState(state)/getState() 传播给它的协作者。

参见Memento 设计模式。如果您熟悉的话,这也在 JSF 状态保存/恢复机制中实现。


一些示例代码(还有其他有效的方法),从状态接口开始:

interface HasState<S extends Serializable> {
    S getState();
    void setState(S state);
}

然后是具有协作者的服务本​​身,以及相关的状态对象:

class SomeServiceState implements Serializable {
    private String someData;
    private Long someId;
    private List<String> list;
    private CollaboratorState collaboratorState;
    // accessors
}

@RequestScoped
public class SomeService implements HasState<SomeServiceState> {

    // COLLABORATORS
    @Inject
    Collaborator collaborator; // assume it's needed

    // INTERNAL STATE
    private String someData;
    private Long someId;
    private List<String> list = new ArrayList<>();

    public void add() {
        list.add("S");
    }

    // ...

    public SomeServiceState getState() {
        SomeServiceState state = new SomeServiceState();
        state.setSomeData(someData);
        state.setSomeId(someId);
        state.setList(new ArrayList<>(list)); // IT IS PROBABLY SAFER TO COPY STATE!
        // SEE HOW STATE GETS EXTRACTED RECURSIVELY:
        state.setCollaboratorState(collaborator.getState());
        return state;
    }

    public void setState(SomeServiceState state) {
        someData = state.getSomeData();
        someId = state.getSomeId();
        list = new ArrayList<>(state.getList());
        // SEE HOW STATE GETS APPLIED RECURSIVELY:
        collaborator.setState(state.getCollaboratorState());
    }
}

协作者及其状态遵循相同的模式:

class CollaboratorState implements Serializable {
    private String anyName;
    // accessors
}

@RequestScoped
class Collaborator implements HasState<CollaboratorState> {
    // you get the point...
}

还有一个示例用法,遵循问题中的代码:

@Stateless
@Path("t1")
public class ChickensResource {

    @Inject
    SomeService someService;

    @GET
    @Path("/test")
    public String test() {
        someService.add();
        byte[] b0 = serialize(someService.getState());
        // ...
    }

    public static <T extends Serializable> byte[] serialize(T s) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos))
        {
            oos.writeObject(s);
            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

编辑:如果服务的客户端需要知道服务具有状态,那么客户端和服务的耦合度可能会超出预期。一个出路是修改HasState来处理不透明的对象:

interface HasState {
    Object getState();
    void setState(Object state);
}

客户端的状态包含每个协作者的状态列表:

class SomeServiceState implements Serializable {
    private String someData;
    private Long someId;
    private List<String> list;
    private List<Object> collaboratorsState;
    // accessors
}

只有在扩展HasState时,客户端才会向状态添加协作者:

    public Object getState() {
        SomeServiceState state = new SomeServiceState();
        state.setSomeData(someData);
        state.setSomeId(someId);
        state.setList(new ArrayList<>(list));
        if( collaborator instanceof HasState ) {
            state.getCollaboratorsState().add(collaborator.getState());
        }
        return state;
    }

【讨论】:

  • 谢谢。如果 Collaborator 没有状态,说它是一个注入的 @Stateless bean,它没有实现 HasState 接口,然后我可以删除行 state.setCollaboratorState(collaborator.getState());?
  • 是的,当然!这会产生耦合,因为服务的客户端必须知道该服务是否具有状态。但是有一条出路(取自 JSF 状态保存机制)。修改getState()/setState()返回/取Object。然后将协作者的状态保存在数组中,仅当它实现HasState 时。我会更新答案。
  • 我玩了很多各种设计,发现这样:我将注入的字段标记为transient并正常序列化。沙漠之后。我使用Instance&lt;SomeService&gt;#get/select 获得了一个新实例,在该实例上的注入代理上调用 get 并将其设置在我的反序列化非托管对象中。然后我在托管实例上调用destroy。 (换句话说,我创建了一个托管对象只是为了窃取它的代理。)我对其进行了测试,它在技术上可以正常工作,但我 99% 确信这出于某种原因是不好的。可能是钝化?但我可以选择退出。
  • 我不确定我是否完全理解你的所作所为,但它看起来确实像一个 hack。你的意思是你需要2个字段,(1)transient SomeService someService和(2)@Inject Instance&lt;SomeService&gt; someServiceInstance?为什么打电话给destroy()
  • SomeService 有一个 @Inject transient Collab collab 字段。我做常规服务。和沙漠。在另一个班级SomeService。现在合作是null。在其他班级我有@Inject Instance&lt;SomeService&gt; someServiceInstance。沙漠之后。我做someServiceInstance.get(),为(非空)collab 字段调用get 并在deser 上调用set。我得到的值的实例。现在我的沙漠。实例具有collab 的有效注入代理。由于我不再需要来自Instance&lt;SomeService&gt; 的实例,因此我在其上调用destroy
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-25
  • 1970-01-01
相关资源
最近更新 更多