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?
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).
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With