Search code examples
javaspringautowiredtightly-coupled-code

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


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?


Solution

  • 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>