How to externalize #SpringBootApplication configuration? - java

I want to externalize the #SpringBootApplication(exclude...) option, to have a reusable class or annotation that I could throw in to exclude any database/hibernate initialization.
So, instead of writing:
#SpringBootApplication(
exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class})
public class MainApp {
}
I would like to create a annotation that I could apply to my #SpringBootApplication main class:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class})
#Configuration
public #interface ExcludeDataSources {
}
And then enable this feature by annotation:
#SpringBootApplication
#ExcludeDataSources
public class MainApp {
}
Problem: the annotation approach does not work, and spring still tries to load a database. Why?
My final goal is to have multiple startup classes, where only one loads the database.

I could manage it by adding an additional #EnableAutoConfiguration that is only executed on a certain condition.
This way I can dynamically exclude the database config, while keeping a clean basis main #SpringBootConfiguration class.
public class DataSourceConfig {
#Configuration
#Conditional(MyCondition.class)
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class})
static class ExcludeDataSource {
}
}

Related

Spring JUnit5 test not loading resource values

I know there are a lot of questions regarding this, but all of them are suggesting to use #TestPropertySource and #EnableConfigurationProperties. I have already used them but still not working.
Config class - src/main/java/com/demo/config/AppConfig.java
#Configuration
#ConfigurationProperties(prefix = "api")
#Getter
#Setter
public class AppConfig {
private List<String> providers;
private boolean enabled;
}
Property source - src/test/resources/application-test.yml
api:
enabled: true
providers:
- prov1
- prov2
Test class - src/test/../MyTest.java
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = MyTestConfiguration.class)
class MyTest {
#Autowired
private AppConfig appConfig;
#Test
void runTest() {//some code with breakpoint}
}
Test configuraton - src/test/.../MyTestConfiguration.java
#TestConfiguration
#TestPropertySource(locations = "classpath:application-test.yml")
#EnableConfigurationProperties(value = AppConfig.class)
#ActiveProfiles("test")
public class MyTestConfiguration {
}
When I run the test, runTest() and inspect autowired appConfig value, the providers are empty and enabled is false. That means the values in yml file were not loaded.
I found a similar kind of question, but without answer.
I modified MyTest as #sergey-tsypanov suggested and then deleted MyTestConfiguration class. It worked and appConfig has values.
#SpringBootTest(classes = AppConfig.class)
#EnableAutoConfiguration
#ActiveProfiles("test")
class MyTest {
#Autowired
private AppConfig appConfig;
#Test
void runTest() {//some code with breakpoint}
}
It seems even if I don't have #SpringBootApplication, I can use #SpringBootTest and #EnableAutoConfiguration. I had spring Boot dependency in pom.xml
I think you need to replace
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = MyTestConfiguration.class)
with
#SpringBootTest(classes = {MyTestConfiguration.class})
Then application-test.yml will be picked up automatically.

SpringBoot #WebMvcTest is loading non-dependent beans when #ComponentScan is used along with #SpringBootApplication

I am using SpringBoot 2.3.3.RELEASE and I have following web controllers and Services.
myapp
- controllers
- ProductController
- OrderController
- services
- ProductService
- OrderService
ProductController only depends on ProductService and OrderController only depends on OrderService.
Following is my SpringBoot main entrypoint class:
package com.sivalabs.myapp;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
I have an #WebMvcTest controller for testing ProductController as follows:
#WebMvcTest(controllers = ProductController.class)
class ProductControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private ProductService productService;
//some tests
}
Everything works perfectly fine with this configuration.
I am trying to use some external library with Spring components which has different package name, so I want to override #ComponentScan as follows:
package com.sivalabs.myapp;
import com.somelib.BeanConfig;
#SpringBootApplication
#ComponentScan(basePackageClasses = {Application.class, BeanConfig.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
When I include #ComponentScan on my main entrypoint class and run my #WebMvcTest based test ProductControllerTest then in addition to ProductService SpringBoot is trying to initialise OrderService also. Ideally ProductControllerTest should not load OrderService as ProductController doesn't depend on OrderService. Is it a bug?
Workarounds:
If I use #ComponentScan the way it is used on #SpringBootApplication meta-annotation and include basePackageClasses it is working fine.
package com.sivalabs.myapp;
#SpringBootApplication
#ComponentScan(excludeFilters = { #Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), #Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) },
basePackageClasses = {Application.class, BeanConfig.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Instead of adding #ComponentScan on main entrypoint class if I add another configuration class and add #ComponentScan on that class then it's working fine.
package com.sivalabs.myapp.config;
#Configuration
#ComponentScan(basePackageClasses = {BeanConfig.class})
public class AppConfig {
}
Is it a bug in component scanning process or is it working as expected?
It working as expected.
#SpringBootAplication already have #ComponentScan with filters, and redeclare it as in workaroud #1 doesn't make sense, right?
Problem With original code is that #ComponentScan overrides exclude filters applied inside #SpringBootApplocation annotation. And stuff is not excluded, so when #WebMvc tries to request part of context using filters it fails, and entire context is loaded.
Just check what beans are initialized for context in each scenario. And stick to solution 2. #SpringBootTest will scan packages where application class is places, and any additional packages should be scanned by configurations. That is even more flixible right? :)

importing spring configuration and exclude filters

I have a spring context that does #ComponentScan.
I'd like to add a variation of my configuration that excludes certain beans either by package name or a regex. I am aware of using #Import to import other configurations.
But, could I import a configuration and override its component scannign by doing an exclusion filter? Will this work?
#Configuration
#ComponentScan(value = { "my.app.pack1", "my.app.pack2" })
public class ClientApplicationConfig {}
#Configuration
#ComponentScan(value = { "my.app.pack1" },
excludeFilters = #ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "my.app.pack1.ignore.*"))
public class DifferentClientAppConfig {}
Yes you can do it. For instance, try the following:
#Configuration
#ComponentScan(value={"my.app.pack1"},excludeFilters=#ComponentScan.Filter(type=FilterType.CUSTOM,value=MainConfig.WebExclude.class))
public class MainConfig
{
public static class WebExclude extends RegexPatternTypeFilter{
public WebExclude(Pattern aPattern)
{
super(Pattern.compile("my.app.pack1\\.ignore"));
}
...
You can give it whatever regex you want. This will component scan my.app.pack1 excluding my.app.pack1.ignore.*

#ComponentScan doesn't work in Spring boot AutoConfiguration class?

I am trying to create a new starter. I have a business module, say ProjectManager, that contains some classes annotated with #Component. Following the tutorial, I created an autoconfigure module, it contains an AutoConfiguration class. Firstly, I tried to use #ComponentSan to find the beans in my business module.
#ComponentScan(value = {"com.foo.project"})
#ConditionalOnClass({Project.class})
#Configuration
public class ProjectAutoConfiguration {
....
}
But it doesn't work. I have to add additional configuration class as below:
#Configuration
#ComponentScan(value = {"com.foo.project"})
#MapperScan(value = {"com.foo.project"})
public class ProjectConfig {
}
And then import it into AutoConfiguration class like below:
#Import(ProjectConfig.class)
#ConditionalOnClass({Project.class})
#Configuration
public class ProjectAutoConfiguration {
....
}
That works. But according to the spring doc.
auto-configuration is implemented with standard #Configuration classes
So my question is, Why #ComponentScan doesn't work here ? Did I make something wrong? Or it is by design ?
you have to use the compentscan annotation into the main class. Here a sample code:
#SpringBootApplication
#ComponentScan("com.foo.project")
public class MainApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MainApplication.class);
}
public static void main(String[] args) {
new MainApplication().configure(new SpringApplicationBuilder(MainApplication.class)).run(args);
}
}
Cheers
Automatic everything requires the Application class (annotated with #SpringBootApplication) to be in a "higher" package than the components you want to scan.
Use:
package com.example.foo;
for your application and put components in a package like:
package com.example.foo.entities;
See also https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-using-springbootapplication-annotation.html
Can you try with following?
#ConditionalOnClass({Project.class})
#Configuration
#EnableAutoConfiguration
#ComponentScan(value = {"com.foo.project"})
public class ProjectAutoConfiguration {
....
}
I was developing a SDK project. It needs the application which depends on the SDK to scan for beans beneath specific package in the SDK during start period.
Anotate with #ComponentScan on autowire configuration class doesn't take effect.
Then I am trying to use #Import annotation to import a class implemented interface ImportBeanDefinitionRegistrar(Interface to be implemented by types that register additional bean definitions when processing #Configuration classes. Useful when operating at the bean definition level (as opposed to #Bean method/instance level) is desired or necessary).
Within ImportBeanDefinitionRegistrar implementation class, I register a class annotated with #ComponentScan as bean. Run application again, it works as expected.
Codes below:
AutoConfiguration Class:
#Configuration
#Import(TestConfigRegistar.Registrar.class)
public class TestClientAutoCofiguration {
}
Registar class:
public class TestConfigRegistar {
public static class Registrar implements ImportBeanDefinitionRegistrar {
private static final String BEAN_NAME = "componentScanConfig";
#Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata,
BeanDefinitionRegistry registry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(ComponentScanConfig.class);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
}
}
}
Class with #ComponentScan annotation
// Leave an empty value of #ComponentScan will let spring scan
// current class package and all sub-packages
#ComponentScan
public class ComponentScanConfig {
}
I believe that the point is that beans annotated with #ComponentScan must be defined at definition level (as opposed to #Bean method/instance level). Please correct me if I'm wrong, Thanks.

