Create request scoped beans from a Java 8 Function

Patrick :

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?

davidxxx :

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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=124445&siteId=1