javaspringdependency-injectionjavabeansspring-loaded

Can a bean class instantiate another class inside, and if/how should we avoid it?


Suppose the bean is registered here.

@Configuration
public class configuration{

    @Bean(name = "MyClass")
    public MyClass getMyClass(@Value("avalue") final String s){
        return new MyClass(s);
        }
    }
}

And the class has another class instantiated inside as below (No annotation needed for either?)

public class MyClass{
    MyOtherClass myOtherClass;
    public MyClass(String s){
        this.myOtherClass = new MyOtherClass(s);
    }
}

public class MyOtherClass{
    String s;
    public MyOtherClass(String s){
        this.s = s;
    }
}

How this mechanic works? Is it safe to instantiate a new instance within a bean injection? What's the advantage or disadvantage of doing so?

If we were to avoid using it by making MyOtherClass a bean too, would this be the way? (Mainly concerning: is @Autowired and getMyclass() taking the right place or it's redundant?)

@Configuration
public class configuration{

    @Bean(name = "MyOtherClass")
    public MyOtherClass getMyOtherClass(@Value("avalue") final String s){
        return new MyOtherClass(s);
        }
    }

    @Bean(name = "MyClass")
    public MyClass getMyClass(MyClass myClass){
    //I was told the input should be myClass not myOtherClass,
    //and the return is myClass not new MyClass(myOtherClass);
    //since it's already autowired. Is that correct?
    //What if the configuration.java and MyClass.java are in different project?
        return myClass;
        }
    }
}

@Component
public class MyClass{
    MyOtherClass myOtherClass;

    @Autowired
    public MyClass(MyOtherClass myOtherClass){
        this.myOtherClass = myOtherClass;
    }
}

public class MyOtherClass{
    String s;
    public MyOtherClass(String s){
        this.s = s;
    }
}

Solution

  • If you want not to expose your MyOtherClass and take control over creating its instances, you can use your first approach:

    ...
    public MyClass(String s) {
        this.myOtherClass = new MyOtherClass(s);
    }
    

    It is safe, but it is not a dependency injection: MyOtherClass is not managed by Spring, so you won't be able to inject it in other beans using @Autowired or to inject other spring managed classes into MyOtherClass. Also, as mentioned by Andrew, it becomes impossible to mock MyOtherClass.

    If registering MyOtherClass as a bean in a Spring context is fine, then the easiest way is to annotate both classes with @Component and let Spring do its work (in this case you should use @ComponentScan to help Spring auto-detect your classes):

    @Component
    class MyClass {
        MyOtherClass myOtherClass;
        @Autowired
        public MyClass(MyOtherClass myOtherClass){
            this.myOtherClass = myOtherClass;
        }
    }
    
    @Component
    class MyOtherClass {
        String s;
        public MyOtherClass(@Value("avalue") final String s){
            this.s = s;
        }
    }
    

    Or, using @Bean (this will work if Config.java and MyClass.java are in different projects):

    @Configuration
    public class Config {
    
        @Value("avalue")
        private String s;
    
        @Bean(name = "myClass")
        public MyClass getMyClass(){
            return new MyClass(getMyOtherClass());
        }
        @Bean(name = "myOtherClass")
        public MyOtherClass getMyOtherClass() {
            return new MyOtherClass(s);
        }
    }
    

    Note: if you have already registered MyClass in a Spring context using

    @Component
    public class MyClass {
        ...
    }
    

    then you don't need to do it again by

    @Bean(name = "MyClass")
    public MyClass getMyClass(MyClass myClass) {
        return myClass;
    }