How can I programmatically configure Spring's #Configuration annotations? - java

Specifically, I would like to be able share configuration classes by instantiating them and including them. Where you would normally do this:
#Configuration
#Import({SharedConfiguration.class})
public class MyAppContext extends WebMvcConfigurerAdapter {
//stuff
}
#Configuration
#ComponentScan("com.example")
public class SharedConfiguration {
//stuff
}
I would like to do this:
#Configuration
public class MyAppContext extends WebMvcConfigurerAdapter {
public SharedConfiguration sharedConfig(){
return new SharedConfiguration("com.example");
}
//stuff
}
#Configuration
public class SharedConfiguration {
public SharedConfiguration(String package){
//tell Spring to scan package
}
}
The reason for this is that I need to be able to tell the shared component doing the scan what package to look at. It will different depending on what project it is being used in.
EDIT:
To provide some additional context, I'm trying to make a general-use configuration for setting up Hibernate and EHCache using our external configuration provider that several projects can use. I'm certainly open to other methods to doing this, but this seemed like the most logical path to me. I'm sure there's ~something~ in Spring that I can fiddle with to say, "Here! Scan this path when Spring initializes you!" instead of hard-coding it into an annotation.

You can take advantage of property sources in this case.
In the test case, I am setting a system property that is picked up by the Spring property source configuration -
#RunWith(SpringRunner.class)
#ContextConfiguration
public class MyAppContextTest {
#Autowired
ApplicationContext context;
#BeforeClass
public static void beforeClass() {
// use a system property to configure the component scan location of the SharedConfiguration
// where the "ExampleBean" lives
System.setProperty("packages", "net.savantly.other.packages");
}
#Test
public void ensureExampleBeanExists() {
// throws exception if it doesnt exist
context.getBean(ExampleBean.class);
}
#Configuration
#Import(MyAppContext.class)
static class TestContext {
}
}
Using the Spring expression language in the ComponentScan -
#Configuration
#ComponentScan("${packages}")
public class SharedConfiguration {}
Other Referenced Classes -
#Configuration
#Import(SharedConfiguration.class)
public class MyAppContext extends WebMvcConfigurerAdapter {
#Autowired
SharedConfiguration sharedConfig;
//stuff
}
#Service
public class ExampleBean {
}

Related

Spring specify implementation in configuration

