Reputation: 6810
I have a spring application and want to create a bean at runtime per request to inject it into another class, just like @Producer
for CDI.
My bean is just a simple POJO:
public class UserDetails {
private String name;
// getter / setter ...
public UserDetails(String name) {
this.name = name;
}
}
My producer class looks like this:
@Configuration
public class UserFactory {
@Bean
@Scope("request")
public UserDetails createUserDetails() {
// this method should be called on every request
String name = SecurityContextHolder.getContext()
.getAuthentication().getPrincipal(); // get some user details, just an example (I am aware of Principal)
// construct a complex user details object here
return new UserDetails(name)
}
}
And this is the class where the UserDetails
instance should be injected:
@RestController
@RequestMapping(value = "/api/something")
public class MyResource {
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public List<String> getSomething(UserDetails userDetails) {
// the userdetails should be injected here per request, some annotations missing?
// do something
}
}
The problem is that Spring complains at runtime about no default constructor (of course).
Failed to instantiate [UserDetails]: No default constructor found
But this is intended and I want to call my own factory to let it handle the Instantiation.
How can I achieve this? Why is UserFactory
never called?
Upvotes: 17
Views: 35764
Reputation: 124441
Basically you aren't using your scoped proxy. You cannot inject a scoped proxy into a method, you have to inject it into your controller.
public List<String> getSomething(UserDetails userDetails) { ... }
This will lead to spring trying to create a new instance of UserDetails
through reflection, it will not inject your scoped bean. Hence it complains about the fact you need a default no-args constructor.
Instead what you should do is wire the dependency into your controller instead of the controller method.
@RestController
@RequestMapping(value = "/api/something")
public class MyResource {
@Autowired
private UserDetails userDetails;
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public List<String> getSomething() {
// the userdetails should be injected here per request, some annotations missing?
// do something
}
}
The idea is that the UserDetails
is a scoped proxy and when used will either use the already present object or create a new one based on the @Bean
method.
Additonally, the @Scope
annotation in the UserFactory
has to be modified as follows in order to work:
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
Upvotes: 21
Reputation: 5341
You're trying to inject a request scoped bean on a singleton bean.
You need to use a proxy for the UserDetails
@Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
Upvotes: 4
Reputation: 69439
You need a no arguments constructor in class UserDetails
.
public class UserDetails {
private String name;
// getter / setter ...
public UserDetails() {
}
public UserDetails(String name) {
this.name = name;
}
}
Upvotes: 0