Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a request scoped bean at runtime with spring

Tags:

java

spring

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?

like image 568
flash Avatar asked Mar 16 '16 09:03

flash


People also ask

Can we create a bean at runtime?

This can be done without restarting the application at runtime when Loading and Removing bean in Spring Application. If the client code needs to register objects which are not managed by Spring container, then we will need to work with an instance of BeanDefinition. Here, We have used the following dependencies.

How does request scope work in Spring?

Request scope – Spring creates an instance of the bean class for every HTTP request. The instance exists only for that specific HTTP request. Session scope – Spring creates an instance and keeps the instance in the server's memory for the full HTTP session.

Which scope creates a new bean instance is time when requested?

The prototype scope If the scope is set to prototype, the Spring IoC container creates a new bean instance of the object every time a request for that specific bean is made. As a rule, use the prototype scope for all state-full beans and the singleton scope for stateless beans.


2 Answers

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) 
like image 75
M. Deinum Avatar answered Oct 12 '22 13:10

M. Deinum


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) 
like image 43
isah Avatar answered Oct 12 '22 14:10

isah