conditional class not detected on mvn clean package - spring-boot

I have the following conditional class that should be unseen unless some other class annotates itself with a custom annotation:
#ConditionalOnBean(MyConf.class)
#Service
#RequiredArgsConstructor
public class CondClass{
public void method() throws MessagingException
Then I have a spring boot test:
#SpringBootTest
public void myTest{
It works fine if I manually execute it, but if I do:
mvn clean package
I get:
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: java.lang.IllegalStateException: Failed to introspect Class
[it.bmed.medmad.arch.common.websocket.service.impl.CondClass] from ClassLoader
[sun.misc.Launcher$AppClassLoader#18b4aac2]
Caused by: java.lang.NoClassDefFoundError: org/springframework/messaging/MessagingException
Caused by: java.lang.ClassNotFoundException:
org.springframework.messaging.MessagingException
Indeed that MessagingException is on a dependency that is not included, but I would expect that the whole class is not inspected because of the annotation.

Add #ConditionalOnClass(MyConf.class), too:
#ConditionalOnClass(MyConf.class)
#ConditionalOnBean(MyConf.class)
#Service
#RequiredArgsConstructor
public class CondClass {
// ...
}
sample: official usage

Related

Wildfly / Infinispan HTTP session replication hits ClassNotFoundException when unmarshalling CGLIB Session Bean

I'm running Wildfly 20.0.1.Final in standalone, two-node cluster. I'm trying to implement HTTP Session sharing between the nodes.
In my Spring web application I have <distributable/> in my web.xml.
My session object is this:
package my.package;
#Component
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.INTERFACES)
public class MySessionBean implements Serializable {
// omitted for brevity
}
As you can see, I have ScopedProxyMode.TARGET_CLASS.
When I perform a failover in Wildfly, my HTTP Session can't be restored however, as I hit this warning:
2021-02-22 13:24:18,651 WARN [org.wildfly.clustering.web.infinispan] (default task-1) WFLYCLWEBINF0007:
Failed to activate attributes of session Pd9oI0OBiZSC9we0uXsZdBwkLnadO1l4TUfvoJZf:
org.wildfly.clustering.marshalling.spi.InvalidSerializedFormException:
java.lang.ClassNotFoundException: my.package.MySessionBean$$EnhancerBySpringCGLIB$$9c0fa1df
from [Module "deployment.myDeployment.war" from Service Module Loader]
...
Caused by: java.lang.ClassNotFoundException: my.package.MySessionBean$$EnhancerBySpringCGLIB$$9c0fa1df from [Module "deployment.myDeployment.war" from Service Module Loader]
at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:255)
at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410)
at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398)
at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:398)
at org.jboss.marshalling#2.0.9.Final//org.jboss.marshalling.ModularClassResolver.resolveClass(ModularClassResolver.java:133)
at org.jboss.marshalling.river#2.0.9.Final//org.jboss.marshalling.river.RiverUnmarshaller.doReadClassDescriptor(RiverUnmarshaller.java:1033)
at org.jboss.marshalling.river#2.0.9.Final//org.jboss.marshalling.river.RiverUnmarshaller.doReadNewObject(RiverUnmarshaller.java:1366)
at org.jboss.marshalling.river#2.0.9.Final//org.jboss.marshalling.river.RiverUnmarshaller.doReadObject(RiverUnmarshaller.java:283)
at org.jboss.marshalling.river#2.0.9.Final//org.jboss.marshalling.river.RiverUnmarshaller.doReadObject(RiverUnmarshaller.java:216)
at org.jboss.marshalling#2.0.9.Final//org.jboss.marshalling.AbstractObjectInput.readObject(AbstractObjectInput.java:41)
at org.wildfly.clustering.marshalling.spi#20.0.1.Final//org.wildfly.clustering.marshalling.spi.util.MapExternalizer.readObject(MapExternalizer.java:65)
...
Note, that the ClassNotFoundException is complaining because the lack of my.package.MySessionBean$$EnhancerBySpringCGLIB$$9c0fa1df, which is the Spring-enhanced bean of my MySessionBean bean.
Changing to ScopedProxyMode.INTERFACES is not an option.
Can you please point me in the right direction with this?
I managed to fix this by creating a simple POJO, called MySessionDTO, and using that in my session.
So initially I had this (which threw the exception in the question):
request.getSession().setAttribute("mySession", mySessionBean);
...and after I created MySessionDTO (see below), I refactored it into this:
request.getSession().setAttribute("mySession", mySessionBean.getMySessionDTO());
MySessionDTO is a simple POJO:
package my.package;
import java.io.Serializable;
public class MySessionDTO extends MySessionBean implements Serializable {
public MySessionDTO (MySessionBean mySessionBean) {
this.setAttributeX(mySessionBean.getAttributeX());
this.setAttributeY(mySessionBean.getAttributeY());
}
}

