Конвертер из @PathVariable DomainObject в строку? (использование ControllerLinkBuilder.methodOn)
Я пытаюсь вызвать весны ControllerLinkBuilder.methodOn() С типом без строки, который всегда терпит неудачу. И я не знаю, какого рода Converter использовать и где его зарегистрировать.
вот мой контроллер:
@RestController
@RequestMapping("/companies")
class CompanyController {
@RequestMapping(value="/{c}", method=RequestMethod.GET)
void getIt(@PathVariable Company c) {
System.out.println(c);
Link link = linkTo(methodOn(getClass()).getIt(c));
}
}
на System.out.println(c) работает хорошо. Мой Company объект домена получает из БД. (Я использую DomainClassConverter)
но другой способ не работает:ConverterNotFoundException: No converter found capable of converting from type @PathVariable Company to type String
мне нужно Converter<Company, String>? И где я должен его зарегистрировать? Я попробовал что-то внутри addFormatters(FormatterRegistry registry) метод WebMvcConfigurationSupport, но он просто отображает ту же ошибку. Но, в конце концов, я не знаю, что именно я пытался сделать...
4 ответов
у меня была такая же проблема, это ошибка. Если вы не хотите копировать и вставлять на каждый контроллер, вы можете попробовать что-то подобное в своем WebMvcConfigurationSupport. Это работает на меня.
@Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
try {
Class<?> clazz = Class.forName("org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor$BoundMethodParameter");
Field field = clazz.getDeclaredField("CONVERSION_SERVICE");
field.setAccessible(true);
DefaultFormattingConversionService service = (DefaultFormattingConversionService) field.get(null);
for (Converter<?, ?> converter : beanFactory.getBeansOfType(Converter.class).values()) {
service.addConverter(converter);
}
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
нашел "решение". Это требует много копирования и вставки из классов Spring, но, по крайней мере, это работает!
в основном мне пришлось скопировать org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor и меняем две строки:
class AnnotatedParametersParameterAccessor {
...
static class BoundMethodParameter {
// OLD: (with this one you can't call addConverter())
// private static final ConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService();
// NEW:
private static final FormattingConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService();
...
public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationAttribute attribute) {
...
// ADD:
CONVERSION_SERVICE.addConverter(new MyNewConverter());
}
...
}
этот класс get используется ControllerLinkBuilderFactory. Поэтому мне пришлось скопировать и вставить, это тоже.
и этот сделать используется ControllerLinkBuilder. Также скопируйте и вставьте.
мой Converter просто так myDomainObject.getId().toString():
public class MyNewConverter implements Converter<Company, String> {
@Override
public String convert(Company source) {
return source.getId().toString();
}
}
теперь вы можете использовать copy & pasted ControllerLinkBuilder внутри контроллера и его работает, как ожидалось!
разработал рамки для отображения ссылок в spring hateoas и поддерживает аннотированные параметры (@PathVariable и @RequestParam) и произвольные типы параметров.
чтобы отобразить эти произвольные типы, вы должны создать Spring bean, который реализует com.github.osvaldopina.linkbuilder.argumentresolver.ArgumentResolver интерфейс.
интерфейс имеет 3 метода:
public boolean resolveFor(MethodParameter methodParameter)
используется для определения, если ArgumentResolver смогите быть использовано для того чтобы общаться с methodParameter. Например:
public boolean resolveFor(MethodParameter methodParameter) {
return UserDefinedType.class.isAssignableFrom(methodParameter.getParameterType());
}
определяет, что это ArgumentResover будет использоваться для UserDefinedType.
-
public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter)
используется для включения в uriTemplate, связанный с методом соответствующих частей шаблона. Например:
@Override
public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter) {
uriTemplateAugmenter.addToQuery("value1");
uriTemplateAugmenter.addToQuery("value2");
}
добавляет 2 параметра запроса (value1 и value2) в шаблон uri.
public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames)
задает в шаблоне значения для шаблона переменная. Например:
@Override
public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames) {
if (parameter != null && ((UserDefinedType) parameter).getValue1() != null) {
template.set("value1", ((UserDefinedType) parameter).getValue1());
}
else {
template.set("value1", "null-value");
}
if (parameter != null && ((UserDefinedType) parameter).getValue2() != null) {
template.set("value2", ((UserDefinedType) parameter).getValue2());
}
else {
template.set("value2", "null-value");
}
}
получает UserDefinedType экземпляр и используйте его для установки переменных шаблонов value1 и value2, определенных в augmentTemplate метод.
A ArgumentResolver полный пример будет выглядеть так:
@Component
public class UserDefinedTypeArgumentResolver implements ArgumentResolver {
@Override
public boolean resolveFor(MethodParameter methodParameter) {
return UserDefinedType.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter) {
uriTemplateAugmenter.addToQuery("value1");
uriTemplateAugmenter.addToQuery("value2");
}
@Override
public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames) {
if (parameter != null && ((UserDefinedType) parameter).getValue1() != null) {
template.set("value1", ((UserDefinedType) parameter).getValue1());
}
else {
template.set("value1", "null-value");
}
if (parameter != null && ((UserDefinedType) parameter).getValue2() != null) {
template.set("value2", ((UserDefinedType) parameter).getValue2());
}
else {
template.set("value2", "null-value");
}
}
}
и для следующего компоновщика ссылок:
linksBuilder.link()
.withRel("user-type")
.fromControllerCall(RootRestController.class)
.queryParameterForUserDefinedType(new UserDefinedType("v1", "v2"));
к следующему методу:
@RequestMapping("/user-defined-type")
@EnableSelfFromCurrentCall
public void queryParameterForUserDefinedType(UserDefinedType userDefinedType) {
}
создаст следующую ссылку:
{
...
"_links": {
"user-type": {
"href": "http://localhost:8080/user-defined-type?value1=v1&value2=v2"
}
...
}
}
полная конфигурация в весенней загрузке. то же, что и ответ Франко Готуссо, просто более подробно. ``
/** * Этот файл конфигурации должен исправить ошибку Spring Hateoas. * пожалуйста, проверьте https://github.com/spring-projects/spring-hateoas/issues/118. */
@компонентов открытый класс MvcConfig расширяет WebMvcConfigurerAdapter {
@Autowired
private ApplicationContext applicationContext;
@Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
try {
Class<?> clazz = Class.forName("org.springframework.hateoas.mvc."
+ "AnnotatedParametersParameterAccessor$BoundMethodParameter");
Field field = clazz.getDeclaredField("CONVERSION_SERVICE");
field.setAccessible(true);
DefaultFormattingConversionService service =
(DefaultFormattingConversionService) field.get(null);
for (Formatter<?> formatter : applicationContext
.getBeansOfType(Formatter.class).values()) {
service.addFormatter(formatter);
}
for (Converter<?, ?> converter : applicationContext
.getBeansOfType(Converter.class).values()) {
service.addConverter(converter);
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
``