Spring: Why do we autowire the interface and not the implemented class?
How does spring know which polymorphic type to use.
As long as there is only a single implementation of the interface and that implementation is annotated with @Component
with Spring's component scan enabled, Spring framework can find out the (interface, implementation) pair. If component scan is not enabled, then you have to define the bean explicitly in your application-config.xml (or equivalent spring configuration file).
Do I need @Qualifier or @Resource?
Once you have more than one implementation, then you need to qualify each of them and during auto-wiring, you would need to use the @Qualifier
annotation to inject the right implementation, along with @Autowired
annotation. If you are using @Resource (J2EE semantics), then you should specify the bean name using the name
attribute of this annotation.
Why do we autowire the interface and not the implemented class?
Firstly, it is always a good practice to code to interfaces in general. Secondly, in case of spring, you can inject any implementation at runtime. A typical use case is to inject mock implementation during testing stage.
interface IA
{
public void someFunction();
}
class B implements IA
{
public void someFunction()
{
//busy code block
}
public void someBfunc()
{
//doing b things
}
}
class C implements IA
{
public void someFunction()
{
//busy code block
}
public void someCfunc()
{
//doing C things
}
}
class MyRunner
{
@Autowire
@Qualifier("b")
IA worker;
....
worker.someFunction();
}
Your bean configuration should look like this:
<bean id="b" class="B" />
<bean id="c" class="C" />
<bean id="runner" class="MyRunner" />
Alternatively, if you enabled component scan on the package where these are present, then you should qualify each class with @Component
as follows:
interface IA
{
public void someFunction();
}
@Component(value="b")
class B implements IA
{
public void someFunction()
{
//busy code block
}
public void someBfunc()
{
//doing b things
}
}
@Component(value="c")
class C implements IA
{
public void someFunction()
{
//busy code block
}
public void someCfunc()
{
//doing C things
}
}
@Component
class MyRunner
{
@Autowire
@Qualifier("b")
IA worker;
....
worker.someFunction();
}
Then worker
in MyRunner
will be injected with an instance of type B
.
Spring Autowiring class vs. interface?
Normally, both will work, you can autowire interfaces or classes.
There's probably an autoproxy generator somewhere in your context, which is wrapping your boo
bean in a generated proxy object. This proxy object will implement TheInterface
, but will not be a TheClass
. When using autoproxies, you need to program to the interface, not the implementation.
The likely candidate is transactional proxies - are you using Spring transactions, using AspectJ or @Transactional
?
What happens when an interface is Autowired vs the implementation
As long as there is only a single implementation of the interface and that implementation is annotated with @Component
with Spring's component scan enabled, Spring framework can find out the (interface, implementation) pair.
Once you have more than one implementation, then you need to qualify each of them and during auto-wiring, you would need to use the @Qualifier
annotation to inject the right implementation, along with @Autowired
annotation. If you are using @Resource
(J2EE), then you should specify the bean name using the name attribute of this annotation.
Normally, both will work, you can autowire interfaces or classes.
Spring autowire interface
@Autowired
is actually perfect for this scenario. You can either autowire a specific class (implemention) or use an interface.
Consider this example:
public interface Item {
}
@Component("itemA")
public class ItemImplA implements Item {
}
@Component("itemB")
public class ItemImplB implements Item {
}
Now you can choose which one of these implementations will be used simply by choosing a name for the object according to the @Component
annotation value
Like this:
@Autowired
private Item itemA; // ItemA
@Autowired
private Item itemB // ItemB
For creating the same instance multiple times, you can use the @Qualifier annotation to specify which implementation will be used:
@Autowired
@Qualifier("itemA")
private Item item1;
In case you need to instantiate the items with some specific constructor parameters, you will have to specify it an XML configuration file. Nice tutorial about using qulifiers and autowiring can be found HERE.
Spring boot autowiring an interface with multiple implementations
Use @Qualifier
annotation is used to differentiate beans of the same interface
Take look at Spring Boot documentation
Also, to inject all beans of the same interface, just autowire List
of interface
(The same way in Spring / Spring Boot / SpringBootTest)
Example below:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
public interface MyService {
void doWork();
}
@Service
@Qualifier("firstService")
public static class FirstServiceImpl implements MyService {
@Override
public void doWork() {
System.out.println("firstService work");
}
}
@Service
@Qualifier("secondService")
public static class SecondServiceImpl implements MyService {
@Override
public void doWork() {
System.out.println("secondService work");
}
}
@Component
public static class FirstManager {
private final MyService myService;
@Autowired // inject FirstServiceImpl
public FirstManager(@Qualifier("firstService") MyService myService) {
this.myService = myService;
}
@PostConstruct
public void startWork() {
System.out.println("firstManager start work");
myService.doWork();
}
}
@Component
public static class SecondManager {
private final List<MyService> myServices;
@Autowired // inject MyService all implementations
public SecondManager(List<MyService> myServices) {
this.myServices = myServices;
}
@PostConstruct
public void startWork() {
System.out.println("secondManager start work");
myServices.forEach(MyService::doWork);
}
}
}
For the second part of your question, take look at this useful answers first / second
Autowiring in concrete classes of an abstract class than implements an interface
I solved the issue by using the factory pattern as mentioned in this link (thanks to @user7294900 for providing the link)
I completely removed the abstract class TaskServiceImpl
. Instead I created two new interfaces ManualTaskService
and AutomatedTaskService
both extending TaskService
interface
public interface ManualTaskService extends TaskService {
}
public interface AutomatedTaskService extends TaskService {
}
Then I created a TaskServiceFactory
@Component
public class TaskServiceFactory {
@Autowired
private ManualTaskService manualTaskService;
@Autowired
private AutomatedTaskService automatedTaskService;
public TaskService getService(TaskType type) throws Exception {
switch (type) {
case MANUAL_TASK:
return manualTaskService;
case AUTOMATED_TASK:
return automatedTaskService;
default:
throw new Exception("Unrecognized task type");
}
}
}
Next I created implementations for both ManualTaskService
and AutomatedTaskService
@Service
public class ManualTaskServiceImpl implements ManualTaskService {
@Autowired
private TaskRepository taskRepository;
@Autowired
private ManualTaskHandlerService manualTaskHandlerService;
@Override
public Task findById(Long taskId) {
return taskRepository.findById(taskId).get();
}
@Override
public Task handleTask(Long taskId) throws Exception {
Task task = findById(taskId);
manualTaskHandlerService.handleManualTask(task);
task.setCompleted(true);
return taskRepository.save(task);
}
}
@Service
public class AutomatedTaskServiceImpl implements AutomatedTaskService {
@Autowired
private TaskRepository taskRepository;
@Autowired
private AutomatedTaskHandlerService automatedTaskHandlerService;
@Override
public Task findById(Long taskId) {
return taskRepository.findById(taskId).get();
}
@Override
public Task handleTask(Long taskId) throws Exception {
Task task = findById(taskId);
automatedTaskHandlerService.handleAutomatedTask(task);
task.setCompleted(true);
return taskRepository.save(task);
}
}
Finally I updated the controller to get the task type from the user and then use the TaskServiceFactory
to get the correct service instance based on the type
@Controller
@RequestMapping("/api/task")
public class TaskController {
@Autowired
private TaskServiceFactory taskServiceFactory;
@PostMapping("/{taskId}/handle")
public String handle(Model model, @PathVariable("taskId") Long taskId, HttpServletRequest request) throws Exception {
try {
TaskType type = TaskType.valueOf(request.getParameter("type"));
Task task = taskServiceFactory.getService(type).handleTask(taskId, request);
model.addAttribute("task", task);
} catch (Exception e) {
return "errorpage";
}
return "successpage";
}
}
Related Topics
How to Pause and Resume a Thread in Java from Another Thread
How to Tell If a Checkbox Is Selected in Selenium for Java
How to Do a Fractional Power on Bigdecimal in Java
Sending the Same But Modifed Object Over Objectoutputstream
Why Does the "Protected" Modifier in Java Allow Access to Other Classes in Same Package
How to Get Unique Values from Array
Reserved Words as Names or Identifiers
How Does Memory Reordering Help Processors and Compilers
Is There a Method for String Conversion to Title Case
What Is the Best Way Get the Symmetric Difference Between Two Sets in Java
Highlights Substring in the Tablecell(S) Which Is Using for Jtable Filetering