How to instruct Spring to Autowire a field in a chain of tightly coupled objects

2k Views Asked by At

It's kinda hard to explain... Hope the question isn't vague...

you can look at the code to get the idea...

ClassA.java

public class ClassA {
    @Autowired
    InterA abcd;
    public void dododo() {
        abcd.doit();
    }
}

ClassB.java

@Component
public class ClassB implements InterA {
    @Override
    public void doit() {
        System.out.println("hoo hoo");
    }
}

ClassC.java

@Component("classc")
public class ClassC {
    public void doFromAbove() {
        ClassA cls = new ClassA();
        cls.dododo();
    }
}

Interface InterA.java

public interface InterA {
    public void doit();
}

Configuration ClassConfig.java (on the same package of other java class files)

@Configuration
@ComponentScan
public class ClassConfig {
}

Main Method

public static void main(String[] args) {
    try(AbstractApplicationContext appctx = new AnnotationConfigApplicationContext(ClassConfig.class)) {
        ClassC obj = (ClassC) appctx.getBean("classc");
        obj.doFromAbove();
    }
}

When I execute the main method, the Autowired field "abcd" in ClassA didn't get injected and results in a NullPointerException

It works only when I declare ClassA as a @Component and get it's bean... indirect autowiring is not happening

Should I decouple ClassA from ClassC and make everything loosely coupled?

Is there any simple annotation that I can use to tell Spring auto inject the @Autowired field even when the object is created in a tight coupled fashion?

Note please don't tell me to use ApplicationContext in ClassC to create the bean of ClassA .

Any Spring Geek who could find an answer?

5

There are 5 best solutions below

1
On BEST ANSWER

After intense googling, Spring Documentation Skimming, I'm convinced there are more possible solutions to this dilemma...

Possible Solutions:

  1. Use JSR 330 Provider<T> with @Autowired
  2. Use FactoryBean<T> with initialization code in getObject() (but the bean returned by the factory is not spring managed and thus any autowired field in the prototype class will return NullPointerException)
  3. Use Lookup Method Injection(include CGLIB library)(I don't prefer this, as it modifies compiled code and sounds like it creates bean objects of Abstract classes)(Java's Purity is violated)
  4. Implement ApplicationContextAware interface and get the context object (not recommended)
  5. Autowire ApplicationContext and use getBean() (not recommended)

Most Subtle approach among the above is JSR330 Provider

ClassA

@Component("classa")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ClassA implements InterB {
    private static int counter=0;

    private int objectid = 0;
    @Autowired
    InterA abcd;

    public ClassA() {
        super();
        this.objectid = ++counter;
    }

    @Override
    public void dododo() {
        System.out.println("instance number "+objectid++);
        abcd.doit();
    }
}

ClassB

@Component
public class ClassB implements InterA {
    @Override
    public void doit() {
        System.out.println("hoo hoo");
    }

}

ClassC

@Component("classc")
public class ClassC {

    @Autowired
    Provider<InterB> classAPrototypeobj;

    public void doFromAbove() {
        //you can do a for loop here and get a set of objects for use
        InterB cls = (InterB) classAPrototypeobj.get();
        InterB cls1 = (InterB) classAPrototypeobj.get();
        cls.dododo();
        cls1.dododo();
        System.out.println(cls);
        System.out.println(cls1);
    }
}

Now it works flawlessly and the initialized object is spring managed too...

Note: JSR330 dependency has to be set in maven pom.xml

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>
3
On

Why not use @Lookup annotation? Based on the accepted answer, I assume you require a new instance of ClassA every time in ClassC.

@Component("classA")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ClassA {
    @Autowired
    InterA abcd;

    private ObjectA objA;

    private ObjectB objB;

    public ClassA(objA, objB) {
        this.objA = objA;
        this.objB = objB;
    }

    public void dododo() {
        abcd.doit();
    }
}

@Component("classc")
public class ClassC {
    public void doFromAbove() {
        ClassA cls = getNewInstanceOfClassA(objA, objB);
        cls.dododo();
    }

    @Lookup("classA")
    private getNewInstanceOfClassA(ObjectA objA, ObjectB objB) {
        //Spring creates a runtime implementation of this method
        return null;
    }
}

The rest of the class implementations remain the same. I have included objA and objB in the implementation for some clarity on injecting constructor arguments.

So the main method gets a spring bean of classC and calls doFromAbove() method. This in turn calls getNewInstanceOfClassA method which returns a spring bean of type classA with the constructor arguments objA, objB. Since we annotated it as prototype bean, we get a new instance of classA every time this method is invoked. You don't need to implement getNewInstanceOfClassA method. Spring adds it's own code during the runtime.

Essentially your problem boils down to injecting a prototype bean in a singleton bean. Lookup annotation is the best way to solve that issue.

0
On

The beans declared within Spring container (either through XML or annotations like @Component) are Spring-managed - Spring will take care of them, will make sure they exist when you request them via ApplicationContext.getBean() and that their dependencies are injected as well.

When you create an instance yourself (cls = new ClassA()), that instance is not Spring-managed and Spring will not do anything to it. In fact, Spring won't (and cannot) even know the object exists.

Some confusion may stem from that you annotate the class with the Spring annotations - but it's really objects (instances) that are actually used in Java; even if the class is annotated, the annotations will only apply on instances that are created and managed by Spring.

6
On

The issue is in ClassC:

    ClassA cls = new ClassA();

If you invoke the constructor of ClassA like this, Spring won't do its magic. If you need an instance of ClassA with injected fields, ask Spring for an instance (using injection or getBean()).

(To avoid having null fields where injection is assumed, I recommend using constructor injection.)

2
On

You could use @Configurable if you enable load time weaving and that would make each new instance of the object a managed spring component, so the new statement you used will work.

Besides this you could create a prototype scoped bean definition and a factory bean that would reference that bean, meaning it will give you a new bean each time, so you would inject the factory and only call the get method for a new instance.