问题
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 byte arrays are the same. i believe the proxy is serialized because if I deserialize one of the arrays later I get the current state of the bean.
Example of serialization not capturing changes in the 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");
}
}
And this is a JAX-RS class:
@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;
}
}
What I want to do is only save the list in State
as that's the relevant data. I also tried JSON serialization and it gave an IOException
, but I'm trying core serialization.
Using JavaEE7 and Wildfly 10.1.
回答1:
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;
}
来源:https://stackoverflow.com/questions/45828159/how-to-serialize-an-injected-bean