#ComponentScan not working in test with spring-boot-starter-test

I am attempting to test my #Service and #Repository classes in my project with spring-boot-starter-test and #Autowired is not working for the classes I'm testing.
Unit test:
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes = HelloWorldConfiguration.class
//#SpringApplicationConfiguration(classes = HelloWorldRs.class)
//#ComponentScan(basePackages = {"com.me.sbworkshop", "com.me.sbworkshop.service"})
//#ConfigurationProperties("helloworld")
//#EnableAutoConfiguration
//#ActiveProfiles("test")
// THIS CLASS IS IN src/test/java/ AND BUILDS INTO target/test-classes
public class HelloWorldTest {
#Autowired
HelloWorldMessageService helloWorldMessageService;
public static final String EXPECTED = "je pense donc je suis-TESTING123";
#Test
public void testGetMessage() {
String result = helloWorldMessageService.getMessage();
Assert.assertEquals(EXPECTED, result);
}
}
Service:
#Service
#ConfigurationProperties("helloworld")
// THIS CLASS IS IN /src/main/java AND BUILDS INTO target/classes
public class HelloWorldMessageService {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message=message;
}
}
The commented class annotations on the unit test represent the various things I've tried to get this working. The test and the project packages are in the same package paths and the #ComponentScan works fine from my entry point (#RestController class with main method). The service #ComponentScan's and #Autowire's fine in my #RestController class in the src/main/java side, but does not in the test. I am required to add it again as a #Bean in my #Configuration class in order for #Autowired to work. The class is otherwise in scope just fine and I can reference and instantiate it just fine from the test. The problem appears to be that #ComponentScan does not appear to correctly traverse multiple entries in my test runner classpath, in this case /target/test-classes and /target/classes.
The IDE I am using is IntelliJ IDEA 13.
UPDATE - here are HelloWorldRs and its config:
#RestController
#EnableAutoConfiguration
#ComponentScan
public class HelloWorldRs {
// SPRING BOOT ENTRY POINT - main() method
public static void main(String[] args) {
SpringApplication.run(HelloWorldRs.class);
}
#Autowired
HelloWorldMessageService helloWorldMessageService;
#RequestMapping("/helloWorld")
public String helloWorld() {
return helloWorldMessageService.getMessage();
}
}
...
#Configuration
public class HelloWorldConfiguration {
#Bean
public Map<String, String> map() {
return new HashMap<>();
}
// This bean was manually added as a workaround to the #ComponentScan problem
#Bean
public HelloWorldMessageService helloWorldMessageService() {
return new HelloWorldMessageService();
}
// This bean was manually added as a workaround to the #ComponentScan problem
#Bean
public HelloWorldRs helloWorldRs() {
return new HelloWorldRs();
}
}
First, I'd recommend to use a newer #RunWith(SpringRunner.class) but that makes no difference, it is just shorter (and recommended).
Second, from the #EnableAutoConfiguration I see that you are using spring boot - which is certainly a good thing. There are some good reasons why not to use #ComponentScan directly. Can you try the following?
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes=YourApplication_or_other_Configuration.class)
public class HelloWorldTest {
... etc.
I don't know if this will turn out to be the solution, but don't use the default package (i.e. don't put *.java in "src/main/java" directly), and definitely don't use a #ComponentScan or #EnableAutoConfiguration in the default package. You will end up killing your application on startup as it tries to scan everything on the classpath (including all the Spring libraries).
SpringBoot 2.7.3, JUnit 5.8.2
If you want to have full control about the spring's configuration (and not rely on the hidden magic of auto configuration) I suggest to create an explicit configuration class:
#ComponentScan(basePackages = { "my.package.to.scan" })
public class MySpringTestConfig
{
// just for spring configuration annotations
}
and reference it in your test class:
#ContextConfiguration(classes = { MySpringTestConfig.class })
#ExtendWith({ SpringExtension.class })
class MySpringTest
{
...
}

Categories