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
Related
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.
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);
Can someone explain why Autowiring is not working in the following context ?
I have the following class that I would love to Autowire:
#Component
public class UserFormatter {
...
}
Now I want to Autowire the UserFormatter in this class:
If I declare the UserFormatter as static it works fine but why does it work only this way?
#Data
#Component
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class ScenarioInfo {
#Transient
private static UserFormatter userFormatter;
#Autowired
public void generateUserFormatter(UserFormatter userFormatter) {
this.userFormatter = userFormatter;
}
Why can't I just write
#Data
#Component
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class ScenarioInfo {
#Transient
#Autowire
private UserFormatter userFormatter;
And why do I have to use a Function in order to Autowire the userFormatter ?
I'd be happy if someone could help me.:)
It depends on how you are initializing your ScenarioInfo class. JB Nizet is right about the use of new keyword stopping all the subsequent hierarchical Autowiring. If you want to create the ScenarioInfo object straight from the main method where #Autowire is not possible, then annotate the calling class(which is App here) with #Configuration and use
Annotationconfigapplicationcontext ctx = new Annotationconfigapplicationcontext(App.class);
ScenarioInfo scenarioInfo = ctx.getBean(ScenarioInfo.class);
where the App is the class with the main method in it. You could also use BeanFactory interface and its implementations but ApplicationContext is more feature rich. Plus I have never used the BeanFactory` myself, so you'd have to ask someone else on how to configure it for your project.
I have a class that is annotated #Component that was then #Autowired into another class. However, I need to remove this #Component annotation and instead, create it with an #Bean annotated method in the class where its was previously autowired.
Where previously the classes looked like:
#Component
public class MyClass implements IMyClass
{
// Stuff
}
#Configuration
public class MyUsingClass
{
#Autowired
private IMyClass myClass;
private void methodUsingMyClass()
{
myClass.doStuff();
}
}
So now I have removed the #Component annotation and written a #Bean annotated method like this:
public class MyClass implements IMyClass
{
// Stuff
}
#Configuration
public class MyUsingClass
{
#Bean
public IMyClass getMyClass()
{
return new MyClass();
}
....
}
My question is around replacing the previous call of myClass.doStuff() to use the new bean. Do I now pass in a parameter of type MyClass to the private method:
private void methodUsingMyClass(final MyClass myClass)
{
myClass.doStuff();
}
... or do I call this method directly (doesn't seem the correct way to me):
private void methodUsingMyClass()
{
getMyClass().doStuff();
}
... or are neither of these correct?
I think you misunderstand the #Bean annotation. It can be used to create a Bean. So basically spring will scan all classes, will find your #Bean and create a Bean, not more. You can now use this bean, like if you would use one created with <bean></bean>. To actually use the bean you need to either get it from ApplicationContext or #Autowire it. Of course you can still use that function like any other function in your code, to create a new instance of that object, but that would contradict to what you want to achieve with beans
Using Annotations that solutions
public class MyClass implements IMyClass{
private OtherClassInjection otherClassInjection;
private OtherClassInjection2 otherClassInjection2;
MyClass(OtherClassInjection otherClassInjection, OtherClassInjection2 otherClassInjection2){
this.otherClassInjection=otherClassInjection;
this.otherClassInjection2=otherClassInjection2;
}
public void useObject(){
otherClassInjection.user();
}
}
#Bean(name = "myClass")
#Autowired
#Scope("prototype") //Define scope as needed
public MyClass getMyClass(#Qualifier("otherClassInjection") OtherClassInjection otherClassInjection,
OtherClassInjection2 otherClassInjection2) throws Exception
{
return new MyClass(otherClassInjection, otherClassInjection2);
}
that logical, it's work injection #Autowired when create a Bean if context are know that bean, that you will to want inject.
I'm use that way.
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);
}
}
}