I have a SpringBoot app that has been working fine:
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class },
scanBasePackageClasses = { IndexSyncController.class, IndexerService.class })
#ImportResource("classpath:/spring.xml")
public class AppLauncher {
public static void main(String[] args) {
SpringApplication.run(AppLauncher.class, args);
}
And also currently sitting in src/test/java, I have a "utility" which has its own main method. This too has been working fine, but can only launched in my development environment:
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
WebMvcAutoConfiguration.class })
public class AuditApp {
public static void main(String[] args) {
SpringApplication.run(AppLauncher.class, args);
}
#Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
...
};
}
Now I have a new requirement
I need to take the AuditApp utility from src/test/java and make it available in the production jar.
So I just move it from src/test/java to src/main/java right? Nope! Very first obstacle I ran into when I do that, is that the main application tries to run the commandLineRunner(). Because of past experience with a similar requirement, I believe this is just the tip of the iceberg and there will be additional issues.
Is there a canonical, general best-practice or checklist for how to accomplish this?
The first thing to consider is the real reason of your problem.
You missed out packages of mentioned classes, but looking at your symptoms, I suspect your AuditApp is in the same package, or in a subpackage of one the AppLauncher is in (or is in package scanned by config declared inside imported spring.xml).
The #SpringBootApplication is an abbreviation for (amongst other) #SpringBootConfiguration and #ComponentScan.
Because of this the AppLauncher treats AuditApp as a #SpringBootConfiguration on a package scan and instantiates beans created within this config. The AuditApp.main method is never called, but the beans declared in the config are instantianted.
Let's say you have AppLauncher in com.yourapp and AuditApp in com.yourapp.audit.
If you move your utility application to the package which is not under the package scan for AppLauncher, like com.yourauditapp. The CommandLineRunner bean won't be created when running the original application. And the AuditApp won't affect the original application.
The only other way it can interfere is when it introduces dependencies which may trigger some Spring-Boot autoconfigurations.
Related
I am very new to Spring Boot and development, so, I am stuck with a problem. I have an old project that needs to be migrated to Spring Boot. The original main method has super cool multi-threaded logic. In my understanding public static void main(String[] args) was entry point to the program, now after creating Spring Boot project #springbootapplication is entry point. How to access the original logic of a method? Should it somehow be transform? I spent hours looking for a suitable solution but no luck. Could you point me? Any help is appreciated :)
You have to use #SpringBootApplication, but also need to modify the main method something like:
#SpringBootApplication
public class YourMainApplicationClass {
public static void main(String[] args) {
SpringApplication.run(YourMainApplicationClass.class, args);
}
}
This will start your application.
Then move your original code of your main method to a new class, which has annotation #Component. Implement CommandLineRunner, and override the run method. So something like:
#Component
public class YourOldMainClass implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
//Your code here
}
}
When your application starts, spring will load 'near everything' with annotation into its container, so your #Component annotated class should be also loaded. The CommandLineRunner with overrode run method will auto call your method at startup.
Also, don't forget to include necessary spring boot jars next to your project, or to your build automation tool - like Maven - if you use it.
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();
}
}
It's my first question ever on stackoverflow.com.
Lately I have been looking for a way to change how Spring Boot (2.x) names its beans, created via #Component (etc) annotation.
I have found the SpringApplicationBuilder class, which thankfully allows to inject custom bean name generator:
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.beanNameGenerator(customBeanNameGenerator)
.run(args);
}
}
But the problem is, despite the fact that this solution works for production, this does not hovewer work in JUnit 4.0 unit tests, where JUnit Runner SpringRunner recreates Spring Context for testing purposes - in that case SpringApplicationBuilder is not used whatsoever, and as a result our application startup fails, becouse of autowiring multi-beans candidates problems.
#RunWith(SpringRunner.class)
#SpringBootTest(classes= {Application.class} )
public class SomeControllerTest {
...
}
Is there any elegant way to make that unit tests use SpringApplicationBuilder as well?
Regards.
the property "classes" of #SpringBootApplication only use annotations of the classes. therefore the code for building SpringApplication is not work when run the tests.
if you want to fix this problem, you can try other ways such as specify the nameGenerator in #ComponentScan
#ComponentScan(nameGenerator = CustomBeanNameGenerator.class)
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.run(args);
}
}
ensure you class CustomBeanNameGenerator has an empty constructor.
ps: english is not my native language; please excuse typing errors.
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.
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