I have a hard time understanding how application context work in multi module project.
Lets say I have a WAR Application "App". App has Jars A, B and C. A has a pom dependency on B and C has pom dependency on B. So can the beans defined in A be used in C as they are packaged in WAR file and are loaded at run time.
The application-context files of A, B and C are imported in WAR application.
Yes it is possible. Here is why:
Simply because Spring is able to scan the classpath of the your application (if you are using the java annotation, #ComponentScan('com') will load every class you have in the package com.**).
If you are only running your JAR C with its dependencies, and you are loading every class in a particular package for instance, then you will be fine, you won't have any surprises.
The same if you do A and B.
But if you use A, B and C, and you have Spring scanning your packages (some common packages between A and C for instance), then you can have beans declared by A and beans declared by B. Which could some error if you thought you were having only one bean of type Datasource, by you have one in A and one in C.
Here is an example:
A has a DataSource.
package com.my.datasource;
#Configuration
public class MyConfigurationA() {
#Bean
public DataSource datasource() {
//return new datasource...
}
}
C has a DataSource.
package com.my.datasource;
#Configuration
public class MyConfigurationC() {
#Bean
public DataSource datasource() {
//return new datasource...
}
}
And B declared a JdbcTemplate :
package com.my.template;
#Configuration
public class MyConfigurationB() {
#Bean
public JdbcTemplate template(DataSource ds) {
return new JdbcTemplate(ds);
}
}
If you run:
- C + B -> 1 DataSource + 1 JdbcTemplate. OK !
- A + B -> 1 DataSource + 1 JdbcTemplate. OK !
- A + C + B -> -> 2 DataSources + 1 JdbcTemplate. KO !
This situation will happen if the WAR you deploy also has a #ComponentScan("com.my").
So yes, it is possible to have access of bean defined in A in C, but this can be like hidden dependencies, you code is unclear, the execution is uncertain and could fail like succeed without you knowing what is going on.
Is it clearer ?
Related
I have created a myApp.properties in resources folder location and mentioned the server.port in this file.
myApp.properties
myApp.server.port=8020
Now I want to read load this property into my application. But I have to read this before I actually a server.
Here I am trying to do like this
#SpringBootApplication
#ComponentScan(basePackages = {"com.myorg.myapp" })
#EnableConfigurationProperties
#PropertySource("classpath:myApp.properties")
#Component
public class MyAppApplication {
#Value("${myApp.server.port}")
private static String serverPort;
public static void main(String[] args) throws Exception{
try {
SpringApplication appCtxt = new SpringApplication(MyAppApplication.class);
appCtxt.setDefaultProperties(Collections
.singletonMap("server.port", serverPort));
appCtxt.run(args);
} catch (Exception e) {
e.printStackTrace();
}
}
But serverPort is coming as null.
I also tried to create a separate Config file like this but it can't be accessed in static main
#Configuration
#PropertySource("myApp.properties")
#ConfigurationProperties
public class MyAppConfig {
#Value("${myApp.server.port}")
private String serverPort;
/**
* #return the serverPort
*/
public String getServerPort() {
return serverPort;
}
}
Any suggestion would be helpful.
Spring boot injects properties during the initialization of the application context.
This happens (gets triggered) in the line:
appCtxt.run(args);
But you try to access the property before this line - that why it doesn't work.
So bottom line, using "#Value" in the main method doesn't work and it shouldn't.
Now from the code snippet, it looks like you could merely follow the "standards" of spring boot and create the file application.properties with:
server.port=1234
The process of starting the embedded web server in spring boot honors this property and bottom line it will have the same effect and Tomcat will be started on port 1234
Update 1
Based on OP's comment:
So, how can I have multiple application.properties.
In the Spring Boot's documentation it is written that application.properties are resolved from the classpath. So you can try the following assuming you have different modules A,B,C and web app D:
Create src/main/resources/application.properties inside each of 4 modules and pack everything together. The configuration values will be merged (hopefully they won't clash)
If you insist on naming properties A.properties, B.properties and C.properties for each of non-web modules, you can do the following (I'll show for module A, but B and C can do the same).
#Configuration
#PropertySource("classpath:A.properties")
public class AConfiguration {
}
Create in Module A: src/main/resources/A.properties
If you need to load the AConfiguration automatically - make the module A starter (using autoconfig feature of spring-boot):
Create src/resources/META-INF/spring.factories file with the following content:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
<package_of_AConfiguration>.AConfiguration
Also this has been the requirement to separate C from entire bundle where it might run as bundle for some and as a separate for some others
Although I haven't totally understood the requirement, but you can use #ConditionalOnProperty for configuration CConfiguration (that will be created just like AConfiguration.java in my previous example) but this times for module C.
If the conditional is met, configuration will run and load some beans / load its own properties or whatever. All in all conditionals (and in particular Profiles in spring) can help to reach the desired flexibility.
By default, the application.properties file can be used to store property pairs, though you can also define any number of additional property files.
If you save myApp.server.port=8020 in application.properties, it will work fine.
To register a custome property file, you can annotate a #Configuration class with the additional #PropertySource annotation:
#Configuration
#PropertySource("classpath:custom.properties")
#PropertySource("classpath:another.properties")
public class ConfigClass {
// Configuration
}
make sure, your class path is correct.
I am doning an unit test which require some objects which injected by spring so I use:
#RunWith(SpringRunner.class)
#Import({BConfig.class})
public class ATest {
private A a;
#Autowired
private B b;
#Before
public void init() {
a = new A(b);
}
}
However, the BConfig class has an autowired field c which I don' need in this test:
class BConfig {
#Autowired
C c;
#Bean
public getB() {
return new B();
}
// other method code will use field c
}
The autowired c field will get data from redis in #PostConstruct which don't exist in unit test. If I don't omit that, the unit test will report error due to redis data is not exist.
I have a solution to make C to 2 subclasses CProduction and CUnitTest both of them implements interface C, then active profile to use CUnitTest in unit test. However this is some kind of invasive because if don't do the unit test, the interface of C is useless.
Is there a better way to do this?
Consider using:
#RunWith(SpringRunner.class)
#ContextConfiguration({BConfig.class})
class ATest {
#MockBean // will place a mock implementation of C to the context instead of real one that tries to connect with Redis.
C c;
...
}
Note that it will work seamlessly if C is an Interface. Otherwise it will try to create a proxy by inheritance (something that extends from C) and if C has some redis-related code in constructor it might still attempt to call it.
Update 1
Based on OP's comment in an attempt to clarify the answer:
When spring application context starts it basically resolves all the beans and injects what's needed.
First it resolves the bean definitions (metadata) - the real class, scope, etc.
And then it creates beans in the order that will allow injection. For example, it class A has a field of class B (both are beans) then spring must create B first, then create a and inject B into A.
Now, #MockBean is a hook relevant for tests. It tells spring application context used in test that instead of a regular bean definition that spring is able to parse out of #Configuration classes or find the component due to #Component annotation placed on it, it should use the Mock - something that is generated in runtime with frameworks like Mockito.
This mock implementation can later be used to specify the expectations (see Mockito basic tutorial, there are plenty of those on internet), but what's more important for your case, it wont connect to redis because this mock implementation doesn't have any redis related code.
Update 2
The configuration should be rewritten as follows:
#Configuration
public class BConfig {
#Bean
public getB() {
return new B();
}
#Bean
public C c() {
return new C();
}
// other method code will use field c:
// if for example D uses C: then you can:
#Bean
public D d(C c) {
return new D(c);
}
}
I'm trying to set up a project with two data sources, one is MongoDB and the other is Postgres. I have repositories for each data source in different packages and I annotated my main class as follows:
#Import({MongoDBConfiguration.class, PostgresDBConfiguration.class})
#SpringBootApplication(exclude = {
MongoRepositoriesAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class
})
public class TemporaryRunner implements CommandLineRunner {
...
}
MongoDBConfiguration:
#Configuration
#EnableMongoRepositories(basePackages = {
"com.example.datastore.mongo",
"com.atlassian.connect.spring"})
public class MongoDBConfiguration {
...
}
PostgresDBConfiguration:
#Configuration
#EnableJpaRepositories(basePackages = {
"com.example.datastore.postgres"
})
public class PostgresDBConfiguration {
...
}
And even though I specified the base packages as described in documentation, I still get those messages in the console:
13:10:44.238 [main] [] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode!
13:10:44.266 [main] [] INFO o.s.d.r.c.RepositoryConfigurationExtensionSupport - Spring Data MongoDB - Could not safely identify store assignment for repository candidate interface com.atlassian.connect.spring.AtlassianHostRepository.
I managed to solve this issue for all my repositories by using MongoRepository and JpaRepository but AtlassianHostRepository comes from an external lib and it is a regular CrudRepository (which totally makes sense because the consumer of the lib can decide what type of DB he would like to use). Anyway it looks that basePackages I specified are completely ignored and not used in any way, even though I specified com.atlassian.connect.spring package only in #EnableMongoRepositories Spring Data somehow can't figure out which data module should be used.
Am I doing something wrong? Is there any other way I could tell spring data to use mongo for AtlassianHostRepository without changing the AtlassianHostRepository.class itself?
The only working solution I found was to let spring data ignore AtlassianHostRepository (because it couldn't figure out which data source to use) then create a separate configuration for it, and simply create it by hand:
#Configuration
#Import({MongoDBConfiguration.class})
public class AtlassianHostRepositoryConfiguration {
private final MongoTemplate mongoTemplate;
#Autowired
public AtlassianHostRepositoryConfiguration(final MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#Bean
public AtlassianHostRepository atlassianHostRepository() {
RepositoryFactorySupport factory = new MongoRepositoryFactory(mongoTemplate);
return factory.getRepository(AtlassianHostRepository.class);
}
}
This solution works fine for a small or limited number of repositories used from a library, it would be rather cumbersome to create all the repositories by hand when there are more of them, but after reading the source code of spring-data I see no way to make it work with basePackages as stated in documentation (I may be wrong though).
I have the following maven project structure:
eu.arrowhead
common
repository
-AJpaRepository.class
orchestrator
controller
-AController.class
OrchestratorApplication
other_modules...
Where two of the modules are common, and orchestrator. Common is a dependency for the Orchestrator module. The JpaRepositoryClass is annotated with #Repository.
In the controller class I use the constructor autowiring to get a copy of the repository:
private final AJpaRepository serviceRepo;
#Autowired
public AController(AJpaRepository serviceRepo){
this.serviceRepo = serviceRepo;
}
And finally, in the Application class, I use scanBasePackages, to pick up the components from the common module:
#SpringBootApplication(scanBasePackages = "eu.arrowhead")
public class OrchestratorApplication {
public static void main(String[] args) {
SpringApplication.run(OrchestratorApplication.class, args);
}
}
When I start the application, I get:
Description:
Parameter 0 of constructor in eu.arrowhead.orchestrator.controller.ArrowheadServiceController required a bean of type 'eu.arrowhead.common.repository.ArrowheadServiceRepo' that could not be found.
Action:
Consider defining a bean of type 'eu.arrowhead.common.repository.ArrowheadServiceRepo' in your configuration.
If I use scanBasePackages = {"eu.arrowhead.common"} then the application starts without an error, but I can not reach the endpoint in my controller class (getting the default 404 error). If I write scanBasePackages = {"eu.arrowhead.common", "eu.arrowhead.orchestrator"} it's the same as if only "eu.arrowhead" is there, I get the same error at startup.
Is this how this supposed to work? I highly doubt it.
Depencendies:
Common module: starter-data-jpa, starter-json, mysql-connector-java, hibernate-validator
Orch module: starter-web, the common module.
I also tried using #ComponentScan, but had the same result. What is the problem? Thanks.
You are missing #EnableJpaRepositories("eu.arrowhead") annotation to enable Spring Data JPA repository scanning.
SETUP
I have one jar (say its A) which adds another jar (say its B) as its dependency through Maven. Jar A is from Spring Boot Application.
ISSUE
I have properties file in jar B under its path src/conf/. Am trying to set the path value in the value attribute of #PropertySource in one of java file in jar B. When trying to do it, it refers the src/conf of jar A. How can i achieve this.
#PropertySource(
value = {
"file:${spring.profiles.path}other-services-dev.properties" })
Here spring.profiles.path gets its value from the deployment script of jar A
Use "classpath:" instead of file:.
Make the property file a resource of jar B by moving it to src/main/resources. Then you can reference the file like this: #PropertySource("classpath:/other-services-dev.properties").
The better approach, which has better encapsulation, would be to define a class in jar B, which is annotated #PropertySource("classpath:/other-services-dev.properties"), and exposes the properties through getters. Then jar A can simply let this class be injected.
In jar B:
#PropertySource("classpath:/other-services-dev.properties")
#Service
class BProperties
#Value("${setting1}")
private String setting1;
public String getSetting1() {
return setting1;
}
}
In jar A
#Service
class SomeService {
#Autowired
private BProperties properties;
}