Template variables with ControllerLinkBuilder

后端 未结 7 1992
生来不讨喜
生来不讨喜 2021-02-13 17:54

I want my response to include this:

\"keyMaps\":{
  \"href\":\"http://localhost/api/keyMaps{/keyMapId}\",
  \"templated\":true
 }

That\'s easy

7条回答
  •  闹比i
    闹比i (楼主)
    2021-02-13 18:12

    It looks to me like the current state of Spring-HATEOAS doesn't allow this via the ControllerLinkBuilder (I'd very much like to be proven wrong), so I have implemented this myself using the following classes for templating query parameters:

    public class TemplatedLinkBuilder {
    
        private static final TemplatedLinkBuilderFactory FACTORY = new TemplatedLinkBuilderFactory();
        public static final String ENCODED_LEFT_BRACE = "%7B";
        public static final String ENCODED_RIGHT_BRACE = "%7D";
    
        private UriComponentsBuilder uriComponentsBuilder;
    
        TemplatedLinkBuilder(UriComponentsBuilder builder) {
            uriComponentsBuilder = builder;
        }
    
        public static TemplatedLinkBuilder linkTo(Object invocationValue) {
            return FACTORY.linkTo(invocationValue);
        }
    
        public static  T methodOn(Class controller, Object... parameters) {
            return DummyInvocationUtils.methodOn(controller, parameters);
        }
    
        public Link withRel(String rel) {
            return new Link(replaceTemplateMarkers(uriComponentsBuilder.build().toString()), rel);
        }
    
        public Link withSelfRel() {
            return withRel(Link.REL_SELF);
        }
    
        private String replaceTemplateMarkers(String encodedUri) {
            return encodedUri.replaceAll(ENCODED_LEFT_BRACE, "{").replaceAll(ENCODED_RIGHT_BRACE, "}");
        }
    
    }
    

    and

    public class TemplatedLinkBuilderFactory {
    
        private final ControllerLinkBuilderFactory controllerLinkBuilderFactory;
    
        public TemplatedLinkBuilderFactory() {
            this.controllerLinkBuilderFactory = new ControllerLinkBuilderFactory();
        }
    
        public TemplatedLinkBuilder linkTo(Object invocationValue) {
            ControllerLinkBuilder controllerLinkBuilder = controllerLinkBuilderFactory.linkTo(invocationValue);
            UriComponentsBuilder uriComponentsBuilder = controllerLinkBuilder.toUriComponentsBuilder();
    
            Assert.isInstanceOf(DummyInvocationUtils.LastInvocationAware.class, invocationValue);
            DummyInvocationUtils.LastInvocationAware invocations = (DummyInvocationUtils.LastInvocationAware) invocationValue;
            DummyInvocationUtils.MethodInvocation invocation = invocations.getLastInvocation();
            Object[] arguments = invocation.getArguments();
            MethodParameters parameters = new MethodParameters(invocation.getMethod());
    
            for (MethodParameter requestParameter : parameters.getParametersWith(RequestParam.class)) {
                Object value = arguments[requestParameter.getParameterIndex()];
                if (value == null) {
                    uriComponentsBuilder.queryParam(requestParameter.getParameterName(), "{" + requestParameter.getParameterName() + "}");
                }
            }
            return new TemplatedLinkBuilder(uriComponentsBuilder);
        }
    }
    

    Which embeds the normal ControllerLinkBuilder and then uses similar logic to parse for @RequestParam annotated parameters that are null and add these on to the query parameters. Also, our client resuses these templated URIs to perform further requests to the server. To achieve this and not need to worry about stripping out the unused templated params, I have to perform the reverse operation (swapping {params} with null), which I'm doing using a custom Spring RequestParamMethodArgumentResolver as follows

    public class TemplatedRequestParamResolver extends RequestParamMethodArgumentResolver {
    
        public TemplatedRequestParamResolver() {
            super(false);
        }
    
        @Override
        protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
            Object value = super.resolveName(name, parameter, webRequest);
            if (value instanceof Object[]) {
                Object[] valueAsCollection = (Object[])value;
                List resultList = new LinkedList();
                for (Object collectionEntry : valueAsCollection) {
                    if (nullifyTemplatedValue(collectionEntry) != null) {
                        resultList.add(collectionEntry);
                    }
                }
                if (resultList.isEmpty()) {
                    value = null;
                } else {
                    value = resultList.toArray();
                }
            } else{
                value = nullifyTemplatedValue(value);
            }
            return value;
        }
    
        private Object nullifyTemplatedValue(Object value) {
            if (value != null && value.toString().startsWith("{") && value.toString().endsWith("}")) {
                value = null;
            }
            return value;
        }
    
    }
    
    
    

    Also this needs to replace the existing RequestParamMethodArgumentResolver which I do with:

    @Configuration
    public class ConfigureTemplatedRequestParamResolver {
    
        private @Autowired RequestMappingHandlerAdapter adapter;
    
        @PostConstruct
        public void replaceArgumentMethodHandlers() {
            List argumentResolvers = new ArrayList(adapter.getArgumentResolvers());
            for (int cursor = 0; cursor < argumentResolvers.size(); ++cursor) {
                HandlerMethodArgumentResolver handlerMethodArgumentResolver = argumentResolvers.get(cursor);
                if (handlerMethodArgumentResolver instanceof RequestParamMethodArgumentResolver) {
                    argumentResolvers.remove(cursor);
                    argumentResolvers.add(cursor, new TemplatedRequestParamResolver());
                    break;
                }
            }
            adapter.setArgumentResolvers(argumentResolvers);
        }
    
    }
    

    Unfortunately, although { and } are valid characters in a templated URI, they are not valid in a URI, which may be a problem for your client code depending on how strict it is. I'd much prefer a neater solution built into Spring-HATEOAS!

    提交回复
    热议问题