Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic Controller in Spring MVC

Tags:

spring-mvc

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 ?

like image 308
abiieez Avatar asked Jun 10 '13 03:06

abiieez


1 Answers

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");
    }
}
like image 95
matsev Avatar answered Oct 25 '22 03:10

matsev