NEWER:
If you also want proper Exception handling, you should use DomainClassPropertyEditorRegistrar
, because DomainClassConverter
swallows underlying exceptions...
Here we go! Just update your WebMvcConfigurationSupport
with:
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer();
initializer.setPropertyEditorRegistrar(domainClassPropertyEditorRegistrar());
return adapter;
}
@Bean
public DomainClassPropertyEditorRegistrar domainClassPropertyEditorRegistrar() {
return new DomainClassPropertyEditorRegistrar();
}
(Maybe @Bean
is unnecessary, but at least it works this way)
NEW:
Spring Data already provides everything I need: DomainClassConverter
Just put
@Bean
public DomainClassConverter<?> domainClassConverter() {
return new DomainClassConverter<FormattingConversionService>(mvcConversionService());
}
in the WebMvcConfigurationSupport
class and it all works out of the box!
OLD:
My final solution was to just stay with the one pair of classes per domain object approach. I just built 2 abstract classes plus an interface, to minimize the effort:
1. PropertyEditor
public abstract class AbstractEntityEditor<ID extends Serializable, SERVICE extends CanGetEntityById<?, ID>> extends PropertyEditorSupport {
@Autowired
SERVICE service;
@Autowired
ConversionService cs;
final Class<ID> id;
public AbstractEntityEditor(Class<ID> id) {
this.id = id;
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(service.getById(cs.convert(text, id)));
}
}
2. ControllerAdvice
public abstract class AbstractEntityEditorControllerAdvice<EDITOR extends PropertyEditor> {
@Autowired
EDITOR editor;
final Class<?> entity;
public AbstractEntityEditorControllerAdvice(Class<?> entity) {
this.entity = entity;
}
@InitBinder
public void register(WebDataBinder binder, WebRequest request) {
binder.registerCustomEditor(entity, editor);
}
}
3. Service interface to retrieve an domain object
public interface CanGetEntityById<ENTITY, ID extends Serializable> {
ENTITY getById(ID id) throws NotFoundException;
}
And here's a sample use case:
1.
@Component
public class UserEditor extends AbstractEntityEditor<ObjectId, UserService> {
public UserEditor() {
super(ObjectId.class);
}
}
2.
@ControllerAdvice
public class UserControllerAdvice extends AbstractEntityEditorControllerAdvice<UserEditor>{
public UserControllerAdvice() {
super(User.class);
}
}
3.
public interface UserService extends GetEntityById<User, ObjectId> { }
4.
@Service
public class UserServiceImpl implements UserService {
public User getById(ObjectId id) throws NotFoundException {
// fetch User from repository and return
}
}
Maybe there's a way to make it a bit better, but at least it works! And now it's just 5 lines of code to write :-)
.