Kotlin integration Tests with Spring Boot

What I have: I'm developing a microservice, using Spring Boot with Web and MongoDB as storage. For CI integration tests I use test containers. I have two integrations tests with SpringBootTest annotations, which use TestConfig class. TestConfig class provides setup MongoDB test container with fixed exposed ports.
My problem: When I run my tests one at a time then they succeed. But when I run my tests at the same time then they failed.
MongoContainerConfig.kt
#TestConfiguration
class MongoContainerConfig {
var mongoContainer: GenericContainer<Nothing>
constructor() {
mongoContainer = FixedHostPortGenericContainer<Nothing>("mongo")
.withFixedExposedPort(27018,27017)
mongoContainer.start()
}
#PreDestroy
fun close() {
mongoContainer.stop()
}
}
First test
#SpringBootTest(
classes = arrayOf(MongoContainerConfig::class, AssertUtilsConfig::class),
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
class CardControllerTest {
#Test
fun test() {}
}
Second test
#SpringBootTest(classes = arrayOf(MongoContainerConfig::class, AssertUtilsConfig::class))
class PositiveTest {
#Test
fun test() {}
}
Error msg
Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoContainerConfig': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.lang.card.engcard.config.MongoContainerConfig$$EnhancerBySpringCGLIB$$e58ffeee]: Constructor threw exception; nested exception is org.testcontainers.containers.ContainerLaunchException: Container startup failed
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1320)
a
This project you can see on github with CI
https://github.com/GSkoba/eng-card/runs/576320155?check_suite_focus=true
It's very funny because tests work if rewrite them to java
Hah, it not easy)
https://docs.spring.io/spring/docs/5.2.5.RELEASE/spring-framework-reference/testing.html#testcontext-ctx-management-caching
Tests have different context and its why MongoContainerConfig call twice.
Fix - add webEnv as in CardControllerTest.kt
#SpringBootTest(classes = arrayOf(MongoContainerConfig::class, AssertUtilsConfig::class),
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PositiveTest
Proof https://github.com/GSkoba/eng-card/actions/runs/78094215

how to override Spring Boot autowired component during testing

