I have a microservice application which has a REST controller and uses an ObjectMapper - trip-definition-service.
#SpringBootApplication
public class TripDefintionService extends TripCommonApplication {
public static void main(String[] args) {
SpringApplication.run(args, TripDefinitionService.class);
}
Now I want another microservice application to create a bean for that same ObjectMapper - trip-common-service.
#SpringBootApplication
public abstract class TripCommonApplication { }
As of now I have build trip-common-service and have kept a jar as a dependency on trip-definition-service.
Now I have a configuration created on trip-common for ObjectMapper as follows -
#Configuration
#Qualifier("standardObjectMapper")
public ObjectMapper getMapper() {
return new ObjectMapper();
}
And I am using a the ObjectMapper which was created in trip-common in my trip-definition-service.
#Service
public class Service {
#Autowired
#Qualifier("standardObjectMapper")
private ObjectMapper objectMapper;
public void doSomething() {
objectMapper.readValue(......);
}
Now when I start my trip-definition-service, it says its not able to find an ObjectMapper bean of type standardObjectMapper.
Need some help/suggestions on this and how to get this done.
Related
Springboot and Axon: Basically I am unit testing an aggregate that uses three different ObjectMapper instances, each with a different configuration. These are defined in config class :
#Configuration
public class JacksonConfiguration {
#Bean(name="flatMapper")
#Primary
public ObjectMapper flatMapper(){
return new ObjectMapper();
}
#Bean(name="unknownPropertiesMapper")
public ObjectMapper unknownPropertiesMapper(){
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
#Bean(name="nullPropertiesMapper")
public ObjectMapper nullPropertiesMapper(){
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
}
They are injected and used in my aggregate as follow:
#Aggregate
#Data
#Component
public class MyAggregate {
#Autowired
#Qualifier("flatMapper")
private ObjectMapper flatMapper;
#Autowired
#Qualifier("unknownPropertiesMapper")
private ObjectMapper unknownPropertiesMapper;
#Autowired
#Qualifier("nullPropertiesMapper")
private ObjectMapper nullPropertiesMapper;
#AggregateIdentifier
private String id;
//Methods and Handlers: a method is using "unknownPropertiesMapper" is "changedKeySet"
when I run SpringBootApplication everything is properly instanciated and working as expected, but when testing I get NullPointerException over thier instances:
#ContextConfiguration(classes = JacksonConfiguration.class)
#SpringBootTest(classes = OccurrenceAggregate.class)
#ComponentScan(basePackageClasses = MyAggregate.class)
public class AggregateTest {
private FixtureConfiguration<MyAggregate> fixture;
#BeforeEach
public void setUp() {
fixture = new AggregateTestFixture<>(MyAggregate.class);
}
#Test
public void myTest() {
fixture.givenNoPriorActivity()....
}
test console:
org.axonframework.test.AxonAssertionError: The command handler threw an unexpected exception
Expected <ANYTHING> but got <exception of type [NullPointerException]>. Stack trace follows:
java.lang.NullPointerException
at com.example.business.aggregates.MyAggregate.changedKeySet(MyAggregate.java:185)
changedKeySet() is throwing NPE because its using unknownPropertiesMapper and it is value is null.
as I mentionned it works fine when I run the Main class but not in tests (Junit5).
The Aggregate is not set up correctly. The correct way to inject a Spring Bean into the Aggregate is to add it to the CommandHandler method.
#CommandHandler
public void handle(ACommand cmd, ObjectMapper flatMapper) {
In the test fixture you can inject it this way:
fixture.registerInjectableResource(flatMapper);
I faced with a problem of beans configuration in spring boot. In other words:
I have 3 microservices (utils, orders, admin). Utils for utility classes, orders for book orders, admin for administrate something. Orders and Admin have as a dependency Utils module. In Utils I have class that used in Orders, but not in the Admin module, but the Admin module has dependency on Utils because it used other utility classes. Back to orders :) Orders used class from Utils for example Payment that has Autowired RestTemplate and RestTemplate need Configuration. Some code below for understanding ...
#Import({
RestTemplateConfig.class})
#SpringBootApplication(scanBasePackages = "com.test")
public class OrdersApp {
public static void main(String[] args) {
SpringApplication.run(OrdersApp.class, args);
}
}
#Import({
RestTemplateConfig.class})
#SpringBootApplication(scanBasePackages = "com.test")
public class AdminApp {
public static void main(String[] args) {
SpringApplication.run(OrdersApp.class, args);
}
}
public class Payment{
private final RestTemplate restTemplate;
}
public Payment(
RestTemplate restTemplate
) {
this.restTemplate = restTemplate;
}
#EnableConfigurationProperties({RestTemplateConfigProperties.class})
public class RestTemplateConfig {
#Bean("restTemplate")
public RestTemplate restTemplate(RestTemplateBuilder builder, RestTemplateConfigProperties configProperties) {
return builder
.setConnectTimeout(Duration.ofSeconds(configProperties.getConnectTimeoutSec()))
.setReadTimeout(Duration.ofSeconds(configProperties.getReadTimeoutSec()))
.build();
}
}
BUT
In AdminApp I don't use Payment class that has autowired rest template that need configuration and I don't need to Import RestTemplateConfig.class for AdminApp, but AdminApp has dependency in pom.xml on Utils that has Payment that has restTemplate that need configuratuion (LOL)
How I cant improve confirations and bean init for this situation
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 have a Spring Boot web app with several #RestController classes.
I like the default json format returned by my REST controllers.
For use in my DAO beans (which do json serialization and deserialization ), I have created a custom ObjectMapper:
#Configuration
public class Config{
#Bean
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
And in each of my DAO classes I autowire my custom ObjectMapper:
#Repository
#Transactional
public class MyDaoImpl implements MyDao {
#Autowired
ObjectMapper objectMapper
//Dao implementation...
}
This all works fine. The problem is that my custom ObjectMapper gets automatically picked up by Spring and is used for serializing REST responses.
This is undesirable. For REST controllers I want to keep the ObjectMapper that Spring creates by default.
How can I tell Spring Boot to not detect and not use my custom ObjectMapper bean for its own internal workings?
The Simone Pontiggia answer is in the correct direction. You should create one #Primary bean, which Spring will use in its internals, and then to create your own ObjectMapper beans and autowired them using #Qualifier.
The problem here is that, creating default bean like:
#Bean
#Primary
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
Won't actually work as expected, because the Spring default ObjectMapper has additional configurations.
The correct way to create default ObjectMapper that will be used by spring, is:
#Bean
#Primary
public ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json().build();
}
You can find more information about the Spring default ObjectMapper here: https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html under 79.3 Customize the Jackson ObjectMapper
Since I didn't want to touch Spring's default ObjectMapper, creating a #Primary ObjectMapper to shadow Spring's default ObjectMapper was out of the question.
Instead, what I ended up doing is creating a BeanFactoryPostProcessor which registers in Spring's context a custom, non primary ObjectMapper:
#Component
public class ObjectMapperPostProcessor implements BeanFactoryPostProcessor {
public static final String OBJECT_MAPPER_BEAN_NAME = "persistenceObjectMapper";
#Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) {
final AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(ObjectMapper.class, this::getCustomObjectMapper)
.getBeanDefinition();
// Leave Spring's default ObjectMapper (configured by JacksonAutoConfiguration)
// as primary
beanDefinition.setPrimary(false);
final AutowireCandidateQualifier mapperQualifier = new AutowireCandidateQualifier(PersistenceObjectMapper.class);
beanDefinition.addQualifier(mapperQualifier);
((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(OBJECT_MAPPER_BEAN_NAME, beanDefinition);
}
private ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
As can be seen in the code above, I also assigned a qualifier to my custom ObjectMapper bean.
My qualifier is an annotation which is annotated with #Qualifier:
#Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface PersistenceObjectMapper {
}
I can then autowire my custom ObjectMapper using my custom annotation, like this:
#Repository
public class MyDao {
#Autowired
public MyDao(DataSource dataSource, #PersistenceObjectMapper ObjectMapper objectMapper) {
// constructor code
}
You can provide a standard ObjectMapper and your customized object mapper, and set the standard as #Primary.
Then gives your custom ObjectMapper a name and use it with #Qualifier annotation.
#Configuration
public class Config{
//This bean will be selected for rest
#Bean
#Primary
public ObjectMapper stdMapper(){
return new ObjectMapper();
}
//You can explicitly refer this bean later
#Bean("customObjectMapper")
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
Now you can reference your custom mapper
#Repository
#Transactional
public class MyDaoImpl implements MyDao {
#Autowired
#Qualifier("customObjectMapper")
ObjectMapper objectMapper
//Dao implementation...
}
#Resource("custonmObjectMapper") will do the same of #Autowired and #Qualifier together
You can create:
public class MapperUtils {
private static final ObjectMapper mapper = new ObjectMapper();
public static <T> T parseResponse(byte[] byteArrray, Class<T> parseType) throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(byteArrray, parseType);
}
}
ObjectMapper is thread-safe. However, some people discourage having single instance because of performance issues (Should I declare Jackson's ObjectMapper as a static field? ).
I want to implement custom repo with Spring data mongodb.
Application.java:
#SpringBootApplication
public class Application implements CommandLineRunner{
#Autowired
private CustomerRepositoryCustom repo;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
System.out.println(this.repo.customMethod());
}
}
My custom repository CustomerRepositoryCustom.java
public interface CustomerRepositoryCustom {
List<Customer> customMethod();
}
Custom implementation CustomCustomerRepositoryImpl.java
public class CustomCustomerRepositoryImpl implements CustomerRepositoryCustom {
#Autowired
private MongoTemplate mongoTemplate;
#Override
public List<Customer> customMethod() {
return this.mongoTemplate.findAll(Customer.class);
}
}
Code Structure
-Application.java
dal
model...
repository
-CustomCustomerRepositoryImpl.java
-CustomerRepositoryCustom.java
When I try to build it, i get an error:
**Description**:
Field repo in socketApp.Application required a bean of type 'socketApp.dal.repository.CustomerRepositoryCustom' that could not be found.
**Action**:
Consider defining a bean of type 'socketApp.dal.repository.CustomerRepositoryCustom' in your configuration.
You have to make Spring aware of your repository. For a Spring Boot application this is typically done by adding this annotation to your application ...
#EnableMongoRepositories("com.package.path.to.repository")
.... thereby telling Spring Boot where to look for Mongo repositories and then let your interface extend org.springframework.data.mongodb.repository.MongoRepository.
For example:
public interface CustomerRepositoryCustom extends MongoRepository {
List<Customer> customMethod();
}
Alternatively, you could annotate your CustomCustomerRepositoryImpl with #Repository and ensure that it is in a package which is scanned by Spring Boot.