I have an #Autowired in one of my Serializers which extends StdSerializer
public class RefSerializer extends StdSerializer<LabeledElement> {
#Autowired
I18n i18n;
public RefSerializer() {
this(null);
}
public RefSerializer(Class<LabeledElement> t) {
super(t);
}
#Override
public void serialize(LabeledElement element, JsonGenerator generator, SerializerProvider provider) throws IOException {
String identifier = null;
String label = LabelUtils.labelPlanElement(this.i18n, planElement, "ref");
generator.writeObject(ReferenceElement.of(element.getId(), label, identifier));
}
}
and is used by #JsonSerialize inside the model class.
#JsonSerialize(contentUsing = RefSerializer.class)
#JsonDeserialize(contentUsing = PlanElementDeserializer.class)
#OneToMany(fetch = FetchType.EAGER)
private List<PlanElement> planElements;
If the Serializer is called inside my #RestComponent annotated endpoints the #Autowired is resolved and everything works fine for incoming and returned models.
Now I want to send the model actively via RestTemplate#exchange, but now the #Autowired inside the Serializer is null.
restTemplate.exchange(endpointUrl, httpMethod, new HttpEntity<>(planElement, authHeader), Map.class, authParameters);
Is there a way to get the autowiring to work for outgoing REST calls with RestTemplate?
Using Spring-boot 2.6.3, Java 17
If the RestTemplate is provided by bean inside a #Configuration file like so:
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
and then autowired via #Autowired inside the service which uses the RestTemplate and not instantiated by "new RestTemplate()" the autowired services inside the custom serializer are also available for the RestTemplates REST calls.
Related
I'm trying to inject my config into my custom StdDeserializer. The problem however is, even though I marked the class as #Component, the value of the field is never injected. The injection works without any problems in the other places of the application.
Therefore, I've come to the conclusion that the problem is with the way the deserializer is working, since it doesn't get instantiated by us but rather like the example down here:
ClassToDeserialize myClass = new ObjectMapper().readValue(mockJson, ClassToDeserialize.class);
As you can see there is no explicit usage of my custom deserializer ClassToDeserializeDeserializer hence it detects the classes with the custom deserializer with the #JsonDeserialize(using = ClassToDeserialize.class) annotation.
Class that should be deserialized
#Data
#AllArgsConstructor
#SuperBuilder
#JsonDeserialize(using = MyClassDeserializer.class)
public class MyClass{
private final String field1;
private final String field2;
}
Config class that should be injected
#Configuration
#ConfigurationProperties(prefix = "myconfig")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class MyConfig {
private final String confField1;
private final String confField2;
}
MyClass's custom deserializer:
#Component
public class MyClassDeserializer extends StdDeserializer<MyClass> {
#Autowired
MyConfig myConfig;
public MyClassDeserializer () {
this(null);
}
public MyClassDeserializer (Class<?> vc) {
super(vc);
}
#Override
public MyClassDeserializer deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
//Deserializing code that basically tries to read from myConfig
myConfig.getConfField1(); //Hello NullPointerException my old friend
}
}
Usage of the deserializer
MyClass myClass = new ObjectMapper().readValue(mockJson, MyClass.class);
Reason why it doesn't work:
Jackson doesn't know anything about Spring Boot stuff, so when you readValue(..) Jackson sees #JsonDeserialize annotation with deserializer class and creates new instance of the deserializer (it doesn't pick up the bean, but rather just new MyClassDeserializer(..)), that is why you never see MyConfig being injected.
If you want to make it work, you need to somehow register this deserializer through Spring Boot, for example, like this: How to provide a custom deserializer with Jackson and Spring Boot
I want to autowire a spring dependency into a jackson deserialization converter. E.g.,
import com.fasterxml.jackson.databind.util.StdConverter;
#Component
public class LookupConverter extends StdConverter<T, T> {
#Autowired
private Repository<T> repo;
#Override
public IsoCountry convert(T value) {
repo.findById(value.getId()).orElse(value);
}
}
I have tried using: SpringBeanAutowiringSupport e.g.,
public LookupConverter() {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
but get the following message
Current WebApplicationContext is not available for processing of LookupConverter: Make sure this class gets constructed in a Spring web application. Proceeding without injection.
I have tried injecting a SpringHandlerInstantiator into the the ObjectMapper ala this and this
#Bean
public HandlerInstantiator handlerInstantiator(ApplicationContext applicationContext) {
return new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory());
}
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder(HandlerInstantiator handlerInstantiator) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.handlerInstantiator(handlerInstantiator);
return builder;
}
This also does not work, seemingly because the SpringHandlerInstantiator is not being used and my custom Converter is not being instantiated by spring.
Any pointers to how this can be accomplished using Spring Boot 2.1.0 would be much appreciated.
One way to get around this issue would be to create #Service that provides repository or repositories in a static way, for example:
#Service
public class RepositoryService {
#Resource
private ExampleEntityRepository repo;
#Getter
private static final Map<Class<?>, Repository<?, ?>> repos = new HashMap<>();
#PostConstruct
private void postConstruct() {
repos.put(ExampleEntity.class, repo);
}
}
Then instead of injecting repo to your converter you would do something like:
private Repository<ExampleEntity, Long> repo = RepositoryService.getRepos()
.get(ExampleEntity.class);
I'm using RESTEasy 3 and Spring 4 and I'm trying to inject #Autowired an service bean into my interceptor as follow below:
But running this code it's returning Null Pointer Exception when access my access service:
#Provider
#MyAnnotationToIntercept
public class MyInterceptor implements ContainerRequestFilter {
private MyAccessService accessService;
#Autowired
public MyInterceptor(MyAccessService accessService) {
this.accessService = accessService;
}
public MyInterceptor() {
}
#Override
public void filter(ContainerRequestContext requestContext) {
// DO SOME STUFF Using accessService
}
}
#Component
public class MyAccessService {
private MyDep1 dep1;
#Autowired
public MyAccessService(Mydep1 dep1) {
this.dep1= dep1;
}
}
Is there any way to achieve this? It's really possible?
You will need to use WebApplicationContextUtils's method to get a bean inside filter which is not managed by spring. Here is the example
MyAccessService myAccessService = (MyAccessService) WebApplicationContextUtils.getRequiredWebApplicationContext(httpServletRequest .getServletContext()).getBean(MyAccessService.class);
And to get HttpServletRequest instance you can use #context injection
#Context
private HttpServletRequest httpServletRequest ;
Looks like you have placed #Autowired annotation at the wrong place. It should be above the declaration of accessService. And depending on how you have configured application context, you may/may not need a setter method for accessService instance variable.
EDIT: This question is specifically pertaining to the #RestClientTest annotation introduced in spring-boot 1.4.0 which is intended to replace the factory method.
Problem:
According to the documentation the #RestClientTest should correctly configure a MockRestServiceServer to use when testing a REST client. However when running a test I am getting an IllegalStateException saying the MockServerRestTemplateCustomizer has not been bound to a RestTemplate.
Its worth noting that I'm using Gson for deserialization and not Jackson, hence the exclude.
Does anyone know how to correctly use this new annotation? I haven't found any examples that require more configuration then when I have already.
Configuration:
#SpringBootConfiguration
#ComponentScan
#EnableAutoConfiguration(exclude = {JacksonAutoConfiguration.class})
public class ClientConfiguration {
...
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
}
Client:
#Service
public class ComponentsClientImpl implements ComponentsClient {
private RestTemplate restTemplate;
#Autowired
public ComponentsClientImpl(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
public ResponseDTO getComponentDetails(RequestDTO requestDTO) {
HttpEntity<RequestDTO> entity = new HttpEntity<>(requestDTO);
ResponseEntity<ResponseDTO> response =
restTemplate.postForEntity("/api", entity, ResponseDTO.class);
return response.getBody();
}
}
Test
#RunWith(SpringRunner.class)
#RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {
#Autowired
private ComponentsClient client;
#Autowired
private MockRestServiceServer server;
#Test
public void getComponentDetailsWhenResultIsSuccessShouldReturnComponentDetails() throws Exception {
server.expect(requestTo("/api"))
.andRespond(withSuccess(getResponseJson(), APPLICATION_JSON));
ResponseDTO response = client.getComponentDetails(requestDto);
ResponseDTO expected = responseFromJson(getResponseJson());
assertThat(response, is(expectedResponse));
}
}
And the Exception:
java.lang.IllegalStateException: Unable to use auto-configured MockRestServiceServer since MockServerRestTemplateCustomizer has not been bound to a RestTemplate
Answer:
As per the answer below there is no need to declare a RestTemplateBuilder bean into the context as it is already provided by the spring-boot auto-configuration.
If the project is a spring-boot application (it has #SpringBootApplication annotation) this will work as intended. In the above case however the project was a client-library and thus had no main application.
In order to ensure the RestTemplateBuilder was injected correctly in the main application context (the bean having been removed) the component scan needs a CUSTOM filter (the one used by #SpringBootApplication)
#ComponentScan(excludeFilters = {
#ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)
})
The MockRestServiceServer instance should be constructed from the static factory, using a RestTemplate. See this article for a detailed description of the testing process.
In your example, you can do:
#RunWith(SpringRunner.class)
#RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {
#Autowired
private ComponentsClient client;
#Autowired
private RestTemplate template;
private MockRestServiceServer server;
#Before
public void setUp() {
server= MockRestServiceServer.createServer(restTemplate);
}
/*Do your test*/
}
You have RestTemplateBuilder at two places. At ClientConfiguration class and at ComponentsClientImpl class. Spring boot 1.4.0 auto-configure a RestTemplateBuilder which can be used to create RestTemplate instances when needed. Remove below code from ClientConfiguration class and run your test.
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
I am trying to enable Jackson's "fail on unknown properties" feature for all endpoints in my service. I've added Jackson2ObjectMapperBuilder:
#Configuration
public class JacksonConfig{
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder(){
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.failOnUnknownProperties(true);
return builder;
}
}
When I use injected ObjectMapper, validation works as supposed, it throws HttpMessageNotReadableException while passing invalid json:
public class Person{
#NotNull
private String name;
#NotNull
private String surname;
/*getters and setters*/
}
#Autowired
private ObjectMapper objectMapper;
Person person = objectMapper.readValue("{ "\unknown\":\"field\", \"name\":\"John\", \"surname\":\"Smith\" }", Person.class);
However when i pass same json straight to controller validation does not occur and body of method is invoked:
#RequestMapping(value = "/something", method = RequestMethod.POST)
public void something(#Valid #RequestBody Person person){...}
Firstly I thought that MessageConverter does not use custom ObjectMapper so I checked it:
#Autowired
private MappingJackson2HttpMessageConverter converter;
converter.getObjectMapper().getDeserializationConfig().hasDeserializationFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES.getMask()));
//returns true
It's even more weird because when i use repositories in other part of service validation works in POST method but does not work in PUT method.
#RepositoryRestResource(collectionResourceRel = "...", path = "...")
public interface CarRepository extends CrudRepository<Car, Long> {
#Override
#PreAuthorize("hasRole('ROLE_ADMIN') || hasPermission(#car, T(...).CREATE_OR_MODIFY")
Car save(#Param("car") Car car);
/*other methods*/
}
Is there a simple way to force controllers and repositories to check if passed json does not contain unknown properties?
When working with spring data rest there are multiple ObjectMappers registered in the application context e.g. one for application/hal+json and one for application/json. It looks like spring data rest is not using the primary object mapper that you configured.
If you want to configure the spring data rest object mapper you can do this using a RepositoryRestConfigurerAdapter - see http://docs.spring.io/spring-data/rest/docs/current/reference/html/#getting-started.configuration
public class DefaultRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
#Autowired(required = false)
private Jackson2ObjectMapperBuilder objectMapperBuilder;
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
if (this.objectMapperBuilder != null) {
this.objectMapperBuilder.configure(objectMapper);
}
}
}