Question

I'm using Spring 4 with Spring Data MongoDB and want to get rid of some boilerplate code in my controllers.

I just want to replace this:

@RequestMapping("{id}")
void a(@PathVariable ObjectId id) {
   DomainObject do = service.getDomainObjectById(id);
   // ...
}

with this:

@RequestMapping("{id}")
void a(@PathVariable("id") DomainObject do) {
  // ...
}

At the moment I've got to write a pair of PropertyEditorSupport and @ControllerAdvice classes for each domain object I have:

@Component
public class SomeDomainObjectEditor extends PropertyEditorSupport {
    @Autowired
    SomeDomainObjectService someDomainObjectService;

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        setValue(someDomainObjectService.getById(new ObjectId(text)));
    }

    @Override
    public String getAsText() {
        SomeDomainObject value = (SomeDomainObject) getValue();
        return (value != null ? value.getId().toString() : null);
    }
}

@ControllerAdvice
public class SomeDomainObjectControllerAdvice {
    @Autowired
    SomeDomainObjectEditor someDomainObjectEditor;

    @InitBinder
    public void register(WebDataBinder binder, WebRequest request) {
        binder.registerCustomEditor(SomeDomainObject.class, someDomainObjectEditor);
    }
}

And I can't figure out an easy way to get this done in a generic way, because I have a lot of domain objects and all behave the same.

All my domain objects implement BaseDocument<ID> and thus have the getId() method. So basically I want something like this:

public class BaseDocumentPropertyEditor extends PropertyEditorSupport { ... }

It would also be okay (= nice) to have this working, using a Converter<String, BaseDocument<?>> which can be used also in other places within the Spring Framework.

My main problem is, that I can't imagine an easy way to find the corresponding @Service in order to fetch the domain object from DB. (I can't use the Repository because of access restriction for certain data).

Hopefully you have some advice. Thank you!

Était-ce utile?

La solution

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 :-).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top