Конвертер из @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 метода:

  1. public boolean resolveFor(MethodParameter methodParameter)

используется для определения, если ArgumentResolver смогите быть использовано для того чтобы общаться с methodParameter. Например:

public boolean resolveFor(MethodParameter methodParameter) {
    return UserDefinedType.class.isAssignableFrom(methodParameter.getParameterType());
}

определяет, что это ArgumentResover будет использоваться для UserDefinedType.

  1. 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.

  1. 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);
    }
}

}

``