10

I am using Spring MVC 3.2.2

I have defined a custom HandlerMethodArgumentResolver class like this

public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {

  public CurrentUserArgumentResolver() {
    System.out.println("Ready");
  }

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

  @Override
  public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

      Principal principal = webRequest.getUserPrincipal();
      System.out.println("*** Principal ***: " + principal);
      return principal;
  }
}

And added the following to my app-servlet.xml

<mvc:annotation-driven>
  <mvc:argument-resolvers>
    <beans:bean class="my.package.CurrentUserArgumentResolver" lazy-init="false"/>
  </mvc:argument-resolvers>
</mvc:annotation-driven>

and created an annotation for CurrentUser

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

}

When I start the application up the class is constructed as I can see the log message "Ready" but the resolver does not execute when I annotate a controller method as such (in a class that has @Controller annotation)

@RequestMapping(method = RequestMethod.POST, value = "/update")
public ModelAndView update(@RequestParam MultipartFile background, @CurrentUser Principal principal) {
 ...
}

If I put breakpoints on either method in the CurrentUserArgumentResolver class, neither work. So not sure what I am missing?

tereško
  • 58,060
  • 25
  • 98
  • 150
Ayub Malik
  • 2,488
  • 6
  • 27
  • 42
  • 1
    It look's exactly like my answer at this question: http://stackoverflow.com/questions/8764545/best-practice-for-getting-active-users-userdetails/8769670#8769670 - I can't see any mistake in the posted code. So maybe the problem is somewhere else: check that everything complies and is deployed correctly. Check that there is only one MVC:anotationdriven. Check that the controller beam is only found by the component scan from the app-servlet.xml – Ralph Jun 27 '13 at 02:44
  • Yes my code was based on a combination of your answer and Spring docs. All the code compiles and the controller method is called correctly, its just that the principal is null. I would at least expect the breakpoint on the 'supportsParameter' to be called. – Ayub Malik Jun 27 '13 at 08:27
  • What is the value of the controller method parameter (principal)? Is it null? – Ralph Jun 27 '13 at 08:32
  • @AyubMalik i have the same issue, i have tried the mvc:annotation or the config the customresolver is not getting invoked. I am using the annotation on String param – tinker_fairy Feb 06 '20 at 12:08

3 Answers3

19

If anybody ever wants to prioritize custom handlers over default handlers added by spring, here's a snippet that does it for me, I do this in a @Configuration file

private @Inject RequestMappingHandlerAdapter adapter;

@PostConstruct
public void prioritizeCustomArgumentMethodHandlers () {
  List<HandlerMethodArgumentResolver> argumentResolvers = 
      new ArrayList<> (adapter.getArgumentResolvers ());
  List<HandlerMethodArgumentResolver> customResolvers = 
      adapter.getCustomArgumentResolvers ();
  argumentResolvers.removeAll (customResolvers);
  argumentResolvers.addAll (0, customResolvers);
  adapter.setArgumentResolvers (argumentResolvers);
}
Lev Kuznetsov
  • 3,520
  • 5
  • 20
  • 33
  • How can this be achieved using XML configuration? – Andy Mar 03 '15 at 19:01
  • It doesn't have to be an `@Configuration` you can put this on any singleton bean, but that's a hack. You don't have to change your entire project to java config, you can have a class with `@Configuration` on your autodiscovery path that does precisely this. All your other configuration would be in XML. Alternatively, you can extend the `RequestMappingHandlerAdapter`, put this code there (you wouldn't have to inject itself obviously) and create the adapter bean in XML, however I would consider the configuration approach cleaner since you are in fact configuring the adapter, – Lev Kuznetsov Mar 03 '15 at 23:09
  • You may need to do other configuration unavailable via XML and you'll have to put it there in your derived class which will become a spot to collect a bunch of things having nothing to do with each other. – Lev Kuznetsov Mar 03 '15 at 23:11
  • Excellent explanation! Thanks very much. I was also looking at the `@Configuration` in general, and wondered if the instance was destroyed and/or the class was unloaded after the code ran. I couldn't find that information anywhere, but it would make sense that you wouldn't want some bean sitting in memory (no matter how tiny) if its only purpose was startup configuration. Either way, using `@Configuration` and registering it as a `` in my XML servlet configuration did exactly what I wanted it to. Thanks! – Andy Mar 04 '15 at 15:26
  • I'm wired about why spring put custom resolver at bottom? There must be some request cannot chosen custom resolver. So, the question is: is there some side effects when I hacked like this? It's violating spring's design. – jixiang Nov 14 '17 at 10:24
  • This answer my question here, thanks! https://stackoverflow.com/questions/67745858/override-default-message-when-responsebody-is-null – Miguel Cabanes May 29 '21 at 00:18
3

OK I worked out that Spring was already resolving the Principal object in my above example and so my argument resolver was not kicking in. I had been lazy and added the @CurrentUser annotation to an existing parameter.

So I changed my example

@RequestMapping(method = RequestMethod.POST, value = "/update")
public ModelAndView update(@RequestParam MultipartFile background, @CurrentUser Principal principal) {
  ...
}

to use my User model class

@RequestMapping(method = RequestMethod.POST, value = "/update")
public ModelAndView update(@RequestParam MultipartFile background, @CurrentUser User user) {
  ...
}

and now it works!

Ayub Malik
  • 2,488
  • 6
  • 27
  • 42
  • 1
    Thank you for this answer - it pointed me in the right direction. One of my classes I wanted to resolve was extending AbstractAuthenticationToken and as such, it was also resolved by Spring without me even knowing. My solution was to create an interface, use it as a method parameter and resolve the interface instead. – Petr Dvořák Dec 27 '16 at 13:05
  • @PetrDvořák I had the exact same issue. I also fixed it by wiring to an Interface. – Kenny Cason Apr 01 '17 at 18:49
-1

I use the property customArgumentResolvers to load bean, like this:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                    <property name="supportedMediaTypes">
                        <list>
                            <value>text/html;charset=UTF-8</value>
                            <value>text/plain;charset=UTF-8</value>
                            <value>application/json;charset=UTF-8</value>
                        </list>
                    </property>
                </bean>
            </list>
        </property>
         <property name="customArgumentResolvers">
         <list>
             <bean id="MyTestMethodArgumentResolver" class="com.gst.authorization.resolvers.MyTestMethodArgumentResolver"></bean>           
             <bean id="currentUserMethodArgumentResolver" class="com.gst.authorization.resolvers.CurrentUserMethodArgumentResolver">
                            <property name="userModelClass" value="com.gst.model.appuser.AppUser" />
                    <property name="userModelRepository" ref="userRepository" />
                </bean>
         </list>         
         </property>
    </bean> 
    <mvc:annotation-driven />
User42
  • 970
  • 1
  • 16
  • 27
zwn
  • 1
  • 1