I'm writing application using spring mvc/boot, and I have two storage implementations: database storage and in memory storage. My global idea is choose in configuration file what storage application should use.
My idea is
put #Qualifier annotation on each storage implementation
create two configurations, like databaseStorageConfiguration and InMemoryStorageConfiguration
depends on profile, apply first or second configuration
The thing is I don't know how to bind implementation and configuration.
I tried something like this:
#Configuration
public class InMemoryStorageConfig {
#Autowired
#Qualifier("inMemoryStorage")
private Storage storage;
#Bean
public Storage getStorage() {
return storage;
}
}
But I get an error, that 3 beans were found: 2 beans with dfferent implementation and the 3rd one - in config
UPDATE 1
I've added #Profile("InMemory") to Configuration and activated that profile in properties. That gave no changes but looks more logical now
UPDATE 2
Full configuration:
#SpringBootApplication
#ImportResource("classpath:spring-config.xml")
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
}
#Service
public class WidgetService {
private WidgetCache widgetCache;
#Autowired
public WidgetService(WidgetCache widgetCache) {
this.widgetCache = widgetCache;
}
....
#Qualifier("databaseWidgetCache")
#Transactional
#Repository
public class DatabaseWidgetCache implements WidgetCache {
private WidgetRepository widgetRepository;
#Autowired
public DatabaseWidgetCache(WidgetRepository widgetRepository) {
this.widgetRepository = widgetRepository;
}
#Qualifier("inMemoryWidgetCache")
#Repository
public class InMemoryWidgetCache implements WidgetCache {
private WidgetLayersStorage widgetLayersStorage;
#Autowired
public InMemoryWidgetCache(WidgetLayersStorage widgetLayersStorage) {
this.widgetLayersStorage = widgetLayersStorage;
}
#Profile("InMemory")
#Configuration
public class InMemoryStorageConfig {
#Autowired
#Qualifier("inMemoryWidgetCache")
private WidgetCache widgetCache;
#Bean
public WidgetCache getWidgetCache() {
return widgetCache;
}
}
Stacktrace:
Parameter 0 of constructor in
com.widgets.service.widget.WidgetService required a single
bean, but 3 were found:
- inMemoryWidgetCache: defined in file [..../MemoryWidgetCache.class]
- databaseWidgetCache: defined in file [..../DatabaseWidgetCache.class]
- getWidgetCache: defined by method 'getWidgetCache' in class path resource
[......../InMemoryStorageConfig.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer
to accept multiple beans, or using #Qualifier to identify the bean
that should be consumed
Your WidgetService should be changed to
#Service
public class WidgetService {
private WidgetCache widgetCache;
/** or
private List<WidgetCache> widgetCaches;
public WidgetService(List<WidgetCache> widgetCaches) {
this.widgetCaches = widgetCaches;
}
*/
public WidgetService(#Qualifier(<desired impl>) WidgetCache widgetCache) {
this.widgetCache = widgetCache;
}
}
and need to annotate your InMemoryWidgetCache and DatabaseWidgetCache with #Qualifier annotation. since you are using default convention.
and please remove
#Bean
public WidgetCache getWidgetCache() {
return widgetCache;
}
i don't see a real use there
In order to specify implementation in Configuration class, you don't need "Qualifier" annotation, and configuration should be changed to:
#Profile("inMemoryStorage")
#Import(InMemoryWidgetCache.class)
#Configuration
public class InMemoryStorageConfig {
}
thus, by activating profile, you choose the desire implementation

Spring Boot test with yaml properties by profile

So there's a lot of hits on this topic, but none of them have worked for me.
I have a very simple configuration class:
#Configuration
#ConfigurationProperties(prefix = "props")
public class TagIncluder {
private static final String PARAMETER_NAME = "tags";
private List<String> tags;
public TagIncluder() {
tags = new ArrayList<>();
}
public List<String> getTags() {
return tags;
}
#Handler
public void attachIncludedTags(Exchange exchange) {
exchange.getIn().setHeader(PARAMETER_NAME, tags);
}
}
I want this class to be able to load different property files. I am using yaml, and my file is named application-tag_test.yml. I have tried placing this file in src/main/resources, src/test/resources and src/test/resources/config, but it is never picked up.
This is the contents of the property file:
props:
tags:
- test
And finally, the test case:
#SpringBootTest
#ActiveProfiles("tag_test")
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TagIncluder.class)
public class TagIncluderTest extends ExchangeTestSupport {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
Exchange testExchange = createExchange();
sut.attachIncludedTags(testExchange);
Assertions.assertThat(testExchange.getIn().getHeader("tags", List.class))
.size().isGreaterThanOrEqualTo(1);
}
}
Additionally, I have tried placing an application.properties file in the above described locations with the following content:
spring.profiles.active=tag_test
What is required for Spring to set my yaml file as the desired configuration for my test class under test?
UPDATE
So after some exploration and trial and error, I have found that the following works:
#SpringBootTest
#ActiveProfiles("tag_test")
#RunWith(SpringJUnit4ClassRunner.class)
public class TagIncluderTest extends ExchangeTestSupport {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
Exchange testExchange = createExchange();
sut.attachIncludedTags(testExchange);
Assertions.assertThat(testExchange.getIn().getHeader("tags", List.class))
.size().isGreaterThanOrEqualTo(1);
}
}
The difference here is that I've removed the #ContextConfiguration annotation and I let Spring take care of all of that.
It is a lot slower, and I would prefer specifying what is needed. I think this might break in the future, for instance if I add another configuration class that will start with the entire context and throw errors because those properties are not included in my application-tag_test.yml configuration.
Finally, any of the above locations I tried for the configuration is valid with the above annotations. The application.properties to specify a profile is not needed.
If anyone knows a way to specify what should be loaded into the context instead, I'd be very grateful for another solution.
With some guidance of Jans suggestion above, I've managed to isolate the test to a slice. Auto configured testing is written about here, however that only touches on Springs predefined #..Test annotations.
If you dive deeper into the #WebMvcTest, for instance, you will find the #ImportAutoConfiguration annotation.
Using this, we can tell our test class to enable auto configuration for a single slice of our application. A tutorial is available here. The full list of factories available for auto configuration can be found in the spring-boot repository.
Finally, this is the entire test class:
#ActiveProfiles("tag_test")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = TagIncluder.class)
#ImportAutoConfiguration(classes = ConfigurationPropertiesAutoConfiguration.class)
public class TagIncluderTest extends ExchangeTestSupport {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
Exchange testExchange = createExchange();
sut.attachIncludedTags(testExchange);
Assertions.assertThat(testExchange.getIn().getHeader("tags", List.class))
.size().isGreaterThanOrEqualTo(1);
}
}
The class under test is untouched.
So now we can:
Use profiles
Use yaml
Test only our desired class in Spring Context
This has been very enlightening :)
The Spring Boot Test documentations states that
External properties, logging, and other features of Spring Boot are installed in the context by default only if you use SpringApplication to create it.
This means that you need to have a working Spring Boot Application in order to test anything related to property loading in a test case.
Also, setting a list from properties needs a setter. This works:
#Configuration
#ConfigurationProperties(prefix = "props")
public class TagIncluder {
private List<String> tags;
public void setTags(List<String> tags) {
this.tags = tags;
}
public List<String> getTags() {
return tags;
}
}
#Component
public class MyComponent {
#Autowired
TagIncluder tagIncluder;
}
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#SpringBootTest
#ActiveProfiles("tag_test")
#RunWith(SpringRunner.class)
public class TagIncluderTest {
#Autowired
private TagIncluder sut;
#Test
public void attachIncludedTags_shouldUseTagsInFileIfFileSpecified() {
System.out.println(sut.getTags());
}
}

