Spring MVC: как читать и изменять значение @PathVariable

Этот вопрос очень похож на этот, но я не знаю, с чего начать.

Предположим, у меня есть такое действие:

@GetMapping("/foo/{id}")
public Collection<Foo> listById(@PathVariable("id") string id) {
    return null;
}

Как можно было перехватить метод listById и изменить значение id (например: объединить строку, заполнить нулями и т. Д.)?

Мой сценарий заключается в том, что большая часть идентификаторов заполняется нулями слева (длина различается), и я не хочу оставлять это для своих вызовов ajax.

Ожидаемое решение:

@GetMapping("/foo/{id}")
public Collection<Foo> listById(@PathVariablePad("id", 4) string id) {
    // id would be "0004" on "/foo/4" calls
    return null;
}

person Henri    schedule 21.12.2017    source источник
comment
Скорее всего, через фильтр - this может помочь.   -  person Andrew S    schedule 21.12.2017
comment
Что не так с вызовом старого доброго метода? id = leftPad(id)?   -  person JB Nizet    schedule 21.12.2017


Ответы (2)


Хорошо, вот как я это сделал.

Поскольку мы не можем наследовать аннотации и, следовательно, цель @PathVariable - это только параметры, мы должны создать новую аннотацию, как показано ниже:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface PathVariablePad {

    int zeros() default 0;

    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

}

Теперь нам нужно создать HandlerMethodArgumentResolver. В этом случае, поскольку все, что я хочу, это добавить @PathVariable влево с нулями, мы собираемся унаследовать PathVariableMethodArgumentResolver, например:

public class PathVariablePadderMethodArgumentResolver extends PathVariableMethodArgumentResolver {

    private String leftPadWithZeros(Object target, int zeros) {
        return String.format("%1$" + zeros + "s", target.toString()).replace(' ', '0'); // Eeeewwwwwwwwwwww!
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(PathVariablePad.class);
    }

    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        PathVariablePad pvp = parameter.getParameterAnnotation(PathVariablePad.class);

        return new NamedValueInfo(pvp.name(), pvp.required(), leftPadWithZeros("", pvp.zeros()));
    }

    @Override
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        PathVariablePad pvp = parameter.getParameterAnnotation(PathVariablePad.class);

        return leftPadWithZeros(super.resolveName(name, parameter, request), pvp.zeros());
    }

}

Наконец, давайте зарегистрируем наш преобразователь аргументов метода (xml):

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="my.package.PathVariablePadderMethodArgumentResolver" />
    </mvc:argument-resolvers>
</mvc:annotation-driven>

Использование довольно простое, и вот как это сделать:

@GetMapping("/ten/{id}")
public void ten(@PathVariablePad(zeros = 10) String id) {
    // id would be "0000000001" on "/ten/1" calls
}

@GetMapping("/five/{id}")
public void five(@PathVariablePad(zeros = 5) String id) {
    // id would be "00001" on "/five/1" calls
}
person Henri    schedule 21.12.2017

Аннотация Spring @InitBinder и класс WebDataBinder помогут вам перехватить параметр и обработать его значение перед вызовом метода контроллера.

Документация:

Полный шаблон кода:

@RestController
public class FooController {

    @InitBinder
    private void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                super.setValue("000" + text);
            }
        } );
    }

    @GetMapping(value = "/foo/{id}")
    public Foo sayHello(
            @PathVariable(value = "id") String id
    ) {
        return new Foo(id);
    }

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class Foo {
        @XmlElement(name = "id")
        private String id;

        public Foo(String id) {
            this.id = id;
        }

        public Foo() {
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }
    }
}

И использование:

curl http://localhost:8080/foo/10 | xmllint --format -

Ответ:

<foo>
<id>00010</id>
</foo>
person yanefedor    schedule 21.12.2017
comment
Привет, спасибо за уделенное время! Хотя ваш ответ работает только для фиксированной длины и дополняет все строковые параметры в действиях контроллера. Также не имеет смысла копировать этот @InitBinder на каждый контроллер. - person Henri; 21.12.2017