1

We have an interface:

public interface NotifyService {

  public void send();

And a class that implements it

public class EmailNotifyService implements NotifyService {

  private EmailBuilder _builder;

  @Autowired
  PersonRepository _personRepository;

  ... other Autowired Repositories ...

  public EmailNotifyService(EmailBuilder builder) {
   this._builder = builder;
  }

  public void send() {
    // send mail using _builder.getRecipient().getEmailAddress(), etc.
  }

We used to instantiate EmailNotifyService with a builder:

public class EmailBuilder {

  private Person _recipient;

  private EmailType _type;

  private Event _event;

  public EmailNotifyService build() {
    return new EmailNotifyService(this);
  }

  public EmailBuilder recipient(Person recipient) {
    this._recipient = recipient;
    return this;
  }

... and so on. But now, instead of using build() to create a new EmailNotifyService, we are trying to use Autowire with Spring instead. The problem is that everywhere else in our app, we are Autowiring interfaces, not classes. And from what I've read it's a good idea in general. In fact, I've tried rewriting the NotifyService to be an Abstract class, and then have EmailNotifyService just extend it. But Spring isn't Autowiring it correctly, it doesn't create a Proxy like it does for interfaces, and all of my Autowired fields are null.

So it would seem we're stuck with Autowiring the NotifyService interface. Fine. What I can't get my head around is - how can I get the data I used to assign with the builder -- the Person, EmailType and Event -- into a Spring Autowired interface?

I suppose I could change the interface definition to have a setPerson(), setEmailType(), etc., but apart from being really ugly, it defeats the purpose of using an interface in the first place. A different NotifyService (WebServiceNotifyService or RestNotifyService for example) night not have need for that info.

Is there any elegant, best-practice way to do this?

Thanks.

EDIT

I am using annotations, very little xml. And I am also using transaction management, which might explain why the abstract class isn't properly autowired? This is the only pertitnent info I have in xml:

<context:annotation-config />  
<context:component-scan base-package="com.myco.myapp" />  
<tx:annotation-driven transaction-manager="transactionManager"/>  

What I mean when I say "autowiring isn't working correctly" is that when I try to autowire the abstract class, Spring doesn't seem to be creating a Proxy like it does for interfaces, and all the Autowired fields in my EmailNotifyService (PersonRepository, others ...) are null. When I use an interface, all the Autowired fields are wired correctly.

But my main problem is that I used to work explicitly with a concrete class, using a builder to create a new EmailNotifyService() directly, and pass it info -- Person, EmailType and Event. These are just normal beans. There are no setters/getters for them in EmailNotifyService but there are the EmailBuilder, which used to live inside EmailNotifyService.

But now I am using the NotifyService interface, which knows nothing about Person, EmailType or Event. But I need this info in order for EmailNotifyService to work.

So my question is, if I use Spring to Autowire my EmailNotifyService like this:

@Autowired
@Qualifier("email") // so Spring knows I want to use the EmailNotifyService implementation
NotifyService _notifyService

How can I set the Person, EmailType and Event data, since NotifyService knows nothing about them?

Currently we are using the mailer service within a web app but theoretically the mailer service should be able to work stand-alone. Regardless, I don't see how request scoped beans can help me here.

Robert Bowen
  • 487
  • 2
  • 13
  • 24

1 Answers1

0

Robert what do you mean by not autowiring correctly? Are you getting any error?

Generally both interface and class auto-wiring works in Spring unless you have some autoproxy configured example @Transactional.

You do not need to have setPerson(), setEmailType(), etc. in your interface but have them autowired in the concrete class which requires them.

But seems Person is not a service but a bean which holds data and its specific to a request. If yours is a web application then look at request scope proxy to inject Person like bean.


So you are using transactions which is why class based injection is failing. Add proxy-target-class="true" to tx:annotation-driven.

Regarding your injection of Person and EmailType then you have to do that to the bean EmailNotifyService. In EmailNotifyService I do not see any Person or EmailType variables defined. Also read what I said about Person bean above.


Your design is not correct. You should not make EmailBuilder a bean and look to autowire to the EmailNotifyService. Instead in EmailNotifyService you should have a method send(EmailBuilder builder) where you pass the builder which you created somewhere dynamically.

Bhushan Bhangale
  • 10,921
  • 5
  • 43
  • 71
  • Hmm ... Ok, I see that I would need to add 'proxy-target-class="true"' if I want to use a concrete class. But if instead I choose to use the interface, I still don't see how to assign the Person, EmailType and Event info. There are no setters for these beans in EmailNotifyService but there is a constructor that accepts the builder, and the builder has all the getters and setters. But how can I pass the builder to the EmailNotifyService constructor if Spring is instantiating the class, using Autowire and the interface? – Robert Bowen Apr 16 '13 at 09:45
  • Remove the build method from the EmailBuilder as Spring is created object of EmailNotifyService. Make EmailBuilder also a bean and autowire that into EmailNotifyService. Also autowire Person and EmailType in EmailBuilder. – Bhushan Bhangale Apr 16 '13 at 09:48
  • But ... the values in Person, EmailType and Event are dynamically assigned at run-time. Currently we do something like this: EmailBuilder builder = new EmailBuilder().recipient(new Person("Joe")).type(new EmailType("normal")).event(new Event("new event")).build(). If I Autowire the Builder, and its Person, EmailType and Event, how would I set them dynamically, at run-time? – Robert Bowen Apr 16 '13 at 11:26
  • That's what I wound up doing. Changing the interface method send() to send(EmailBuilder builder). But I wanted to avoid doing this. Because now my interface is tied to an implementation. I'm going to go back and try again with an abstract class, adding 'proxy-target-class="true"' to my config, see if it works. Many thanks for your advice. – Robert Bowen Apr 17 '13 at 07:27
  • Its not tied as NotifyService must provide a way to take information which needs to be sent. You can make a Builder interface and change send(Builder builder) in the NotifyService. – Bhushan Bhangale Apr 17 '13 at 07:32
  • 1
    True ... but I originally I wanted to simply have send(), without parameters, in the interface, the way it used to be. But I think using interface with Spring Autowired this isn't possible. But it should be possible using an abstract class and setting 'proxy-target-class="true"'. – Robert Bowen Apr 17 '13 at 09:10