Reputation: 111
What I need to achieve is the same behaviour as @RequestParam("foo")
but in the constructor of a REQUEST
scoped bean, where other constructor arguments would be autowired normally.
I would like to have a request parameter injected in the constructor as if it were a normal bean, but obviously without actually been a spring bean.
Example:
@Component
@RequestScope
public class RequestBean {
private final String filter;
private final UserRepository userRepository;
public RequestBean(@Param("filter") String filter, UserRepository userRepository) {
this.filter = filter;
this.userRepository = userRepository;
}
...
}
I tried using a BeanFactoryPostProcessor but the value I need to inject have to be obtained at request time, not at bean definition time.
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Stream.of(beanFactory.getBeanDefinitionNames())
.map(name -> beanFactory.getBeanDefinition(name))
.filter(def -> "request".equals(def.getScope()))
.forEach(beanDefinition -> {
try {
System.out.println(beanDefinition);
for (Constructor<?> constructor : Class.forName(beanDefinition.getBeanClassName()).getConstructors()) {
for (Parameter parameter : constructor.getParameters()) {
Param param = parameter.getAnnotation(Param.class);
if(param != null) {
String paramName = param.value(); // filter
Object requestParameterValue = "request.getParameter('" + paramName + "')"; // bean creation time evaluated argument
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
new ConstructorArgumentValues.ValueHolder(requestParameterValue, parameter.getType().getName(), parameter.getName())
);
}
}
}
} catch (Exception ex) {
// whatever
}
});
}
}
For a request to /index?filter=blue
I would expect "blue" and userRepository been autowired.
If I try with fixed values, it works perfectly, the value and the other bean are autowired normally.
I cannot use BeanPostProcessor
because the values have to be acquired before instantiation (contructor injection)
Upvotes: 0
Views: 876
Reputation: 111
Finally, I found out a way to achieve what I want.
I defined a REQUEST
scoped to ease the process of getting and converting a request parameter.
This bean will be called at creation time thanks to an expression (SprEL) registered in the BeanFactoryPostProcessor.
The expression will be evaluated and the returned value injected in the annotated parameter.
This adds the dynamic expression to the constructor parameter:
String name = param.value();
TypedStringValue dynamicValue = new TypedStringValue("#{@myBean.getParameter('"+name+"')}");
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(index, dynamicValue, constructorParameter.getType().getName());
And this is the bean called by the expression before the annotated bean instantiation:
@Component
@RequestScope
class MyBean {
final HttpServletRequest request;
public MyBean(HttpServletRequest request) {
this.request = request;
}
public String getParameter(String name){
// do something
return request.getParameter(name);
}
}
Upvotes: 1