mojo
mojo

Reputation: 1078

Spring Dependency Injection using Java Container Configuration

I'm new to Java and Spring, coming from C# and the .NET world, so bear with me - what I am attempting to do may be off the mark...

I am attempting to configure Spring DI using Java configuration and annotations, not XML configuration, however I am having a few issues. This is for a standalone application, not a web app. I have worked through the springsource documentationand as far as I can tell my very basic configuration should be correct...but isn't. Please take a look at the code below:

Java Configuration Annotated Class:

package birdalerter.common;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import birdalerter.process.ISightingsProcessor;
import birdalerter.process.SightingsProcessor;

@Configuration
@ComponentScan({"birdalerter.process", "birdalerter.common"})
public class AppConfig {
    @Bean
    @Scope("prototype")
    public ISightingsProcessor sightingsProcessor(){
        return new SightingsProcessor();
    }
}

Configure Component implementing the ISightingsProcessor interface:

package birdalerter.process;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

import org.springframework.stereotype.Component;

import birdalerter.domainobjects.IBirdSighting;
@Component
public class SightingsProcessor implements ISightingsProcessor{

    private LinkedBlockingQueue<IBirdSighting> queue;
    private List<ISightingVisitor> sightingVisitors = new ArrayList<ISightingVisitor>();

    public SightingsProcessor(){
    }

    ...
}

Configure Factory Component:

package birdalerter.process;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
public class ProcessorFactory {
    private ISightingsProcessor sightingsProcessor;

    @Autowired
    @Required
    private void setSightingsProcessor(ISightingsProcessor sightingsProcessor){
        this.sightingsProcessor = sightingsProcessor;
    }

    public ISightingsProcessor getSightingsProcessor(){
        return this.sightingsProcessor;
    }
}

Wire up the AnnotationConfigApplicationContext and test:

@Test
public void testProcessingDI(){
    @SuppressWarnings("resource")
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(AppConfig.class);
    context.refresh();


    ISightingsProcessor processor = new ProcessorFactory().getSightingsProcessor();
    System.out.println(processor);
    Assert.assertTrue(processor != null);
}

The SightingsProcessor is not being setter injected and the assert is failing as the returned object is null. Hopefully I have missed something very obvious.

Thanks in advance.

Edited in Response to Meriton:

Thanks for the answer Meriton.

Why would Spring not know about the newly created object? Does Spring not maintain dependencies throughout the application lifecycle and inject as appropriate when new objects are created that are configured as beans?

I don't want to directly use context.getBean(ISightingsProcessor.class) if I can help it to be honest, I would like the dependency injected in the setter method without having manual intervention - it just seems cleaner.

I am using the ProcessorFactory as the ISightingsProcessor interface extends Runnable - the implementing object is to be started as a thread. The application will be configurable to have n* threads, with each being started within a loop iteration. I don't think it is possible (I may be wrong, please advise if so) to have @Autowired annotations within method declarations, hence I use the factory to supply a new instance of the injected ISightingsProcessor concrete class.

Yes I've just had a look regarding the @Scope annotation - you are right, that needs moving to the AppConfig @Bean declaration (which I've done in this edit), thanks for that.

Upvotes: 3

Views: 1382

Answers (1)

meriton
meriton

Reputation: 70564

ISightingsProcessor processor = new ProcessorFactory().getSightingsProcessor();

This calls the constructor of ProcessorFactory, and then the getter of the instance the constructor created. Spring can not know about that newly created object, and therefore not inject its dependencies. You should ask Spring for the ProcessorFactory instead, for instance with

ProcessorFactory pf = context.getBean(ProcessorFactory.class);
ISightingsProcessor processor = pf.getSightingsProcessor();

That said, I don't know why you need class ProcessorFactory at all. You might just as well get the ISightingsProcessor directly:

ISightingsProcessor processor = context.getBean(ISightingsProcessor.class);

Additionally, "Java Based Configuration" and component scanning are independent ways to declare beans. Currently, you are therefore declaring the ISightingsProcessor twice: Once with the @Bean-annotated factory method, and once with the component scan and the @Component annotation on the class. Doing either of that will do. In fact, doing both might cause one bean definition to override the other.

Oh, and the @Scope annotation is for bean definitions (those you annotate with @Bean or @Component). It will likely be ignored on injection points (@Autowired).

Upvotes: 1

Related Questions