Reputation: 851
I am using Spring boot and I have the following Task model
public class Task {
private String name;
private TaskType type; // ManualTask, AutomatedTask
private boolean completed;
//....other fields
//getters and setters
}
A controller
@Controller
@RequestMapping("/api/task")
public class TaskController {
@Autowired
private TaskService taskService;
@GetMapping("/{taskId}/handle")
public String handle(Model model, @PathVariable("taskId") Long taskId) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
try {
Task task = taskService.handle(taskId);
model.addAttribute("task", task);
} catch (Exception e) {
return "errorpage";
}
return "successpage";
}
}
I have an interface
public interface TaskService {
Task findById(Long taskId);
Task handleTask(Long taskId) throws ClassNotFoundException, InstantiationException, IllegalAccessException;
}
An abstract class implements the interface:
@Service
public abstract class TaskServiceImpl implements TaskService {
@Autowired
private TaskRepository taskRepository;
private static final String PATH_OF_CLASS = "com.task.service.impl";
protected abstract Task doTypeSpecificTask(Long taskId);
@Override
public Task findById(Long taskId) {
return taskRepository.findById(taskId).get();
}
@Override
public Task handleTask(Long taskId) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Task task = findById(taskId);
TaskServiceImpl service = getHandlerService(task);
return service.doTypeSpecificTask(taskId);
}
private TaskServiceImpl getHandlerService(Task task) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
String serviceClassName = PATH_OF_CLASS.concat(".").concat(task.getTaskType().getName()).concat("Service");
Class<?> serviceClass = Class.forName(serviceClassName);
if (!TaskServiceImpl.class.isAssignableFrom(serviceClass)) {
throw new RuntimeException("Service class " + serviceClassName + " did not implements " + TaskServiceImpl.class.getName());
}
Object serviceObject = serviceClass.newInstance();
TaskServiceImpl service = (TaskServiceImpl) serviceObject;
return service;
}
}
And concrete services that extend the abstract class
@Service
@Primary
public class ManualTaskService extends TaskServiceImpl {
@Autowired
private TaskRepository taskRepository;
@Autowired
private ManualTaskHandlerService manualTaskHandlerService;
@Override
protected Task doTypeSpecificTask(Long taskId) {
Task task = findById(taskId);
manualTaskHandlerService.handleManualTask(task);
task.setCompleted(true);
return taskRepository.save(task);
}
}
@Service
public class AutomatedTaskService extends TaskServiceImpl {
@Autowired
private TaskRepository taskRepository;
@Autowired
private AutomatedTaskHandlerService automatedTaskHandlerService;
@Override
protected Task doTypeSpecificTask(Long taskId) {
Task task = findById(taskId);
automatedTaskHandlerService.handleAutomatedTask(task);
task.setCompleted(true);
return taskRepository.save(task);
}
}
public interface TaskRepository extends JpaRepository<Task, Long> {
}
The ManualTaskService
or AutomatedTaskService
is selected dynamically based on the type of task on runtime.
Now, without the @Primary
, I get the following error:
Field taskService in com.test.controller.TaskController required a single bean, but 2 were found:
- manualTaskService
- automatedTaskService
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
With @Primary
set in ManualTaskService, doTypeSpecificTask
in ManualTaskService works but in AutomatedTaskService it fails because of automatedTaskHandlerService.handleAutomatedTask(task)
. Also calls to taskRepository from AutomatedTaskService fail.
I've tried using @Qualifier
as well as defining all @Autowired
in the abstract class as protected but nothing works. What am I doing wrong?
Upvotes: 1
Views: 553
Reputation: 851
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";
}
}
Upvotes: 2
Reputation: 58774
You should have different names to each Qualifier:
@Autowired
@Qualifier("manualTaskService")
private TaskServiceImpl manualTaskService;
@Autowired
@Qualifier("automatedTaskService")
private TaskServiceImpl automatedTaskService;
Which is defined in services:
@Service("manualTaskService")
public class ManualTaskService extends TaskServiceImpl {
@Service("automatedTaskService")
public class AutomatedTaskService extends TaskServiceImpl {
Upvotes: 2