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.
Advertisement
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 initiateSomeService
inSomeController
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 becauseSomeContext
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; } }