How to serialize an injected bean?

后端 未结 1 1886
暖寄归人
暖寄归人 2021-01-27 22:54

I would like to save the data of an injected stateful bean at various intervals: change - save - change- save... I\'m using core serialization and the problem is that all the by

相关标签:
1条回答
  • 2021-01-27 23:07

    For various reasons, serializing a CDI bean directly is dangerous:

    • You may have a proxy, not the actual object; same holds true for the dependencies of that object
    • Serialization implies that the data will be deserialized at a time. But CDI beans are managed by CDI and CDI has no way to "attach" a deserialized object into its set of managed objects.

    But the purpose of this question is to somehow save the state of a CDI bean in a way that it can be restored later. This can be accomplished by using another object that holds the state of the CDI bean. This other object is not managed by CDI, i.e. created with new, and is serializable. Each CDI bean that needs to persist its state has the pair of setState(state)/getState() methods - they could even be part of an interface. You probably want each object to propagate setState(state)/getState() to its collaborators too.

    See the Memento design pattern. This is also implemented in the JSF state saving/restoring mechanism, if you are familiar with it.


    Some example code (there are other valid ways to do it), starting with the state interface:

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

    Then the service itself, that has a collaborator, and the relevant state object:

    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());
        }
    }
    

    The collaborator and its state follow the same pattern:

    class CollaboratorState implements Serializable {
        private String anyName;
        // accessors
    }
    
    @RequestScoped
    class Collaborator implements HasState<CollaboratorState> {
        // you get the point...
    }
    

    And an example usage, following the code from the question:

    @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;
        }
    }
    

    EDIT: If the client of a service needs to know that a service has state, then the client and service might be more coupled than it would be desired. A way out is to modify HasState to deal with opaque objects:

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

    The state of the client contains a list for the state of each collaborator:

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

    The client adds a collaborator to the state only if it extends 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;
        }
    
    0 讨论(0)
提交回复
热议问题