Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create request scoped beans from a Java 8 Function

Based on this answer I try to configure a request scope bean using java.util.Function interface.

My Configuration looks like this:

@Configuration
public class RequestConfig {

    @Bean
    public Function<? extends BaseRequest, RequestWrapper<? extends BaseRequest, ? extends BaseResponse>> requestWrapperFactory() {
        return request -> requestWrapper(request);
    }

    @Bean
    @RequestScope
    public RequestWrapper<? extends BaseRequest, ? extends BaseResponse> requestWrapper(
            BaseRequest request) {
        RequestWrapper<?, ?> requestWrapper = new RequestWrapper<BaseRequest, BaseResponse>(request);
        return requestWrapper;
    }
}

And I try to use the bean like this:

@RestController
public class CheckRequestController {

    private final RequestService<CheckRequest, CheckResponse> checkRequestServiceImpl;

    @Autowired
    private Function<CheckRequest, RequestWrapper<CheckRequest, CheckResponse>> requestWrapperFactory;

    public CheckRequestController(
            RequestService<CheckRequest, CheckResponse> checkRequestServiceImpl) {
        super();
        this.checkRequestServiceImpl = checkRequestServiceImpl;
    }

    @PostMapping(value = "/check", consumes = { MediaType.TEXT_XML_VALUE,
            MediaType.MULTIPART_FORM_DATA_VALUE }, produces = MediaType.TEXT_XML_VALUE)
    public ResponseEntity<CheckResponse> checkRequest(
            @RequestBody(required = true) CheckRequest checkRequest) {

        RequestWrapper<CheckRequest, CheckResponse> requestWrapper = requestWrapperFactory
                .apply(checkRequest);
        checkRequestServiceImpl.getResponse(requestWrapper);

        return new ResponseEntity<CheckResponse>(requestWrapper.getResponse(),
                HttpStatus.OK);
    }
}

And here:

@RestController
public class CancelRequestController {

private final RequestService<CancelRequest, CancelResponse> cancelRequestServiceImpl;

@Autowired
private Function<CancelRequest, RequestWrapper<CancelRequest, CancelResponse>> requestWrapperFactory;

public CancelRequestController(
        RequestService<CancelRequest, CancelResponse> cancelRequestServiceImpl) {
    super();
    this.cancelRequestServiceImpl = cancelRequestServiceImpl;
}

@PostMapping(value = "/cancel", consumes = { MediaType.TEXT_XML_VALUE,
        MediaType.MULTIPART_FORM_DATA_VALUE }, produces = MediaType.TEXT_XML_VALUE)
public ResponseEntity<CancelResponse> CancelRequest(
        @RequestBody(required = true) CancelRequest cancelRequest) {
    RequestWrapper<CancelRequest, CancelResponse> requestWrapper = requestWrapperFactory
            .apply(cancelRequest);
    cancelRequestServiceImpl.getResponse(requestWrapper);
    return new ResponseEntity<CancelResponse>(requestWrapper.getResponse(),
            HttpStatus.OK);
}
}

But I get the exception that there is no bean of type Function defined.

  Field requestWrapperFactory in CheckRequestController required a bean of type 'java.util.Function' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'java.util.Function' in your configuration.

Is there a problem by using generic types? What do I wrong?

like image 568
Patrick Avatar asked Aug 01 '19 14:08

Patrick


People also ask

What is a request scoped bean?

A request-scoped bean is an object managed by Spring, for which the framework creates a new instance for every HTTP request. The app can use the instance only for the request that created it. Any new HTTP request (from the same or other clients) creates and uses a different instance of the same class (figure 2).

Which scope creates a new bean instance each 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.

When a bean has scope limited to only HTTP request that is called?

request. Scopes a single bean definition to the lifecycle of a single HTTP request; that is each and every HTTP request will have its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext . session.


Video Answer


1 Answers

The answer that you refer has a difference : it uses exactly the same generics type in the declared bean and the injected bean :

@Bean
public Function<String, Thing> thingFactory() {
    return name -> thing(name); // or this::thing
} 

and :

@Autowired
private Function<String, Thing> thingFactory;

Is there a problem by using generic types? What do I wrong?

Yes. You want to inject a bean with this signature :

Function<CheckRequest, RequestWrapper<CheckRequest, CheckResponse>> requestWrapperFactory;

But you declared a bean with this signature :

Function<? extends BaseRequest, RequestWrapper<? extends BaseRequest, ? extends BaseResponse>>

here :

@Bean
public Function<? extends BaseRequest, RequestWrapper<? extends BaseRequest, ? extends BaseResponse>> requestWrapperFactory() {
    return request -> requestWrapper(request);
}

The generics used in the bean declaration and the bean wired have to be the same to match in terms of dependency injections.

So just declare the same types in both sides.

so this means there is no way to configure a bean using generics? because I wanted to use the bean creation also for CancelRequest (updated answer). So I have to create a Bean for all types of BaseRequest..

For @RequestScope beans, in theory it should not create any issue to use generics because the bean is created at each request and not reused but I think that the generic features for @Bean doesn't make this difference and so consider the general case (singleton scope) where the perfect matching is necessary to avoid type safe and consistency issues. It could interest you.


After your edit :

I updated the first part to be consistent with your changes.

Now your requirement is declaring a function that returns to the client a prototype bean with a generic type specified by the client.
That is possible. But to make it neat, you should not use two beans : one for the factory (the singleton) and another to create the RequestWrapper object ( the prototype).
As the factory bean doesn't allow clients to specify the generic type, you will have to perform undesirable uncasts.
You should also replace @RequestScope by @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) because request scoped beans don't allow to be as much configurable as singleton and prototype beans in a configuration class.
For example, using parameters or wildcard don't work well.

So the idea is declaring a prototype bean which the generic type returned depends on the parameter and the target.
AboutRequestConfig, it would have better now to be named as RequestFactory as that is its role.

@Configuration
public class RequestFactory {

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public <T extends BaseRequest, U extends BaseResponse> RequestWrapper<T, U> requestWrapper(
            T request) {
        RequestWrapper<T, U> requestWrapper = new RequestWrapper<>(request);
        return requestWrapper;
    }

}

In the controller inject the @Configuration bean requestFactory :

private RequestFactory requestFactory; // Change

public CheckRequestController(
        RequestService<CheckRequest, CheckResponse> checkRequestServiceImpl,
        RequestConfig requestConfig) {
    this.checkRequestServiceImpl = checkRequestServiceImpl;
    this.requestFactory = requestFactory; // Change
}

And now you can inject a prototype bean with the desired RequestWrapper whenever you need :

@PostMapping(value = "/cancel", consumes = { MediaType.TEXT_XML_VALUE,
        MediaType.MULTIPART_FORM_DATA_VALUE }, produces = MediaType.TEXT_XML_VALUE)
public ResponseEntity<CancelResponse> CancelRequest(
        @RequestBody(required = true) CancelRequest cancelRequest) {
    RequestWrapper<CheckRequest, CheckResponse> requestWrapper = 
               requestFactory.requestWrapper(cancelRequest);
     //...
    return new ResponseEntity<CancelResponse>(requestWrapper.getResponse(),
            HttpStatus.OK);
}

Tested that now, it looks working.

like image 121
davidxxx Avatar answered Nov 15 '22 02:11

davidxxx