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?

4
  • 1
    It look's exactly like my answer at this question: stackoverflow.com/questions/8764545/… - 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 Commented Jun 27, 2013 at 2: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. Commented Jun 27, 2013 at 8:27
  • What is the value of the controller method parameter (principal)? Is it null? Commented Jun 27, 2013 at 8: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 Commented Feb 6, 2020 at 12:08

3 Answers 3

20

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);
}
Sign up to request clarification or add additional context in comments.

6 Comments

How can this be achieved using XML configuration?
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, <CONTINUED>
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.
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 <bean> in my XML servlet configuration did exactly what I wanted it to. Thanks!
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.
|
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!

2 Comments

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.
@PetrDvořák I had the exact same issue. I also fixed it by wiring to an Interface.
-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 />

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.