Spring Boot auto configuration order from external dependency - java

I have a problem trying to get my autoconfiguration working. I have two jars as follows, each have a spring.factories file where these two are enabled for EnableAutoConfigurationProperties.
This configuration is in my-package-mock.jar, it depends on my-package-real.jar below:
package org.packages.package.packageA;
#Configuration
#AutoConfigureBefore(AutoConfigurationB.class)
public class AutoConfigurationA {
#Bean
public MyService mockService() {
return new MyMockService();
}
}
This configuration is in my-package-real.jar:
package org.packages.package.packageB;
#Configuration
#ConditionalOnMissingBean(MyService.class)
public class AutoConfigurationB {
#Bean
public MyService realService() {
return new MyRealService();
}
}
Now the idea is that if my-package-mock.jar is included then AutoConfigurationB will not be processed as A is ordered to be before and by the time it gets to B MyService is already defined.
However, it does not work when used in a third project that includes these jars. It looks like the AutoConfigureOrder annotation is skipped when loading these jars from the classpath and these configurations are processed in the order the jvm loads these classes. In my particular case it does B first and at that point MyService is not yet defined and thus will instantiate the RealService bean. How can I get this to work?
Obviously this is a small example where a #Primary annotation on the mock will do the job, but that is not what I'm looking for.
Edit: it seems if the #SpringBootApplication annotated main is not a part of the package where these configurations are then things do work. E.g. the annotation is not in "org.packages.package" but "org.somewhereelse" then things work.
package org.packages.package;
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(Collections.singletonList(TestApplication.class).toArray(), args);
}
}

#AutoConfigureBefore and #AutoConfigureAfter only apply when a configuration class is loaded as a result of auto-configuration being enabled and it being listed in spring.factories. When your auto-configuration classes are in org.packages.package (or a sub-package) and your main application class is in the same package, they're being found by Spring Framework's standard component scanning. This happens because #SpringBootApplication enables component scanning for the package of the class that it's annotating. As a result of this the auto-configuration-specific ordering doesn't apply.
To avoid the problem, you should places your auto-configuration classes in a package that isn't used by any application code.

Related

After adding #ComponentScan to load beans from a jar my controllers isn't scan and i get 404

I have a spring-boot application (Java8, spring-boot 2.1.4-RELEASE).
One of the services in the business layer need to #Autowire a bean from a certain jars in my classpath. In order to achieve that i had to add #ComponentScan({"package.to.my.bean.inside.the.jar"}) and it was magically scanned and wired successfully (this was added to the main spring-boot class which declare the main method).
However, since then my controllers aren't scanned hence the DispatcherServlet is returning 404 for every request i trigger (default dispatcher).
Actually my entire spring-boot app annotations is being ignored - no scan is performed.
Just to emphasis - the application worked perfectly before adding the #ComponentScan.
Main spring-boot app class:
package com.liav.ezer;
// This is the problematic addition that cause the endpoints to stop
// inside a jar in the classpath, in com.internal.jar package resides an
// object which i need to wire at run time
#ComponentScan({"com.internal.jar"})
#SpringBootApplication
public class JobsApplication {
public static void main(String[] args) {
SpringApplication.run(JobsApplication .class, args);
}
}
Controller example:
package com.liav.ezer.controller;
#RestController
#EnableAutoConfiguration
#RequestMapping(path = "/jobs")
public class JobController {
#GetMapping(path="/create", produces = "application/json")
#ResponseStatus(HttpStatus.OK)
String createJob(#RequestParam() String jobName) String jobName){
return "job created...";
}
}
I tried adding my spring-boot app base package to the list of packages in the #ComponentScan with no luck.
I tried narrowing down the scope of the package declaration to be only on the class which i need with no luck.
Here is the code
According to Spring documentation
Configures component scanning directives for use with #Configuration
classes. Provides support parallel with Spring XML's
element. Either basePackageClasses() or
basePackages() (or its alias value()) may be specified to define
specific packages to scan. If specific packages are not defined,
scanning will occur from the package of the class that declares this
annotation.
In your case when you are adding
#ComponentScan({"com.internal.jar"})
you are disabling scanning of
com.liav.ezer.controller
To fix it you can do the following configuration
#ComponentScan(basePackages = {"com.internal.jar", "com.liav.ezer.controller"})
If so, remove #ComponentScan, can declare that bean in yourself configuration.
try below
#SpringBootApplication
public class JobsApplication {
public static void main(String[] args) {
SpringApplication.run(JobsApplication .class, args);
}
#Bean
public BeanInOtherJar xxBean(){
return new com.internal.jar.XXX();
}
}

