flash
flash

Reputation: 6810

How to create a request scoped bean at runtime with 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?

Upvotes: 17

Views: 35764

Answers (3)

M. Deinum
M. Deinum

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

isah
isah

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

Jens
Jens

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

Related Questions