5

When having a bean object for request params in spring: is there a way to define an alias for a bean properties?

@RestController
public class MyServlet {
   @GetMapping
   public void test(MyReq req) {
   }
}

public class MyReq {
   @RequestParam("different-name") //this is invalid
   private String name;
   private int age;
}

Of course @RequestParam does not work, but is there a similar annotation I could use?

membersound
  • 81,582
  • 193
  • 585
  • 1,120

4 Answers4

2

With the following approach it is possible to set custom names using an annotation:

See Bozhos answer: How to customize parameter names when binding spring mvc command objects

As I'm using spring 4, the custom resolver can be added as follows.

@Configuration
public class AdapterConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        super.addArgumentResolvers(argumentResolvers);
        argumentResolvers.add(new AnnotationServletModelAttributeResolver(false));
    }
}

It then can be used on the get query bean as follows:

@SupportsCustomizedBinding
public class MyReq {
   @CommandParameter("different-name") //this is valid now!
   private String name;
}

Further, as I also like to match the get query parameters case insensitive, I'm using the following class:

https://github.com/mdeinum/spring-utils/blob/master/src/main/java/biz/deinum/web/filter/CaseInsensitiveRequestFilter.java

It can be wired as follows:

@Bean
public CaseInsensitiveRequestFilter caseInsensitiveFilter() {
    return new CaseInsensitiveRequestFilter();
}
membersound
  • 81,582
  • 193
  • 585
  • 1,120
2

Request parameter are bind by setters. You can add an extra setter with original parameter name. Something like:

public class MyReq {
   private String name;
   private int age;

   public void setDifferentName(String differentName) {
      this.name=differentName;
   }
}

NOTE: it will work only if your parameter is camel case like differentName=abc. Will not work with different-name=abc.

Sergiy Dakhniy
  • 538
  • 1
  • 4
  • 15
1

You can use setters for that. Given your example:

@SpringBootApplication
public class So44390404Application {

    public static void main(String[] args) {
        SpringApplication.run(So44390404Application.class, args);
    }

    @RestController
    public static class MyServlet {
        @GetMapping
        public String test(MyReq req) {
            return req.toString();
        }
    }

    public static class MyReq {
        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void setDifferent_Name(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "{" + name + age + '}';
        }
    }
}

And caller might use:

$so44390404 curl -XGET 'http://localhost:8000?name=adam&age=42'          
{adam42}%
$so44390404 curl -XGET 'http://localhost:8000?Different_Name=John&age=23'
{John23}% 

Update

Well, if you're dealing with hyphen-named parameters things become a little bit trickier.

Basically you can:

  1. Make a filter which will normalize hyphened parameter names, so spring can bind them successfully.
  2. Receive all request params as a raw map in your controller, normalize keys and then populate object with all type conversion stuff by yourself.

An option with filter might look like this:

@Component
public static class CustomRequestParametersFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        filterChain.doFilter(new RequestParameterNormalizerWrapper(request), response);
    }

    public static class RequestParameterNormalizerWrapper extends HttpServletRequestWrapper {
        public static final String HYPHEN = "-";
        private final Map<String, String[]> parameterMap = new HashMap<>();

        public RequestParameterNormalizerWrapper(HttpServletRequest request) {
            super(request);

            for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
                if (entry.getKey().contains(HYPHEN)) {
                    parameterMap.put(normalize(entry.getKey()), entry.getValue());
                }
                else {
                    parameterMap.put(entry.getKey(), entry.getValue());
                }
            }
        }

        private String normalize(final String key) {
            if (key.contains(HYPHEN)) {
                return WordUtils.capitalizeFully(key, HYPHEN.charAt(0)).replaceAll(HYPHEN, "");
            }
            return key;
        }

        @Override
        public Map<String, String[]> getParameterMap() {
            return Collections.unmodifiableMap(this.parameterMap);
        }

        @Override
        public Enumeration<String> getParameterNames() {
            return Collections.enumeration(this.parameterMap.keySet());
        }

        @Override
        public String getParameter(String name) {
            return super.getParameter(normalize(name));
        }

        @Override
        public String[] getParameterValues(String name) {
            return parameterMap.get(normalize(name));
        }
    }
}

With that previous example should work as is.

The second option might be:

@RestController
public static class MyServlet {

    @GetMapping
    public String test(@RequestParam Map<String, String> pvs) {
        final MyReq req = new MyReq();
        final BeanWrapper beanWrapper = new HyphenAwareBeanWrapper(req);
        beanWrapper.setPropertyValues(pvs);
        return req.toString();
    }
}

And the wrapper:

public static class HyphenAwareBeanWrapper extends BeanWrapperImpl {
    public static final String HYPHEN = "-";

    public HyphenAwareBeanWrapper(Object object) {
        super(object);
    }

    @Override
    public void setPropertyValues(Map<?, ?> map) throws BeansException {
        final ArrayList<PropertyValue> propertyValueList = new ArrayList<>(map.size());
        for (Map.Entry<?, ?> entry : map.entrySet()) {
            final String key = entry.getKey().toString().contains(HYPHEN)
                    ? WordUtils.capitalizeFully(entry.getKey().toString(), HYPHEN.charAt(0)).replaceAll(HYPHEN, "")
                    : entry.getKey().toString();
            propertyValueList.add(new PropertyValue(key, entry.getValue()));
        }
        super.setPropertyValues(new MutablePropertyValues(propertyValueList));
    }
}

Testing:

$ curl -XGET 'http://localhost:8000?name=John&age=42'
{John42}%
$ curl -XGET 'http://localhost:8000?different-name=John&age=42'
{John42}%
Bohdan Levchenko
  • 3,411
  • 2
  • 24
  • 28
  • Well but if I rename the setter, I could as well rename the property + getter + setter completely. But that's not what I want. Imagine the input parameter should be send as "MY-INPUT". A setter like `my-input=test` is an invalid variable name. Though I want to provide a feature where such params are possible. – membersound Jun 07 '17 at 06:56
  • Yes, you can rename field and getter as well, but you don't have to. You can have as many setters for one property as you want. As for the hypen-separated parameter names - I have updated an answer. – Bohdan Levchenko Jun 07 '17 at 12:34
1

Similar to Sergiy Dakhniy/Bohdan Levchenko comment. Request parameter are bind by setters. You can add an extra setter with parameter name from your incoming request. Something like:

@GetMapping(value = "/do-something")
public ResponseEntity<String> handleDoSomething(@Valid MyReq myReq) {
...
}
public class MyReq {
  private String name;

  public void setDifferent_name(String name) {
    this.name = name;
  }
}

for example: http://www.example.com/do-something?different_name=Joe

Alan Ho
  • 646
  • 9
  • 5