Create Custom Repository to Spring Data JPA

I try to create a custom repository by following this tutorial: https://www.baeldung.com/spring-data-jpa-method-in-all-repositories
My app build fail with error:
NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.dao.ExtendedStudentRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
My full source code:
https://github.com/sesong11/springjpa-custom-repo
There are plenty of similar questions, but none of them fixed for me. Perhaps it's Spring issue on current version 2.1.1, or I missed some configuration.
To get your test working, you have to do the following:
1) Replace the incorrect definition of basePackages in StudentJPAH2Config to com.example.demo.dao, or better remove it as redundant:
#Configuration
#EnableJpaRepositories(repositoryBaseClass = ExtendedRepositoryImpl.class)
public class StudentJPAH2Config {
}
2) Also replace basePackages in #ComponentScan and #EntityScan in DemoApplication class to com.example.demo.dao and com.example.demo.entity. Or better removed those and #EnableTransactionManagement annotations at all:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3) Add a no-argument constructor to the entity Student.
4) Correct your test class - use #DataJpaTest to test DAO layer and import your StudentJPAH2Config configuration:
#RunWith(SpringRunner.class)
#DataJpaTest
#Import(StudentJPAH2Config.class)
public class ExtendedStudentRepositoryIntegrationTest {
//...
}
With these corrections, I've run your test successful.
Before we run the test, let's make sure the main application is running. Actually, it has some issues.
1. IllegalArgumentException: Not a managed type: class com.example.demo.entity.Student.
The problem is #EntityScan("com.example.demo.entity.*"). The package name isn't correct, therefore the Student class isn't scanned.
The solution is #EntityScan("com.example.demo.entity").
2. PropertyReferenceException: No property attributeContainsText found for type Student!
The problem is that the repository base class isn't set, and JpaQueryLookupStrategy thinks it should construct a RepositoryQuery from the method name findByAttributeContainsText. It recognises the pattern findBy{EntityPropertyName} and fails to find the field attributeContainsText in Student.
The repository base class isn't set, because the configuration StudentJPAH2Config isn't applied. A configuration doesn't get applied if it's unable to be scanned.
#ComponentScan("com.example.demo.dao") doesn't scan the package where StudentJPAH2Config resides.
The solution is #ComponentScan("com.example.demo") which will scan both "com.example.demo" and "com.example.demo.dao" packages1.
Now, when the application is fine, let's get back to the test.
3. NoSuchBeanDefinitionException: No qualifying bean of type com.example.demo.dao.ExtendedStudentRepository available.
The problem is that StudentJPAH2Config doesn't constitute the whole configuration, and some beans are missing.
The solution is to list all the configuration classes.
#ContextConfiguration(classes = { StudentJPAH2Config.class, DemoApplication.class })
or
#ContextConfiguration(classes = DemoApplication.class)
or
#SpringBootTest
4. JpaSystemException: No default constructor for entity com.example.demo.entity.Student.
The problem is that Hibernate2 requires the default constructor for Student:
#Entity
public class Student {
#Id
private long id;
private String name;
public Student() {}
public Student(int id, String name) {
this.id = id;
this.name = name;
}
// getters & setters
}
1 #ComponentScan("com.example.demo.dao") is redundant since this package will be scanned because of #SpringBootApplication located in there.
2 Hibernate is the default JPA provider in Spring applications.
Following changes were made:
#SpringBootApplication
#ComponentScan("com.example.demo.dao")
#EntityScan("com.example.demo.entity")
#EnableJpaRepositories(basePackages = "com.example.demo.dao",
repositoryBaseClass = ExtendedRepositoryImpl.class)
#EnableTransactionManagement
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
and for StudentJPAH2Config class
#Configuration
#ComponentScan("com.example.demo")
#EnableJpaRepositories(basePackages = "com.example.demo.dao",
repositoryBaseClass = ExtendedRepositoryImpl.class)
public class StudentJPAH2Config {
// additional JPA Configuration
}
Student class which was missing empty constructor:
#Entity
public class Student {
public Student() {
}
public Student(long id, String name) {
this.id = id;
this.name = name;
}
#Id
private long id;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And the result
and application running state
Your implementation of the custom Spring Data JPA repository base class does work. There were just a few items with the Spring Boot Application Context that needed to be addressed. You did have Spring Boot Starter modules declared as dependencies, so I submitted a pull request with changes that moved all the responsibility for manually configuring the context to Spring Auto Configuration factory providers.
Here are some notes about changes that were required to get the tests to pass, along with some brief details on each. Note, to run the Spring Boot Application outside of the test scope, you will want to add an appropriate JDBC vendor dependency to the classpath, along with any spring application properties to define the datasource properties. Spring Auto Config should take care of any EntityManagers and TransactionManagers with reasonable settings after the datasource properties are defined. See Spring Boot features - Working with SQL Databases.
Notes on changes:
Removed all Spring Configuration annotations (with exception to #SpringBootApplication and #EnableJpaRepositories). Application pom is configured to depend on Spring Boot Starter modules that provide Spring Auto Configuration classes already through spring.factories while also ensuring all other dependencies are met.
#EnableJpaRepositories does not need to declare basePackages as Auto Config modules will default to using all packages beneath the Spring Main Application class. Declaration of the additional repositoryBaseClass is still necessary.
#ComponentScan removed as Auto Config modules will perform a ComponentScan for all packages beneath the Spring Main Application class, which is already at the top level package for the project. Furthermore, this is declaring a basePackages to scan value that will not include the #Configuration class StudentJPAH2Config.
#EntityScan removed as Auto Config modules will perform a scan in all packages beneath the Spring Main Application class.
#EnableTransactionManagement removed as Auto Config modules will perform the same action.
Removed default application.properties as they depend on H2, however the pom only provides that dependency within the test scope's classpath. Furthermore all configuration properties set within the property file are generally Auto Configured by Spring Starters on presence of H2.
Added a default constructor on Student for hibernate. Hibernate requires this constructor while managing entities, unless #PersistenceConstructor or #Immutable is being used which is outside the scope of this issue.
Configured Spring Boot Auto Configuration for unit test application context in ExtendedStudentRepositoryIntegrationTest. This is the same as the context configuration defined for the Spring Boot integration test in DemoApplicationTests. When using #ContextConfiguration directly with StudentJPAH2Config, the configuration that was defined on the Spring Boot Main Application class (which was the ComponentScan, EntityScan, TransactionManagement) was not being applied to the Spring Test Application Context.
A few notes on Spring Auto Configuration as it was the main issue impeding this project to successfully run:
Spring Application Starters are (generally) split into two types of dependencies.
autoconfigure modules that declare a minimal set of dependencies in it's POM, however are built with many dependencies during it's compilation. The auto configure modules also advertise Configuration classes and factories via a spring.factories file located in META-INF of the package. Spring Boot during it's bootstrapping phase will load these configuration classes. Lastly the configuration classes will selectively declare beans and make other application context changes/enhancements based on other beans declared in the context and what dependencies are actually found on the classpath. This allows for general/reasonable default configurations all based on what libraries you are pulling in and a minimal set of properties.
starter modules (generally) do not provide much/any classes. Their purpose is to provide POMs that declare dependencies required for good baselines of development for a particular Spring project. The starter modules also depend on applicable autoconfigure modules, which pair up with these dependencies to allow you to start running with your application quickly.
For the most part, when using Spring Starters, the main driver should not be figuring out what you need to manually configure in the Spring context, but rather, what you should un-configure in case its baseline does not suit your needs. When it comes to following tutorials/examples online like you have, keep in mind that some items surrounding configuration may be shown. There may be times these configuration instructions are necessary, but often they are to show details for configuration required in non-boot Spring applications or for transparency.
You need an annotation in your implemented class.
You must add the #Component or #Service annotation in order for Spring to pick up the injection
#Component
public class ExtendedRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements ExtendedRepository<T, ID> {
You need to make below changes to resolve this issue. Also, i executed this with JDK 8 as i don't have JDK 11 in my local. Also, i executed ExtendedStudentRepositoryIntegrationTest.java for the purpose of issue reproduction.
In StudentJPAH2Config.java, component scan need to be added.
#Configuration
#ComponentScan("com.example.demo")
#EnableJpaRepositories(basePackages = "com.example.demo.dao",
repositoryBaseClass = ExtendedRepositoryImpl.class)
public class StudentJPAH2Config {
In DemoApplication the base package name has to be corrected.
#SpringBootApplication
#ComponentScan("com.example.demo.dao")
#EntityScan("com.example.demo.entity")
#EnableTransactionManagement
public class DemoApplication {
The reason for #1 change is without component scan in test config, spring was not able to look for the dependencies in right packages. #2 is needed as your Student.java is inside com.example.demo.entity package directly, not in any subpackage of it. You can also specify #EntityScan in test config file StudentJPAH2Config, though it's not needed.
Now, this resolve the immediate issue faced by you but still you will be welcomed with some other issues which are pretty straight forward and you would be able to take it forward from here.
You have to put #Primaryannotation on the ExtendedRepositoryImpl class. It should work.
Spring basically not able to qualify the dependency. #Primary will make sure wherever you are injecting dependency for ExtendedRepository interface first qualifying bean(class) would be ExtendedRepositoryImpl.
You have to remove #ContextConfiguration(classes = { StudentJPAH2Config.class }) and add #SpringBootTestannotations on your test class ExtendedStudentRepositoryIntegrationTest it will resolve the error NoSuchBeanDefinitionException you currently have.
But again I have got error like:
Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.example.demo.entity.Student
which is because Spring is not able to scan Entity Student.
For this you have to change the #EntityScan("com.example.demo.entity.*") to #EntityScan("com.example.demo.entity") on DemoApplication class.
Again I got the error:
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property attributeContainsText found for type Student!
Which is because the method findByAttributeContainsText() is not a valid method.

Spring boot test with multiple configuration

In my Spring boot 2.1 project I have different #Configurations for different test (ConfigurationA and ConfigurationB), that reside in different packages. Both configurations define the same set of beans but in a different manner (mocked vs. the real thing)
As I am aware of the Bean overriding mechanism introduced in Spring Boot 2.1, I have set the property: spring.main.allow-bean-definition-overriding=true.
However I do have a test with the following the setup of the following configuration and test class. First there is a #Configuration in the productive part (I'm using Maven):
package com.stackoverflow;
#Configuration
public class ProdConfiguration{
...
}
Then in the test branch there is a general Test #Configuration on the same package level:
package com.stackoverflow
#Configuration
public class TestConfiguration {
#Bean
public GameMap gameMap() {
return Mockito.mock(GameMap.class);
}
}
And in a subpackage I have another #Configuration:
package com.stackoverflow.impl;
#Configuration
public class RealMapTestConfiguration {
#Bean
public GameMap gameMap() {
return new GameMap("testMap.json");
}
}
And then of course there is the test that is troubling me:
package com.stackoverflow.impl;
#ExtendWith(SpringExtension.class)
#SpringBootTest
#ContextConfiguration(classes={RealMapTestConfiguration.class, ProdConfiguration.class})
#ActiveProfiles("bug") // spring.main.allow-bean-definition-overriding=true
public class MapImageServiceIT {
#Autowired
private GameMap map;
}
It turns out that the injected GameMap into my test is a mock instance from TestConfiguration instead of the real thing from RealMapTestConfiguration. Aparrently in my test I have the configuration from ProdConfiguration and TestConfiguration, when I wanted ProdConfiguration and RealMapTestConfiguration. As the beans defined in the ProdConfiguration and *TestConfiguration are different the combination works, but TestConfiguration and RealMapTestConfiguration define the same been. It seems like the TestConfiguration is picked up by component scanning as it is in the same package as ProdConfiguration.
I was under the impression that when overriding beans the bean definition that is closer to the test class would be preferred. However this seems not to be the case.
So here are my questions:
When overriding beans, what is the order? Which bean overrides which one?
How to go about to get the correct instance in my test (using a different bean name is not an option, as in reality the injected bean is not directly used in the test but in a service the test uses and there is no qualifier on it.)
I've not used the spring.main.allow-bean-definition-overriding=true property, but specifying specific config in a test class has worked fine for me as a way of switching between objects in different tests.
You say...
It turns out that the injected GameMap into my test is a mock instance from TestConfiguration instead of the real thing from RealMapTestConfiguration.
But RealMapTestConfiguration does return a mock
package com.stackoverflow.impl;
#Configuration
public class RealMapTestConfiguration {
#Bean
public GameMap gameMap() {
return Mockito.mock(GameMap.class);
}
}
I think the problem here is that including ContextConfiguration nullifies (part of) the effect of #SpringBootTest. #SpringBootTest has the effect of looking for #SpringBootConfiguration in your application (starting from the same package, I believe). However, if ContextConfiguration is applied, then configurations are loaded from there.
Another way of saying that: because you have ContextConfiguration in your test, scanning for #Configuration classes is disabled, and TestConfiguration is not loaded.
I don't think I have a full picture of your configuration setup so can't really recommend a best practice here, but a quick way to fix this is to add TestConfiguration to your ContextConfiguration in your test. Make sure you add it last, so that it overrides the bean definitions in the other two configurations.
The other thing that might work is removing #ContextConfiguration entirely and letting the SpringBootApplication scanning do its thing - that's where what you said about the bean definition that is closest may apply.
In that case just don't use #Configuration on configuration class and import it to the test manually using #Import, example:
#SpringBootTest
#Import(MyTest.MyTestConfig.class)
public class MyTest {
#Autowired
private String string;
#Test
public void myTest() {
System.out.println(string);
}
static class MyTestConfig {
#Bean
public String string() {
return "String";
}
}
}

Where to put #Bean in Spring Boot?

I am wondering what the best place would be for a Spring Boot app to register additional beans. I have a Main class that is annotated with #SpringBootApplication and beans defined in that class are picked up. But when i put those beans in another class it seems that the are not being registered.
When reading the documentation i got the idea that the #SpringBootApplication would implicitly search for classes that have #Bean annotations in them.
So my options are now:
Put all #Bean annotated bean in my main class
#SpringBootApplication
public class MyApplication {
#Bean
public Filter AuthenticationFilter() {
return new AuthenticationFilter();
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Create a configuration class and annotate that with #Configuration
#Configuration
public class MyConfiguration {
#Bean
public Filter AuthenticationFilter() {
return new AuthenticationFilter();
}
}
Is there a better way of doing this?
It is pretty much a matter of preference, but it is generally considered best practice to put exposed beans in configuration classes, which are logically grouped.
For example, you might have several configuration classes with a number of beans contained within each: An authentication configuration class with beans for AuthenticationProvider or UserDetailsService; a Thymeleaf configuration class containing beans for various Thymeleaf dialects, etc.
Actually, it is your choice there is no spring standard present to tell which one is best but while defining a class OOP design principles says A class should be loosely coupled and highly cohesive, should follow Single Responsibility Principle (SRP), Here
Coupling --> Degree of knowledge a class has about another class
Cohesion --> Degree which tells how well focused your class is
SRP --> A class should have only one responsibility, there should be only one reason to change a class.
So according to cohesion and SRP principle class should be well focused and have only one responsibility.
Here in your case you have only 2 beans but in future, these beans might increase. So should follow your second point and create another class for your bean declaration.
And In my choice should even create more configuration classes, So one configuration class should have a similar type of beans.
Yes, including your beans inside the #Configuration class is usually the preferred way for Spring.
This is also one of the ways Spring recommends injecting inter-dependencies between beans is shown in the following sample copied from the Spring's reference guide here:
Additionally, the default scope of #Beans is SINGLETON, if you specify a different scope such as PROTOTYPE the call will be passed to the original method.
Have a look at this section in the Spring Reference Guide
It depends on where the main class is located which has generally #SpringBootApplication annotations. You can annotate this main class with #ComponentScan(basePackageClasses = HelloWorld.class). Here HelloWorld has bean definitions annotated with #Beans and the class is annotated with #Configurations.
Spring container will scan all the sub-packages of the class specified in #ComponentScan arguments. You can also give wild card entries instead of class name.
Ex:
#SpringBootApplication
#ComponentScan(basePackageClasses = HelloWorld.class)
public class DemoApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class);
}
}
**Bean Class:**
#Configuration
public class HelloWorld {
#Bean
public TweetUserSevice tweetUserSevice() {
return new TweetUserSeviceImpl();
}
}
It depends on where the main class is located which has generally #SpringBootApplication annotations. You can annotate this main class with #ComponentScan(basePackageClasses = HelloWorld.class). Here HelloWorld has had definitions annotated with #Beans and the class is annotated with #Configurations.
Spring container will scan all the sub-packages of the class specified in #ComponentScan arguments. You can also give wild card entries for basePackageClasses argument instead of class name as specified above. E.g.
#SpringBootApplication
#ComponentScan(basePackageClasses = HelloWorld.class)
public class DemoApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class);
}
}
Bean Class:
#Configuration
public class HelloWorld {
#Bean
public TweetUserSevice tweetUserSevice() {
return new TweetUserSeviceImpl();
}
}
Another approach:
Generally in big projects, we will have multiple spring config classes containing bean definitions. We can avoid worrying about that all the bean class should be in sub-package of main class. What we can do is that we can have a single master spring config class(but make sure this master spring config class is under sub-package of main class so that #SpringBootApplication annotations automatically detects the master config) and import all the other bean classes.
I have 2 bean classes (TweetBeansConfig, TweetSystemHealthBeansConfig) in the package com.ronak.tweet (This package is not sub-package where main class exists). I have one master spring config class (TweetMasterSpringConfig) and this class resides in package which is sub-package where my main class resides.
package com.ronak.tweet.beans;
#Configuration
#Order(value=1)
#Import({
TweetBeansConfig.class,
TweetSystemHealthBeansConfig.class
})
public class TweetMasterSpringConfig {
public TweetMasterSpringConfig() {
System.out.println("Initilaizing master spring config");
}
}
package com.ronak.beans;
#Configuration
public class TweetBeansConfig {
#Bean
{
//
}
}
package com.ronak.beans;
#Configuration
public class TweetSystemHealthBeansConfig {
#Bean
{
//
}
}
Main class
package com.ronak.tweet;
#SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
/**This is for registing REST layer for Jersey which implements jaxb. It will register all the classes which is in the pacakage com.ronak.tweet.rest. You can add comma separated package names too.
#Bean
ResourceConfig resourceConfig() {
return new ResourceConfig().packages("com.ronak.tweet.rest");
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class);
}
}
It depends on personal choices and there is no good or bad way to do it.
As it is preferred or showed way in documentation of Spring Boot references.
As annotating main class with #SpringBootApplication makes it convenient to Bootstrap spring app. But it does only looks for subpackages so nesting configuration inside subfolder would not detect the #Bean automatically that is the only thing to remember other than it's all about personal preferences.

How to set Spring Active Profiles Pragmatically from a configuration class?

On a project I'm working on we have some old dependencies that define their own spring beans but need to be initialized from the main application. These beans are all constructed using spring profiles, i.e. "default" for production code and "test" for test code. We want to move away from using spring profiles, instead simply using #import to explicitly wire up our context.
The idea is to encapsulate all these old dependencies so that no other components need to care about spring profiles. Thus, from a test`s point of view, the application context setup can be described as follows:
#ContextConfiguration(classes = {TestContext.class})
#RunWith(SpringJUnit4ClassRunner.class)
public class MyTest {
//tests
}
TestContext further directs to two classes, one of which encapsulates the old dependencies:
#Configuration
#Import(value = {OldComponents.class, NewComponents.class})
public class TestContext {
//common spring context
}
To encapsulate the old components` need for profiles, the OldComponents.class looks as follows:
#Configuration
#Import(value = {OldContext1.class, OldContext2.class})
public class OldComponents {
static {
System.setProperty("spring.profiles.active", "test");
}
}
The problem here is that the static block does not appear to be executed in time. When running mvn clean install, the test gets an IllegalStateException because the ApplicationContext could not be loaded. I have verified that the static block gets executed, but it would appear that OldContext1 and OldContext2 (which are profile dependent) are already loaded at this time, which means it is too late.
The frustrating thing is that IntelliJ runs the tests just fine this way. Maven, however, does not. Is there a way to force these profiles while keeping it encapsulated? I've tried creating an intermediary context class, but it didn't solve the problem.
If we use the annotation #ActiveProfiles on the test class, it runs just fine but this kind of defeats the purpose. Naturally, we want to achieve the same in production and this means that if we cannot encapsulate the need for profiles, it needs to be configured in the web.xml.
If your configuration classes inherits of AbstractApplicationContext you can call:
getEnvironment().setActiveProfiles("your_profile");
For example:
public class TestContext extends AnnotationConfigWebApplicationContext {
public TestContext () {
getEnvironment().setActiveProfiles("test");
refresh();
}
}
Hope it helps.
It definietly seems that OldContext1 and OldContext2 are being class-loaded and initialized before the static block in OldComponents is executed.
Whilst I can't explain why there is a difference between your IDE and Maven (to do so would require some in-depth knowledge of some, if not all all, of : spring 3.x context initialization, maven surefire plugin, SpringJunit4ClassRunner and the internal IntelliJ test runner), can I recommend to try this?
#Configuration
#Import(value = {UseTestProfile.class, OldContext1.class, OldContext2.class})
public class OldComponents {
// moved the System.setProperty call to UseTestProfile.class
}
and
#Configuration
public class UseTestProfile {
static {
System.setProperty("spring.profiles.active", "test");
}
}
If I am understanding your problem correctly, class UseTestProfile should be loaded first (you might want to investigate a way to guarantee this?) and the other two classes in the import list should have the system setting they need to initialize properly.
Hope this helps...
You need make sure, environment takes effect at first.This is how I do:
#Component
public class ScheduledIni {
#Autowired
private Environment env;
#PostConstruct
public void inilizetion() {
String mechineName = env.getProperty("MACHINE_NAME");
if ("test".equals(mechineName) || "production".equals(mechineName) {
System.setProperty("spring.profiles.default", "Scheduled");
System.setProperty("spring.profiles.active", "Scheduled");
}
}
}
In scheduler add annotation Prodile and DependsOn to make it work.
#DependsOn("scheduledIni")
#Profile(value = { "Scheduled" })
#Component
Use #profile annotation in the class to load the configuration like below
#Configuration
#Profile("test")
public class UseTestProfile {
}
and set the value for the property spring.profiles.active either in property file or as a runtime argument

Categories

Resources