How to use custom converters with #DataMongoTest? - java

I have a test instantiating some entities, saving them to MongoDB and loading them again to make sure the mapping works corretly. I'd like to use the #DataMongoTest annotation on the test class to make sure an embedded MongoDB instance is dynamically created.
This worked just fine until I had to introduce custom converters (org.springframework.core.convert.converter.Converter) for some classes. These are set up like this:
#ReadingConverter
public class MyClassReadConverter implements Converter<Document, MyClass> {
...
#WritingConverter
public class MyClassWriteConverter implements Converter<MyClass, Document> {
...
#Configuration
public class SpringMongoSetup extends AbstractMongoConfiguration {
#Override
public Mongo mongo() throws Exception {
//I don't want that in the test..
return new MongoClient("localhost");
}
#Override
public CustomConversions customConversions() {
// ..but I need this
List<Converter<?,?>> converters = new ArrayList<>();
converters.add(new MyClassWriteConverter());
converters.add(new MyClassReadConverter());
return new CustomConversions(converters);
}
...
For normal (non-test) execution this works just fine. The test also works if I use the #SpringBootTest annotation which makes the test use my configuration. Unfortunately, this configuration also defines the host/port for MongoDB, but I'd like to use the host/port of the embedded MongoDB started by #DataMongoTest.
Can I somehow configure it so that either #DataMongoTest uses the custom converters with the embedded MongoDB, or that I can get the embedded host/port while instantiating my configuration class?

To use CustomConverters with #DataMongoTest you need to expose those converters as a Spring bean, e.g.:
#Configuration
public class CustomConversionsConfiguration {
#Bean
public CustomConversions customConversions() {
List<Converter<?,?>> converters = new ArrayList<>();
converters.add(new MyClassWriteConverter());
converters.add(new MyClassReadConverter());
return new CustomConversions(converters);
}
}
...and use the configuration in Mongo test classes:
#RunWith(SpringRunner.class)
#DataMongoTest
#Import(CustomConversionsConfiguration.class)
public class MyMongoTest { ... }

If you are using slicing we will disable all scanning that isn't relevant to Mongo. We have no way to know that your SpringMongoSetup is related to Mongo so, since we don't scan it, it's not applied.
If you do not rely on the auto-configuration for Mongo, you'll have to import that class yourself. You can do so with #Import, e.g.
#RunWith(SpringRunner.class)
#DataMongoTest
#Import(SpringMongoSetup.class)
public class MyMongoTest { ... }

Related

Class based on factory class not available for autowiring

I'm trying to learn the basics of Spring and have defined this class?
public class Processor {
public Processor() {
this.setup();
}
private void setup(){
//run some setup code
}
public String processString(String str) {
// process the string
return str;
}
}
I want to Spring enable this class so I use a factory bean:
Reading https://www.baeldung.com/spring-factorybean I use:
public class Processor implements FactoryBean<Processor> {
#Override
public Processor getObject() throws Exception {
return new Processor();
}
#Override
public Class<?> getObjectType() {
return Processor.class;
}
}
To Test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ProcessorFactory.class)
public class ProcessorTest {
#Autowired
private Processor processor;
#Test
public void testProcessor() {
//Some tests
}
}
This works as expected.
When I try to use
#Autowired
private Processor processor;
elsewhere in my project I receive compile-time error :
Could not autowire. No beans of 'Processor' type found.
Have I not setup the factory correctly? I should annotate the Processor object to indicate it is to be autowired ? Perhaps this is not a valid use case for Factory ?
In general, factory beans are pretty outdated, first spring versions indeed used this approach, but spring has evolved since that time.
The Factory bean itself should be registered in the spring configuration (the very first versions of spring used xml based configuration because Java Annotation did not exist at that time) so the tutorial contains the XML configuration example. Anyway, that's probably the reason of failure. In the test you should also specify the path to the xml configuration otherwise the factory bean won't be loaded.
You can use these factory beans (they're still supported) but they have the following downsides:
They couple your code to the spring framework
A lot of boilerplate (usually in typical application there can be hundreds of beans, so creating a Factory Bean for each one is an overkill).
So you can:
Instead of using Factory Beans, annotate the Processor with #Component annotation (or more specialized #Service).
Alternatively Use Java configuration:
#Configration
public class MyConfig {
#Bean
public Processor processor() {
// optionally slightly customize the creation of the class if required;
return new Processor();
}
}

Why when use #Autowired in #Configuration in Spring failed sometimes?

I'm using Spring Boot with a thrift server, and I have two #Configuration class with two bean generation method, and the code is as following:
#Configuration
public class EagleBeanCreator {
#Bean(destroyMethod = "destroy")
public EagleRestClient build() {
EagleRestClient client = new EagleRestClient();
// some set values code
return client;
}
}
And another one:
#Configuration
public class EagleServiceBuilder {
#Autowired
private EagleRestClient eagleProxy;
#Bean
public EagleService eagleService() {
EagleService service = new EagleService();
System.out.println(eagleProxy);
service.setEagleProxy(eagleProxy);
return service;
}
}
But when I run spring-boot:run, it print out null for "System.out.println(eagleProxy);"
Why?
=========================UPDATE=============================
I know setter injection or constructor injection works.
You may want to try this out.
#Configuration
public class EagleServiceBuilder {
#Bean
public EagleService eagleService(EagleRestClient eagleProxy) {
EagleService service = new EagleService();
System.out.println(eagleProxy);
service.setEagleProxy(eagleProxy);
return service;
}
}
My guess is that the way you currently implement doesn't indicate a dependency between the EagleService and EagleRestClient. So your current implementation leads to random initialization order between the two beans. The modified version tells Spring "Hey, my EagleService depends on EagleRestClient. Please initialize EagleRestClient before EagleService.
Because the order to load EagleBeanCreator and EagleServiceBuilder is not definite. You can use #Order or #ConditionalOnClass to make sure EagleBeanCreator initialize first.
Because the #Configuration bean are initialized in the same phase of bean lifecycle. I don't remember them clearly but something like:
Configurations -> Components -> Services
With the beans in the same phase, if they depend on each other, you should declare the load order by some #Conditional or #Order
Add #DependsOn("eagleRestClient") annotation on the definition of EagleService.
#DependsOn("eagleRestClient")
#Bean
public EagleService eagleService() {
EagleService service = new
EagleService();
System.out.println(eagleProxy);
service.setEagleProxy(eagleProxy);
return service;
}
Spring will then first create rest client then the eagle service.
First, you need to get the spring container through ApplicationContextAware try
ApplicationContext.getBean(EagleRestClient.class)

Test spring configuration with ConditionalOnCloudPlatform annotation

I am trying to test my Spring configuration class which is annotated with ConditionalOnCloudPlatform.
Here is a very simplified example of the configuration class (I can't post my actual code):
#Configuration
#ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
public class CloudConfigurationExample {
#Bean
public MyBean myBean(MyProperties properties) {
return new MyBean(properties.getParam);
}
}
To test I was hoping to do this:
#RunWith(MockitoJUnitRunner.class)
public class CloudConfigurationExampleTest {
private CloudConfigurationExample cloudConfigurationExample;
private MyProperties myProperties;
#Before
public void setUp() {
myProperties = new MyProperies();
myProperties.setParam("test");
cloudConfigurationExample = new CloudConfigurationExample(myProperties);
}
#Test
public void test() {
MyBean myBean = cloudConfigurationExample.myBean();
// do asserts etc.
}
}
The issue I have is that ConditionalOnCloudPlatform is activated and expects a valid cloud connector to be present. As a result I get No suitable cloud connector found.
Does anyone know the correct way so get Junit to ignore this annotation? I tried setting an environment variable with VCAP_SERVICES, which is what this annotation expects, but it didn't work.
Thanks!
ConditionalOnCloudPlatform gets activated if environment contains properties VCAP_APPLICATION and VCAP_SERVICES
There are different ways to overcome this issue,
Firstly, ensure no properties containing above prefixes are passed.
Secondly, check for cloud profile #Profile("cloud") or ignore this class during test #Profile("!test") and many more ways.
code snippet:
CLOUD_FOUNDRY {
#Override
public boolean isActive(Environment environment) {
return environment.containsProperty("VCAP_APPLICATION")
|| environment.containsProperty("VCAP_SERVICES");
}
},

Injecting library class as dependencies in spring project

I have multiple library classes in my project which need to be injected into a service class. This is the error statement for IntegrationFactory class:
Consider defining a bean of type 'com.ignitionone.service.programmanager.integration.IntegrationFactory' in your configuration.
This error is coming on almost every injection where this library class is injected.
I have already added the Library package in #ComponentScan, but, as it is read-only file, I can not annotate the library class. I came to know from some answer here that Spring can not inject classes which it does not manage. This library is not built on spring.
I have tried to create a #Bean method which returns the IntegrationFactory(class in question) in the class where #Inject is used, but this too does not seem to work.
How can this be done, preferably without creating a stub/copy class?
This is EngagementServiceImpl class snippet:
#Inject
public EngagementServiceImpl(EngagementRepository engagementRepository,
#Lazy IntegrationFactory integrationFactory, TokenRepository tokenRepository,
EngagementPartnerRepository engagementPartnerRepository, MetricsService metricsService) {
this.engagementRepository = engagementRepository;
this.integrationFactory = integrationFactory;
this.tokenRepository = tokenRepository;
this.engagementPartnerRepository = engagementPartnerRepository;
this.metricsService = metricsService;
}
This is injection part:
#Autowired
private EngagementService engagementService;
This is ConfigClass:
#Configuration
public class ConfigClass {
#Bean
public IntegrationFactory getIntegrationFactory(){
Map<String, Object> globalConfig = new HashMap<>();
return new IntegrationFactory(globalConfig);
}
#Bean
#Primary
public EntityDataStore getEntityDataStore(){
EntityModel entityModel = Models.ENTITY;
return new EntityDataStore(this.dataSource(), entityModel );
}
#ConfigurationProperties(prefix = "datasource.postgres")
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.build();
}
}
You need to add your bean definitions in a configuration class.
#Configuration
public class ServiceConfig {
#Bean
public IntegrationFactory getIntegrationFactory(){
// return an IntegrationFactory instance
}
}
Then you have to make sure your #Configuration class gets detected by Spring, either by having it within your scanned path or by manually importing it via #Import from somewhere withing you scanned path. An example of #Import, considering you are using Spring Boot.
#Import(ServiceConfig.class)
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Hope this helps!
Your Bean IntegrationFactory can't be found, as it is not annotated with any Spring stereotype and therefore not recognized by the component scan.
As you have multiple options to provide an instance of your class to the application context, read the Spring documentation (which also includes samples) to find out which one fits you the most:
https://docs.spring.io/spring/docs/5.1.0.RELEASE/spring-framework-reference/core.html#beans-java-basic-concepts
One Option would be to create a factory which provides an instance of your class to the application context, like it is stated in the documentation:
#Configuration
public class AppConfig {
#Bean
public IntegrationFactory myIntegrationFactory() {
return new IntegrationFactory();
}
}
Do not forget to add the Configuration to the application context.

How to customise Jackson in Spring Boot 1.4

I've been unable to find examples of how to use Jackson2ObjectMapperBuilderCustomizer.java in spring boot 1.4 to customise the features of Jackson.
The doco for customising Jackson in boot 1.4 - https://docs.spring.io/spring-boot/docs/1.4.x/reference/htmlsingle/#howto-customize-the-jackson-objectmapper
My configuration works, although I am unsure if this is the correct way to customise the object mapper using Jackson2ObjectMapperBuilderCustomizer.java
#Configuration
public class JacksonAutoConfiguration {
#Autowired
private Environment env;
#Bean
public Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Jackson2ObjectMapperBuilder builder = configureObjectMapper();
customize(builder, customizers);
return builder;
}
private void customize(Jackson2ObjectMapperBuilder builder,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) {
customizer.customize(builder);
}
}
private Jackson2ObjectMapperBuilder configureObjectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
List<String> activeProfiles = asList(env.getActiveProfiles());
if (activeProfiles.contains(SPRING_PROFILE_DEVELOPMENT)) {
builder.featuresToEnable(SerializationFeature.INDENT_OUTPUT);
}
return builder;
}
}
To provide some context, this class sits in my own spring starter project for REST services that just auto configures a number of things, like ControllerAdvice and some trivial features like the above.
So my goal is to extend the Jackson configuration rather than to override any configuration provided by boot or other packages.
To customize the Jackson ObjectMapper that's already pre-configured by Spring Boot, I was able to do this (the example here is to add a custom deserializer).
Configuration class:
#SpringBootConfiguration
public class Application {
#Autowired
private BigDecimalDeserializer bigDecimalDeserializer;
...
#Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.deserializerByType(BigDecimal.class, bigDecimalDeserializer);
}
};
}
...
}
And my custom deserializer, to show how it's picked up by Spring:
#Component
public class BigDecimalDeserializer extends StdDeserializer<BigDecimal> {
public BigDecimalDeserializer() {
super(BigDecimal.class);
}
#Override
public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
...
}
...
}
It depends on what you're trying to do.
If you want to make some customisations in addition to those that are performed by default then you should create your own Jackson2ObjectMapperBuilderCustomizer implementation and expose it as a bean. What you currently have is a more complex version of this. Rather than having the customisers injected and then calling them yourself, you can just create your own customiser bean and Boot will call it for you.
If you want to take complete control and switch off all of Boot's customisations then create a Jackson2ObjectMapperBuilder or ObjectMapper bean and configure it as required. The builder approach is preferred as this builder is then also used to configure ObjectMappers created by other components such as Spring Data REST.
Looking at your code and taking a step back, you could configure things far more simply by using a profile-specific configuration file (something like application-dev.properties) to enable indenting of Jackson's output. You can read more about that here.
just create an ObjectMapper bean:
#Bean
ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder
.json()
.featuresToEnable(MapperFeature.DEFAULT_VIEW_INCLUSION)
.build();
}

Categories