views:

314

answers:

3

Hi StackOverflow!

I am just in the middle of the development of a new software component involving the Spring Framework. I like it but now i have a question regarding IoC and serialization.

Given i have this class (Omitted imports and package declaration):

public class EMailNotificationEndpoint implements NotificationEndpoint {
 private List<String> notficationEmailAdresses = new ArrayList<String>();
 transient private MailSender mailSender;

 public EMailNotificationEndpoint(MailSender mailSender) {
  this.mailSender = mailSender;
 }

 @Override
 public void notify(Notification notification) {
  SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
  simpleMailMessage.setTo(notficationEmailAdresses.toArray(new String[notficationEmailAdresses.size()]));
  simpleMailMessage.setSubject(notification.getType().toString());
  simpleMailMessage.setText(notification.getMessage());

  mailSender.send(simpleMailMessage);
 }
}

NotificationEndpoint extends Serializable to ensure all implementations are serializable. The point is that i cannot serialize the MailSender (Which is org.springframework.mail.MailSender BTW) and want it to be injected at deserialisation time. Now my question is quire obvious: Can i do that? And if the answer is yes: how?

I'm free to change the interface and the implementations, so there should be a way to such a thing, even if this means i have to refactor the relevant classes.

Any Idea or hint is highly appreciated!

+2  A: 

You are correct about making it transient.

All you need to do is restore it as needed, when deserializing.

You could implement the adequate readObject method, to do that initialisation:

     /**
      * Always treat de-serialization as a full-blown constructor, by
      * initializing or validating the final state of the de-serialized object.
      */
      private void readObject(ObjectInputStream aInputStream) 
          throws ClassNotFoundException, IOException {
        //always perform the default de-serialization first
        aInputStream.defaultReadObject();
        //do your other stuff, like initialization
        this.mailSender = ...
      }

Note: Here, the object itself has to get the MailSender implementation, which is not very DI (Dependency Injection). Either you are ok with that, or you find another way (tipically another object is involved after deserialization, and sets the correct dependency).


In case you really like to do the Dependency Injection, I found a link for Spring and Entities, that might contains general knowledge applicable to this case:
http://chris-richardson.blog-city.com/migrating_to_spring_2_part_3__injecting_dependencies_into_en.htm

KLE
Your Note states exactly what my Question is about. I am looking for a way to do this in a DI way. Maybe Spring is supporting something for that?
Malax
@Malax I know Spring gives support for objects that are instanciated outside of Spring, but post-processed by Spring. I can't remember how to do it right now.
KLE
@KLE - it's the ... bit in your code sample that's the hard part. How are you meant to obtain the `mailSender` within the object when it is ordinarily injected?
Nick Holt
+2  A: 

You'll find a solution in New Improvements in Domain Object Dependency Injection Feature (based on pre-written aspects and/or the @Configurable annotation)

Pascal Thivent
Acceppted because it was the fastest hint to the solution i was lookin g for. :-) Thanks!
Malax
+1  A: 

You'll need to implement custom readObject and writeObject methods that serialize/deserialize the state and the class of the MailSender, something like this:

private void writeObject(ObjectOutputStream out) throws ClassNotFoundException, IOException 
{
  try
  {
    out.defaultWriteObject();
    out.writeObject(mailSender.getClass().getName());

    for (Field field : mailSender.getClass().getDeclaredFields()) 
    {
      if (!(Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())))
      {  
        Object value = field.get(mailSender);
        if (value instanceof Serializable) 
        {
          out.writeObject(field.getName());
          out.writeObject(value);
        }
      }
    }  
  }
  catch (IllegalAccessException e) 
  {
    throw new IOException(e.getMessage(), e);
  }
}

private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException 
{
  try 
  {
    in.defaultReadObject();     
    mailSender = (MailSender)Class.forName((String)in.readObject()).newInstance();

    Map<String, Field> fields = new HashMap<String, Field>();
    for (Field field : mailSender.getClass().getDeclaredFields()) 
    {
      fields.put(field.getName(), field);
    }  

    int remainingFields = mailSender.getClass().getDeclaredFields().length;  
    while (remainingFields > 0) 
    {   
      String fieldName = (String)in.readObject();
      Object value = in.readObject();
    fields.get(fieldName).set(mailSender, value);
      remainingFields--;
    }
  }
  catch (IllegalAccessException e)
  {
    throw new IOException(e.getMessage(), e);
  }
  catch (InstantiationException e)
  {
    throw new IOException(e.getMessage(), e);
  }
}

You'll probably want to tighten up the reflective code and maybe use recursion if the state you require isn't serializable but you the gist of what you need to do.

Nick Holt