I have been trying to write a generic controller to improve code re-usability. Below is what I have so far:
public abstract class CRUDController<T> {
@Autowired
private BaseService<T> service;
@RequestMapping(value = "/validation.json", method = RequestMethod.POST)
@ResponseBody
public ValidationResponse ajaxValidation(@Valid T t,
BindingResult result) {
ValidationResponse res = new ValidationResponse();
if (!result.hasErrors()) {
res.setStatus("SUCCESS");
} else {
res.setStatus("FAIL");
List<FieldError> allErrors = result.getFieldErrors();
List<ErrorMessage> errorMesages = new ArrayList<ErrorMessage>();
for (FieldError objectError : allErrors) {
errorMesages.add(new ErrorMessage(objectError.getField(),
objectError.getDefaultMessage()));
}
res.setErrorMessageList(errorMesages);
}
return res;
}
@RequestMapping(method = RequestMethod.GET)
public String initForm(Model model) {
service.initializeForm(model);
return "country"; // how can I make this generic too ?
}
}
T
can be things like Country, Item, Registration and User. The issue I am facing now the autowiring process failed with the following error:
No unique bean of type [com.ucmas.cms.service.BaseService] is defined: expected single matching bean but found 4: [countryServiceImpl, itemServiceImpl, registrationServiceImpl, userServiceImpl].
Is it possible to achieve what I need ? How can I fix this ?
I suggest that you add the BaseService
as a constructor parameter to the CRUDController
class:
public abstract class CRUDController<T> {
private final BaseService<T> service;
private final String initFormParam;
public CRUDController(BaseService<T> service, String initFormParam) {
this.service = service;
this.initFormParam;
}
@RequestMapping(value = "/validation.json", method = RequestMethod.POST)
@ResponseBody
public ValidationResponse ajaxValidation(@Valid T t, BindingResult result) {
// same as in the example
return res;
}
@RequestMapping(method = RequestMethod.GET)
public String initForm(Model model) {
service.initializeForm(model);
return initFormParam; // Now initialized by the constructor
}
}
Then you can use autowiring for each of the subclasses that extend it:
public class CountryController extends CRUDController<Country> {
@Autowired
public CountryController(CountryService countryService) {
super(countryService, "country");
}
}
Alternatively, you can use the @Qualifier annotation in your constructors to distinguis between different BaseService
implementation:
@Autowired
public CountryController(@Qualifier("countryServiceImpl") BaseService<Country> baseService) {
super(baseService, "country");
}
Update:
As of Spring 4.0 RC1, it is possible to autowire based on generic type. Consequently, you can use a generic BaseService<Country>
as a parameter when autowiring your constructor, and Spring will still be able to figure out which is the correct one without throwing any NoSuchBeanDefinitionException
:
@Controller
public class CountryController extends CRUDController<Country> {
@Autowired
public CountryController(BaseService<Country> countryService) {
super(countryService, "country");
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With