How to inject a private field in a Component class while keep initiating with @Autowired in parent class in Spring?



I am learning Spring while I like the idea of using @Component and @Autowired to let Spring manage the dependent bean. For example, I have a Service class and Builder Class I can do with

// SomeService.java
@Service
public class SomeService {
    // SomeBuilder is a @Component class
    @Autowired
    SomeBuilder someBuilder;
}

// SomeController.java
@Component
public class SomeController {
    @Autowired
    SomeService someSerivce;
}

Spring would take care of the creation of from SomeController to SomeService to SomeBuilder with the usage of @Autowired. However, now my SomeService class needs a private field which is NOT a Component class, just a plain context object, for example

// SomeService.java
@Service
public class SomeService {
    @Autowired
    SomeBuilder someBuilder;
    
    private SomeContext someContext;

    // Plan A: Using constructor to initiate the private field. However, I cannot use @Autowired to initiate SomeService in SomeController anymore as it requires a parameterless constructor
    // Plan B: using @Autowired on constructor level, I cannot use this because SomeContext is not a @Component class
    //public SomeService(SomeContext someContext) {
        //this.someContext = someContext;
    //}
    
    // Plan C: This seems work but I kinda feel it's not the right way, as usually private field are initiated through constructor
    //public void init(SomeContext someContext) {
        // this.someContext = someContext;
    //}
    
    // demo usage of someContext
    public someAnswer realMethod() {
        System.out.println(someContext.getName());
    }
}

Now I have no idea how to inject the someContext now, I used

plan A: Assign the private field using class constructor

plan B: Using @Autowired on constructor level

plan C: Using a wired method to assign the private field.

but I am not satisfied and don’t have a clear way of doing the right approach.

Answer

First lets take a look at your plans and bust some myths/misunderstandings.

Plan A: Using constructor to initiate the private field. However, I cannot use @Autowired to initiate SomeService in SomeController anymore as it requires a parameterless constructor

Great plan, and the way to go. @Autowired doesn’t depend on having a default constructor. It only indicates that you want the field to be injected with an object of that type. How that object comes to live (default constructor, constructor with arguments) doesn’t matter for @Autowired. So that part of your understanding is just wrong.

using @Autowired on constructor level, I cannot use this because SomeContext is not a @Component class

If there is just a single constructor in a bean Spring will automatically use that to satisfy the dependencies. So in this case you don’t need @Autowired. A bean doesn’t have to be an @Component, a bean is just an instance of a class available to the application context. One way of achieving that is by marking it as an @Component but there are other ways as well. Like defining an @Bean method in in an @Configuration class to construct the bean.

@Configuration
@ComponentScan("your.base.package")
public class YourConfiguration {

  @Bean
  public SomeContext someContext() {
    return new SomeContext();
  }
}

Something along those lines. It will detect the @Component annotated classes through the @ComponentScan and will create a bean of type SomeContext for use as a bean.

Plan C: This seems work but I kinda feel it’s not the right way, as usually private field are initiated through constructor

All your fields should be private not just the ones initialized in a constructor, so also the @Autowired ones. You don’t want those fields to be, easily, accessible from the outside so they can be modified. So make them private.

That all being said, go with constructor injection over field injection or setters/methods for injection. It is clearer and less hidden than field injection and the way to go for mandatory dependencies (for optional dependencies you can use a setter/method).

So using the above config and below classes, it should “just work ™”.

// SomeService.java
@Service
public class SomeService {
  // SomeBuilder is a @Component class  
  private final SomeBuilder someBuilder;
  private final SomeContext someContext;
  public SomeService(SomeBuilder someBuilder, SomeContext someContext) {
    this.someBuilder=someBuilder;
    this.someContext=someContext;
  }
}

// SomeController.java
@Component
public class SomeController {

  private final SomeService someSerivce;

  public SomeController(SomeService someService) {
    this.someService=someService;
  }
}


Source: stackoverflow