3

I have a lot of Spring RestControllers with methods annotated with RequestMapping. I now would like to inject a custom object into these RequestMapping methods, and create an custom instance for each request.

I would like to write something like the following:

@RequestMapping("/search")
public SomeReturnObject foobar(@RequestParam("query") String query, MyRequestFoo foo) {
   // ...
}

Now I would like to create a mechanism, where each call to that method (i.e. each request) get a new instance of MyRequestFoo created and injected into the method. If this would work better with an parameter annotation instead of injecting by type, that would also be okay (e.g. @MyRequestInject MyRequestFoo foo).

I need to know if I can create now a method that creates a new instance of MyRequestFoo especially for that request, like the following:

public MyRequestFoo createRequestInstanceSomehow(HttpServletRequest request) {
   // extract some values from the HttpServletRequest and create a
   // new MyRequestFoo instance from that and return it
}

Is this possible by any means to create such a mechanism, so that I can inject custom per request objects into my request handling methods?

Tim Roes
  • 1,406
  • 1
  • 12
  • 22

4 Answers4

4

Spring MVC has a arguments resolver construct that directly supports your request. Every handler method annotated with @RequestMapping will be subject to argument resolving, where the framework scans through the handler arguments, checks the type and instantiates an appropriate object. That is the mechanism behind injecting request, model and a number of other types, just by declaring the object in the handler's method signature.

You can write a custom argument resolver to have the custom types resolved and available in the method. The procedure is simple three step process

  1. Make a POJO class, in your case MyRequestFoo

  2. Make a resolver, e.g.

      public class MyRequestFooResolver implements HandlerMethodArgumentResolver {
    
            @Override
            public boolean supportsParameter(MethodParameter parameter) {
    
                return parameter.getParameterType().equals(MyRequestFoo.class);
            }
    
            @Override
            public Object resolveArgument(MethodParameter parameter, 
                                          ModelAndViewContainer mavContainer,
                                          NativeWebRequest webRequest, 
                                          WebDataBinderFactory binderFactory)
            throws Exception {
    
                return new MyRequestFoo();
            }
        }
    

3.Register a resolver

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

or in java config

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

  @Override
  public void addArgumentResolvers(List< Handlermethodargumentresolver > argumentResolvers) {
        MyRequestFooResolver myRequestFooResolver = new MyRequestFooResolver ();
        argumentResolvers.add(myRequestFooResolver );
  }
}

Than you use it just by adding the type as a handler method argument

@RequestMapping("/search")
public SomeReturnObject search(MyRequestFoo foo) {
   // ...
}
Master Slave
  • 27,771
  • 4
  • 57
  • 55
  • I've also posted an answer now that works. Could you perhaps elaborate what is more clean and a "better" approach from your (or springs) perspective? – Tim Roes Mar 25 '15 at 08:57
  • 1
    I've seen the discussion and posted an answer anyways as an alternative, because custom argument resolving is a concept directly supporting your requirement. I would restrain from debating on whats better, but this is the approach I would use. – Master Slave Mar 25 '15 at 09:04
2

What about putting an instance variable of type MyRequestFoo on the Controller class and Autowire it changing the default scope from "Singleton" to "Request" on the Bean definition?

Check out this link or the Spring reference sheet!

Community
  • 1
  • 1
lateralus
  • 1,020
  • 1
  • 12
  • 35
  • But that would require the whole controller to be generated new for each request? Generating controllers new for each request doesn't seem like something that is meant to be done in Spring. Also I still would need to access the HttpServletRequest when creating the Autowired instance, how would I do that? – Tim Roes Mar 25 '15 at 08:23
  • I believe he's suggesting changing the scope of the *dependency*, not that of the controller. – chrylis -cautiouslyoptimistic- Mar 25 '15 at 08:31
  • @chrylis unfortunately you cannot autowire request scopes beans into an instance variable inside a non request scopes bean (in this case the controller). – Tim Roes Mar 25 '15 at 08:38
  • Do you have a citation for that? Spring does that all the time for particular beans (such as a persistence or security context). – chrylis -cautiouslyoptimistic- Mar 25 '15 at 08:48
  • @chrylis `java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.` when it tries to create the instance of the controller. – Tim Roes Mar 25 '15 at 08:50
  • Okay, you're talking specifically about injecting the `HttpServletRequest` into the dependency. – chrylis -cautiouslyoptimistic- Mar 25 '15 at 08:52
  • @chrylis No, I just tried to autowire a request scoped (but otherwise completely empty) bean to a `@RestController` and that is what happened. The stripped down example: http://pastebin.com/5pux7Cu9 – Tim Roes Mar 25 '15 at 09:01
0

I found a solution, that does what I was trying to do.

Just create the MyRequestFoo as a bean with scope "request" and you can access the current request via the RequestContextHolder:

@Component
@Scope("request")
public class MyRequestFoo {
   private final HttpServletRequest request;

   public MyRequestFoo() {
      request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
   }

   // do whatever you want with the request

}

And now i can just inject a new instance of this by its type into any request handler method:

@RequestMapping("/search")
public SomeReturnObject search(MyRequestFoo foo) {
   // ...
}

And Spring will automatically take care of instantiating a new instance.

It does not work to autowire MyRequestFoo as an instance variable, since you cannot autowire request scoped beans into non request scoped beans.

Tim Roes
  • 1,406
  • 1
  • 12
  • 22
0

Since you need to pass in request-specific information to your bean, I recommend instead injecting a builder bean into your controller and calling builder.build(request) from your method to create the new per-request instance.

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
  • 1
    I wanted to avoid having to inject HttpServletRequest into each request handler to then write the same builder line into each request handling method, since I need it for ALL handlers. Though it would work of course. – Tim Roes Mar 25 '15 at 08:56