I'm trying to write tests for a Spring Boot batch application.
I have an interface "WsaaClient" and two implementations, I need to use one of them for normal execution and the other for testing purposes.
In the project, I have FCEClient class that has an autowired field "LoginManager", which has an autowired field "WsaaClient".
#Component
#Profile("!dev")
public class FCEClient implements IFCEClient {
#Autowired
LoginManager loginManager;
#Component
public class LoginManager {
#Autowired
WsaaClient client;
#Component
public class AfipWsaaClientSpring extends AfipWsaaClient {
AfipWsaaClient is in a non-spring maven dependency. It implements WsaaClient.
Running the Spring Batch application works well and AfipWsaaClientSpring is picked.
Now I want to write a test and need to use a dummy implementation for WsaaClient.
So I put under src/test/java this class:
#Component
public class TestWsaaClientSpring implements WsaaClient {
And this test:
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration
public class FceBatchApplicationTests {
private JobLauncherTestUtils jobLauncherTestUtils;
#Test
public void testJob() throws Exception {
JobExecution jobExecution = jobLauncherTestUtils.launchJob();
Assert.assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode());
}
}
Running it from JUnit Launcher on Eclipse throws:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'afipWsaaClientSpring' defined in file [/home/guish/vmshare/eclipsews/ec/ec-batch/target/classes/com/mycompany/AfipWsaaClientSpring.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.mycompany.AfipWsaaClientSpring]: Constructor threw exception; nested exception is java.io.FileNotFoundException: ./wsaa_client.properties (No such file or directory)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1303) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1197) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
The FileNotFoundException is not relevant, the file is not present because of running as a test and Spring Boot should not pick the AfipWsaaClientSpring implementation.
How can I override the Autowired option in my test code and choose TestWsaaClientSpring instead?
And just in case, how can I prevent Spring Boot from instantiating the AfipWsaaClientSpring when running as a test?
Annotation #SpringBootTest has 'properties' attribute (https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/SpringBootTest.html).
so, you can specify spring profile like this,
#SpringBootTest(properties = {"spring.profiles.active=test"}, classes=MyConfiguration.class)
As mentioned by Charles Lee you could provide the active profile for SpringBootTest. Also you could do this with the annotation #ActiveProfile("theprofile") on your FceBatchApplicationTests class.

Spring Boot ConfigurationProperties fail to initialize for integration testing

Using gradle (3.4.1) with an integrationTest configuration, the tests using the ConfigurationProperties of Spring Boot (1.5.1.RELEASE) is failing to initialize even though the application initializes correctly (./gradlew bootRun). The class annotated with ConfigurationProperties is similar to the following
#Component
#ConfigurationProperties(prefix = "foo")
#Validated
public class AppConfiguration {
#NonNull
private URL serviceUrl;
...
The configuration file does have getters and setters. The error that is generated is similar to the following
java.lang.IllegalStateException: Failed to load ApplicationContext
....
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'AppConfiguration': Could not bind properties to AppConfiguration
....
Caused by: org.springframework.validation.BindException: org.springframework.boot.bind.RelaxedDataBinder$RelaxedBeanPropertyBindingResult
Field error in object 'foo' on field 'serviceUrl': rejected value [null]; codes ...
The configuration class of the integration test is annotated as follows
#Configuration
#ComponentScan(...)
#EnableConfigurationProperties
#EnableIntegration
public static class ContextConfiguration {}
The test class had the following annotations
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class ReleaseTest {
...
After looking at the Spring Boot code for the ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization() it suggested that the property source was not being discovered. Adding the org.springframework.boot:spring-boot-starter-test artifact as a compile-time dependency and modifying the context configuration of the test class to
#ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
the AppConfiguration class was initialized properly using a YAML-based properties file.
An alternative is to add
#TestPropertySource("classpath:/application.properties")
This approach doesn't require the spring-boot-starter-test dependency and requires that a "traditional" properties file is used (a YAML file will not work with this approach).

Usage of Spring #ConfigurationProperties gives nullpointer when running tests, JHipster app

I have implemented a simple file upload routine using Spring Boot ConfigurationProperties annotation so I can load the directory from my YAML configuration file:
Service:
#Service
public class FileSystemStorageService implements StorageService {
private final Logger log = LoggerFactory.getLogger(FileSystemStorageService.class);
private final Path pictureLocation;
#Autowired
public FileSystemStorageService(StorageProperties storageProperties) {
pictureLocation = Paths.get(storageProperties.getUpload());
}
And the StorageProperties:
#Component
#ConfigurationProperties("nutrilife.meals")
public class StorageProperties {
private String upload;
public String getUpload() {
return upload;
}
public void setUpload(String upload) {
this.upload= upload;
}
}
In the yaml file I have:
nutrilife:
meals:
upload: /$full_path_to_my_upload_dir
This works perfectly in normal Spring Boot runtime but the problem starts when I try to run my integration tests, it throws the error:
java.lang.IllegalStateException: Failed to load ApplicationContext
...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'fileSystemStorageService' defined in file [/home/mmaia/git/nutrilife/build/classes/main/com/getnutrilife/service/upload/FileSystemStorageService.class]: Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.getnutrilife.service.upload.FileSystemStorageService]: Constructor threw exception; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:279)
...
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.getnutrilife.service.upload.FileSystemStorageService]: Constructor threw exception; nested exception is java.lang.NullPointerException
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:154)
So basically during tests the YAML file configuration looks like it's not being properly picked up. I am new to Spring. How can I fix it?
try adding the #value annotation :
#Value("upload")
private String upload;
The above answer is valid for application.properties config.
it also works with yaml as well.
You may find your correct correct yaml config here :
24.6 Using YAML instead of Properties

Resources