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?
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
Make a POJO class, in your case MyRequestFoo
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) {
// ...
}
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!
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.
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.
Related
I need to build an object before it even reaches the controller and one way I found to do that is by using HandlerMethodArgumentResolver.
Basically, I have a pojo which gets mapped to the request parameters, but I want to set some other fields in that pojo before it reaches the controller.
POJO: UserParams.java
#AllArgsConstructor
public class UserParams {
private String firstName;
private String lastName;
private String sessionId;
}
Let's say my request comes in as localhost:8080/user?firstName=John&lastName=Doe
So, in my resolver I want to bind the UserParams object using the request params from the above request and populate sessionId field and return the bound object with additional value.
#Component
public class UserParamsResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(UserParams.class);
}
#Override
public Object resolveArgument(final MethodParameter parameter,
final ModelAndViewContainer mavContainer,
final NativeWebRequest webRequest,
final WebDataBinderFactory binderFactory) {
WebDataBinder binder = new WebDataBinder(BeanUtils.instantiateClass(parameter.getParameterType()));
ServletRequestParameterPropertyValues values = new ServletRequestParameterPropertyValues(((ServletWebRequest) webRequest).getRequest());
binder.bind(values);
BindingResult result = binder.getBindingResult();
// UserParams userParams = how to get this object?
// userParams.setSessionId(userParams.getLastName + Math.random())
return userParams;
}
So, when the request eventually reaches the controller, I've the userParams with sessionId in it.
I tried looking at many places (programcreek.com had lot of examples for WebDataBinder) and tried to find out how Spring binds the objects with request params before coming to the Controller, but I had no luck.
One solution is to use spring AOP in order to process all #Controller methods having UserParams as a parameter. Then you have to get access to WebRequest in order to get sessionId and this is a little bit trickier; you'll have to create a #Bean #Scope("request") (e.g. name it WebRequestAccessor) which to contain an #Autowired field of type WebRequest. Autowire that WebRequestAccessor bean into your #Aspect in order to use its WebRequest field which then gives you access to sessionId. Set the sessionId on the UserParams parameter then let the advised method continue its work.
Take a look here about how to write & use an #Aspect.
UPDATE
You could use JSESSIONID instead of HttpSession.getId(). Just annotate private String sessionId with #CookieValue("JSESSIONID").
UPDATE 2
I'm pretty sure you should use ServletModelAttributeMethodProcessor (or ModelAttributeMethodProcessor) instead of HandlerMethodArgumentResolver.
Similar to Spring MVC abstract class binding, I have handler methods with an #ModelAttribute parameter which is an interface. Based on some of the properties of the incoming request, I want to programmatically select an implementation to instantiate and then bind it to the request body using whatever Spring has down in its guts for marshalling into objects.
Is this possible and what Spring components would I need to make use of to achieve it?
You can implement a HandlerMethodArgumentResolver to resolve your custom method parameters. Suppose your interface is CustomInterface, then:
public class CustomInterfaceResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(CustomInterface.class);
}
#Override
public CustomInterface resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// instantiate CustomInterface impls based on request parameters
// and return it of course
}
}
supportsParameter method ensures that this resolver only resolves arguemnts of type CustomInterface and resolveArgument method, does the actual mechanics of resolving the argument.
After implementing this class, add it to your list of current argument resolvers. For example, in java config:
public class WebConfig extends WebMvcAutoConfigurationAdapter {
...
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(customArgumentResolver);
}
...
}
After that, any time you add a CustomInterface to your controller method argument, CustomInterfaceResolver#resolveArgument would be called to resolve the argument:
#RestController
#RequestMapping("/somewhere")
public class CustomInterfaceController {
#RequestMapping
public ResponseEntity doSomething(CustomInterface ci, ...) {
....
}
}
I have annotation defined as below
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface OTPFlow {
}
And class A defined as below
public abstract class A {
#OTPFlow
public ModelAndView doSomething() {
//do something and return ModelAndView
}
}
Class B is a controller defined as below
#Controller
#RequestMapping(value = {"/someurl"})
public class B extends A {
#RequestMapping(value = {"/get"}, method = {RequestMethod.POST, RequestMethod.GET})
public ModelAndView get(HttpServletRequest request, HttpServletResponse response) {
return doSomething();
}
}
Aspect is defined as
#Component
#Aspect
public class OTPAspect {
private static final Logger logger = LoggerFactory.getLogger(OTPAspect.class);
#Pointcut("#annotation(OTPFlow)")
public void OTPFlow() {}
#Around("OTPFlow()")
public Object checkOTP(ProceedingJoinPoint joinPoint) {
try {
logger.info("Inside Aspect");
return joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
The problem is when i access "/someurl/get" url, the aspect does not execute. But when i annotate "get" method of class B, aspect executes.
So basically, annotated methods of superclass does not invoke Aspect.
What is the issue? Is there any other way to achieve this? Any help would be appreciated. Thanks
What happens when spring applies the aspect, is that spring creates proxies that wrap your controller instance to intercept calls and add the aspect behaviour before calling the instance method. This has the effect, that any call to "this" from within your controller instance is directly invoked on that instance and will not be intercepted by the wrapping proxy. Therefore, the "get" method is called on the proxy class, which in turn will call the "get" method of the controller instance, and when the latter tries to call the "doSomething" method it will not pass through the proxied "doSomething", but through the internal one.
The way to handle this situation is to apply aspects directly to the methods of your class that will be externally called, in your case directly on the "get" method instead of the "doSomething"
I want to offer an alternative to what M. Deinum and Marios have said correctly: Use AspectJ instead of Spring AOP. AspectJ does not rely on proxies, is faster, more powerful and integrates nicely with Spring as described in Spring manual, Section 9.8, Using AspectJ with Spring applications. With AspectJ what you want to do works out of the box.
As of Spring MVC 3, AbstractCommandController is deprecated so you can no longer specify the command class in setCommandClass(). Instead you hard-code the command class in the parameter list of a request handler. For example,
#RequestMapping(method = RequestMethod.POST)
public void show(HttpServletRequest request, #ModelAttribute("employee") Employee employee)
My problem is that I'm developing a generic page that allows the user to edit a generic bean, so the command class isn't known until the run-time. If the variable beanClass holds the command class, with AbstractCommandController, you would simply do the following,
setCommandClass(beanClass)
Since I can't declare the command object as a method parameter, is there any way to have Spring bind request parameters to a generic bean in the body of the request handler?
Instantiation of the command object is the only place where Spring needs to know a command class. However, you can override it with #ModelAttribute-annotated method:
#RequestMapping(method = RequestMethod.POST)
public void show(HttpServletRequest request,
#ModelAttribute("objectToShow") Object objectToShow)
{
...
}
#ModelAttribute("objectToShow")
public Object createCommandObject() {
return getCommandClass().newInstance();
}
By the way, Spring also works fine with the real generics:
public abstract class GenericController<T> {
#RequestMapping("/edit")
public ModelAndView edit(#ModelAttribute("t") T t) { ... }
}
#Controller #RequestMapping("/foo")
public class FooController extends GenericController<Foo> { ... }
I have a #Controller with #Autowired fields and handler methods that I want to annotate with custom annotations.
For example,
#Controller
public class MyController{
#Autowired
public MyDao myDao;
#RequestMapping("/home")
#OnlyIfXYZ
public String onlyForXYZ() {
// do something
return "xyz";
}
}
Where #OnlyIfXYZ is an example of a custom annotation. I was thinking I would intercept the Controller bean creation, pass my own CGLIB proxy on which Spring can then set properties, like the autowired field.
I tried using a InstantiationAwareBeanPostProcessor but that solution doesn't work great because postProcessBeforeInstantiation() short-circuits the rest of the process. I tried with postProcessAfterInitialization(), like below
public class MyProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// Here the bean autowired fields are already set
return bean;
}
#Override
public Object postProcessAfterInitialization(Object aBean, String aBeanName) throws BeansException {
Class<?> clazz = aBean.getClass();
// only for Controllers, possibly only those with my custom annotation on them
if (!clazz.isAnnotationPresent(Controller.class))
return aBean;
Object proxy = Enhancer.create(clazz, new MyMethodInterceptor());
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
// get the field and copy it over to the proxy
Object objectToCopy = field.get(aBean);
field.set(proxy, objectToCopy);
} catch (IllegalArgumentException | IllegalAccessException e) {
return aBean;
}
}
return proxy;
}
}
This solution uses reflection to copy over all the fields of the target bean to the proxy bean (kind of hacky for my taste). But I don't have access to the HttpServletRequest and HttpServletResponse objects if those aren't arguments in the method I'm intercepting.
Is there another callback I can inject into Spring bean creation logic to inject my own Proxy Controller before Spring populates its properties? I need to be able to access the HttpServletRequest and HttpServletResponse objects regardless of if the Controller handler method has it in its definition, ie. as arguments.
N.B The #Autowired field is also a proxy, it is annotated with #Transactional so Spring proxies it up.
EDIT: The AOP solution works nicely for intercepting the method invocation, but I can't find a way to access the HttpServletRequest and HttpServletResponse objects, if they aren't already method arguments.
I'm probably going to end up using HandlerInterceptorAdapter, but I was hoping I can do it with OOP so as to not add the overhead to methods that don't need it.
Take a look at Spring AOP. It has exactly the facilities you are after. For your example, you could do something like this:
#Aspect
#Component
public class MyAspect {
#Around("#annotation(path.to.your.annotation.OnlyIfXYZ)")
public Object onlyIfXyz(final ProceedingJoinPoint pjp) throws Exception {
//do some stuff before invoking methods annotated with #OnlyIfXYZ
final Object returnValue = pjp.proceed();
//do some stuff after invoking methods annotated with #OnlyIfXYZ
return returnValue;
}
}
It is worth noting that Spring will only apply the proxy to classes that are a part of its application context. (which it appears is the case in your example)
You can also use Spring AOP to bind parameters to your aspect method. This can be done in various ways, but the one you are after is probably args(paramName).
#Aspect
#Component
public class MyAspect2 {
#Around("#annotation(path.to.your.annotation.OnlyIfXYZ) && " +
"args(..,request,..)")
public Object onlyIfXyzAndHasHttpServletRequest(final ProceedingJoinPoint pjp,
final HttpServletRequest request) throws Exception {
//do some stuff before invoking methods annotated with #OnlyIfXYZ
//do something special with your HttpServletRequest
final Object returnValue = pjp.proceed();
//do some stuff after invoking methods annotated with #OnlyIfXYZ
//do more special things with your HttpServletRequest
return returnValue;
}
}
This aspect should do a part of what you are after. It will proxy methods annotated with #OnlyIfXYZ that ALSO take in a HttpServletRequest as a parameter. Further, it will bind this HttpServletRequest into the Aspect method as a passed in parameter.
I understand that you are after potentially both HttpServletRequest and HttpServletResponse, so you should be able to modify the args expression to take in both request and response.
Taking into account your comment under the question all you need is HandlerInterceptor.
http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/web/servlet/HandlerInterceptor.html
You need to implement that interface and add it to your configuration, for example:
<mvc:interceptors>
<bean id="customInterceptor" class="com.example.interceptors.CustomInterceptor"/>
</mvc:interceptors>
This interface provides method preHanlde, which has request, response and HandlerMethod. To check if the method is annotated just try this:
HandlerMethod method = (HandlerMethod) handler;
OnlyIfXYZ customAnnotation = method.getMethodAnnotation(OnlyIfXYZ.class);
I think that not, but I supose that you could autowire the proxy after creating it.
public class MyProcessor extends InstantiationAwareBeanPostProcessorAdapter
implements BeanFactoryAware {
private AutowireCapableBeanFactory beanFactory;
#Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
// This is where I thought I would do it, but it then skips setting fields alltogether
if (beanClass.isAnnotationPresent(Controller.class)) {
Object proxy = Enhancer.create(beanClass, new MyInterceptor());
// autowire
beanFactory.autowireBean(proxy);
return proxy;
}
return null;
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (AutowireCapableBeanFactory) beanFactory;
}
}
Other alternative is to create a Spring AOP Proxy (using ProxyFactory) in postProcessAfterInitialization method. For this way AbstractAutoProxyCreator could be useful. See BeanNameAutoProxyCreator as sample. But imho, an annotation pointcut (Nicholas answer) do the same and is simpler.
InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation will short-circuit the bean creation approach. The only processing applied is postProcessAfterInitialization. Which means that, autowiring won't happen because AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues will never be called. Therefore, you should manually inject or autowire the properties of the proxied beans in postProcessAfterInitialization method.
Question: Does moving the proxying logic in postProcessAfterInitialization method have an impact to your business requirements? If none, I suggest you do the proxying there.
FYI: If you are not building an API, do the annotation approach as suggested by #nicholas.hauschild.