Spring inherited #Component with constructor arguments

I have a service which needs to create Agents on the runtime. Agents inherit from a base Agent class. I would like to use the Autowired ability of spring instead of doing my own dependency injections.
But I am running into this issue, even though I am marking the component as scope=prototype, and even #Lazy to prevent anything from happening at compile-time.
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.my.project.AgentType1 required a bean of type 'com.my.project.POJO' that could not be found.
This is the service that tries to create the agents:
#Service
public class ProjectMain {
#Autowired
ApplicationContext context;
List<IAgent> agents = new ArrayList<>();
void SetupAgents(List<POJO> agentPojos) {
for(POJO agentPojo: agentPojos) {
IAgent agent = AgentFactory.CreateAgent(agentPojo, context);
agents.add(agent);
}
}
}
This is the factory class, not marked as #Component etc. It uses the context passed to it to create the child class beans. It tries to pass the constructor argument via the getBean method.
public class AgentFactory {
public static IAgent CreateAgent(POJO agentPojo, ApplicationContext context) {
if (agentPojo.type.equals("AgentType1")) {
return context.getBean(AgentType1.class, agentPojo);
} else {
return context.getBean(AgentType2.class, agentPojo);
}
}
}
This is a custom annotation which I found is needed for inheritance scenarios.
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Component
#Inherited
#Lazy
#Scope("prototype")
public #interface AgentAnnotation {}
These are the base and child agent classes, which need a custom data structure called POJO to work.
#AgentAnnotation
public class BaseAgent implements IAgent {
#Autowired
Environment env;
public BaseAgent(POJO agentPojo, String someotherdata) {
}
}
public class AgentType1 extends BaseAgent {
public AgentType1(POJO agentPojo) {
super(agentPojo, "mydata1");
...
}
}
public class AgentType2 extends BaseAgent {
public AgentType2(POJO agentPojo) {
super(agentPojo, "mydata2");
...
}
}
This is the starter app.
#ComponentScan(basePackages = "com.my.project", includeFilters = #ComponentScan.Filter(AgentAnnotation.class))
#EnableScheduling
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
I also tried the configuration approach:
#Configuration
public class BaseAgentConfig {
#Bean
#Scope("prototype")
public AgentType1 agentType1(POJO agentPojo) {
return new AgentType1(agentPojo);
}
#Bean
#Scope("prototype")
public AgentType2 agentType2(POJO agentPojo) {
return new AgentType2(agentPojo);
}
}
In this case, I removed the #AgentAnnotation from the baseAgent class as we are now instantiating through this config. Also removed the ComponentScan line from the main App.
This time around, the #Autowired doesn't work. All Autowired references in the baseAgent class are null.
Please advise on the best approach to solve this error. Thanks.
Found the issue and solution.
Basically, I was expecting child classes to inherit #Component and #Scope, which it doesn't.
So essentially, I need to annotate each child class with #Component and #Scope("prototype").
The other problem was that I was expecting Autowired items in the constructor, which was too early. Adding a #PostConstruct addressed that issue.
So I ended up deleting the custom annotation and the configuration class and making the changes I just described.

#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