i am currently working on a project consisting of several modules which are all spring managed. I am now trying to autowire a service of module A into module B. This service is configured using a application.yml config file in module A. When using a standalone version of module A everything is working fine and config values are correctly injected into the fields annotated with #Value("${...}"). But if I use this service from module B, its construction fails due to spring being unable to resolve the placeholders given in the annotations.
So it seems like autowiring the service into another project renders it unable of finding the config file. Is there any way of resolving this issue?
Thanks for your help!
EDIT: This is how the relevant parts of the config class look:
#EnableKafka
#Configuration
#EnableElasticsearchRepositories(basePackages = "...")
public class ElasticsearchConfig {
#Value("${elasticsearch.home}")
private String elasticsearchHome;
#Value("${elasticsearch.cluster}")
private String clusterName;
#Bean
public Client client() {
Settings elasticSettings = Settings
.builder()
.put("path.home", elasticsearchHome)
.put("cluster.name", clusterName)
.put("client.transport.sniff", true)
.build();
PreBuiltTransportClient client =
new PreBuiltTransportClient(elasticSettings);
try {
client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
return client;
}
...
}
These values are then used to create the beans necessary to build an elasticsearch client . The corresponding application.yml looks something like this:
elasticsearch:
cluster: elasticsearch
home: "/path/to/elasticsearch-6.4.2/"
kafka:
bootstrap-servers: localhost:9092
...
Here is the relevant part of the project structure:
Project
|-- module A
| `-- src
| |-- main
| | `-- java
| | |-- ElasticsearchConfig.java
| | |-- SomeService.java
| | `-- ServiceUsedFromA.java
| `-- resources
| `-- application.yml
`-- module B
`-- src
`-- main
|-- java
| `-- ServiceUsedFromB.java
`-- resources
`-- ...
If SomeService is autowired in ServiceUsedFromA everything is working as expected, if autowired into ServiceUsedFromB (which is also spring based) the above mentioned issue occurs.
After a substantial amount of searching and trying out different ideas to fix the issue, I have finally found a solution:
I switched from using a YAML config file to using application.properties placed in the resource folder mentioned in the project structure posted above. This allowed me to specify the property file via a #PropertySource(value = "classpath:application.properties") annotation in ElasticSearchConfig.java (which is not possible for YAML configs).
Now spring seems to be able to pick up the config file, even if the service is autowired into another module.
Thanks for your effort :)
Related
I have a Spring Boot Web project that has a Spring Boot JPA project as a dependency like so:
spring_boot_web
|__.../application.yml
|
|__spring_boot_jpa
| |__.../data.properties
| |__.../data-test.properties
| |__.../data-dev.properties
| \__pom.xml
|
|__pom.xml
Web project uses the default application.yml file and jpa uses a properties file as yml are not supported by #PropertySource annotation.
I can run them alone flawlessly but when I try to include the jpa inside web there are problems creating the beans related to db. Is there any way to have those project running their own config files?
I just got it working. As the JPA project does not stand on its own (only for testing purposes) I just left some properties for the profiles I want that project to run standalone and then the main properties file under the web project. The structure is as follows:
spring_boot_web
|__.../application.yml
|
|__spring_boot_jpa
| |__.../application-test.yaml
| |__.../application-dev.yaml
| \__pom.xml
|
|__pom.xml
And then I made sure that there was only one #SpringBootApplication in between both projects altogether.
Also keep in mind that the main #SpringBootApplication has to be in a package in common for both projects:
spring_boot_web
|__foo.bar.core
| \__SpringWebApplication.java <- main #SpringBootApplication
|
|__foo.bar.core.web
| \__...
|
\__spring_boot_jpa
\__foo.bar.core.services
\__...
I have a gradle project that is divided into multiple modules. There is a shared module that defines a root GraphQL schema that I want to expose to other modules but I'm not sure how to define this dependency in other modules. Also a requirement is that IntelliJ IDEA would be able to resolve the types in the other modules' GraphQL schema to their definitions in the shared module's schema.
For example, my project has the following structure:
projectRoot
|-- shared/src/main/resources/graphql
| `-- schema.graphql
|-- user/src/main/resources/graphql
| `-- schema.graphql
`-- task/src/main/resources/graphql
`-- schema.graphql
In shared/src/main/resources/graphql/schema.graphql I have defined the following:
type Mutation {}
type Query {}
And in user/src/main/resources/graphql/schema.graphql I have defined the following:
extend type Mutation {}
extend type Query {}
I have defined a dependency on the shared module in the user module's gradle.build file as follows:
dependencies {
api(project(":shared")) // I have tried implementation also
}
I am unable to run the project as I have a few other things to sort out also but IDEA is unable to resolve the types:
The GraphQL I'm using is Spring for GraphQL
In your case, there are two different problems that need to be solved: the Spring for GraphQL auto-detection of schema files, and the IntelliJ IDEA plugin support.
First, if you've structured your application this way, you should tell Spring Boot to look for schema files in the entire classpath with a configuration property:
spring.graphql.schema.locations="classpath*:graphql/**/"
Second, the IntelliJ IDEA plugin for GraphQL support is relying on the .graphqlconfig specification. In your case, you could add a .graphqlconfig file at the root of your project with the following:
{
"projects": {
"shared": {
"includes": ["./shared/src/main/resources/graphql/*.graphql"]
},
"user": {
"includes": ["./user/src/main/resources/graphql/*.graphql"]
},
"task": {
"includes": ["./task/src/main/resources/graphql/*.graphql"]
}
}
}
You can check out the plugin documentation since there are multiple ways to structure your configuration for this use case.
I have a spring boot project with the following structure
src
|--- main
| |--- java
| | |--- io.example.config
| | | |--- AppConfig (Annotated #Cofiguration)
| | |--- io.example.beans
| | | |--- Bean1 (Annotated #Component)
| | | |--- Bean2 (Annotated #Component)
| | |--- io.example.repository
| | | |--- Repo (DynamoDB Crud Repo; Annotated #EnableScane)
| | |--- io.example.main
| | | |--- Application (Annotated #SpringBootApplication)
| |--- module-info.java
|--- test
| |--- java
| | |--- io.example.main
| | | |--- IntTest (Annotated #SpringBootTest)
The code in the IntTest class looks like
#RunWith(SpringRunner.class)
#SpringBootTest
public class IntTest {
...
}
When I run the tests from IntelliJ it runs fine. But I run it from maven mvn test it throws the following error
Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
I read some of the documentation and similar questions in SO. Most of the solution rectifies the package alignment between source and test folder. Which is not a problem in my case.
The test runs fine with mvn test when I do the following I explicitly define the context dependencies
package io.example.main;
import org.springframework.test.context.junit4.SpringRunner;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import io.example.config.AppConfig;
import io.example.beans.Bean1;
import io.example.beans.Bean2;
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = { Application.class, AppConfig.class, Bean1.class, Bean2.class })
#SpringBootTest
public class IntTest {
...
#Autowired
private Bean1 b1;
#Autowired
private Bean2 b2;
#Test
public void testRandom() {
/* assert statements */
}
}
I can do the above, but is there any way I can run the tests without explicitly defining ContextConfiguration?
Try to put your Application (Annotated #SpringBootApplication) class in the package that is root of other packages. In your case it is io.example package.
#piradian has already provided a correct answer, I'll try to explain why is it correct.
#SpringBootTest annotation when placed on test in its most simplistic way (without parameters) tries to mimic the the process of starting the microservice for the test as precise as possible.
There are basically two steps it should do when it comes to the configuration retrieval:
Find the spring boot application
Find all configurations / components that should be loaded
For the first step, it first tries to find the #SpringBootConfiguration annotation. This is a meta-annotation placed on #SpringBootApplication annotation. This is required to find all the registered components / configurations.
So it starts with the package where the test resides (io.example.main) and if it finds class with #SpringBootConfiguration in the same package (and yes, it founds it) - then it means that this is the base package to search for all configurations / components.
If it doesn't find a class like this, then it goes one package up (io.example) and start searching again, then if not found goes for (io)
When the class is found, it start to search for configuration / components downwards starting with the package where the application resides. This is exactly how spring boot application works. And this is the source of the failure:
io.example.main package doesn't have any "sub-packages" so the spring boot test finds nothing and that's why test fails.
If you move the SpringBootApplication one package up, this solves the issue.
, because now the first step of the process described above searches in io.example.main package, finds nothing, searches in io.example, finds the required class and this is where it starts the second step from.
Now, if you use #SpringBootTest in conjunction with #ContextConfiguration this is like saying to spring boot: "Don't activate this two-phase search, just take the configuration classes that I supply and start from there". That's why it works for you with #ContextConfiguration
I set the view config of spring boot app as:
#Configuration
public class ViewConfig extends WebMvcConfigurerAdapter {
public static final String[] SCRIPTS = {
"nashorn/ejs.min.js",
"nashorn/polyfill.js",
"nashorn/render.js"
};
#Bean
public ViewResolver viewResolver() {
ScriptTemplateViewResolver viewResolver = new ScriptTemplateViewResolver();
viewResolver.setPrefix("templates/");
viewResolver.setSuffix(".ejs");
return viewResolver;
}
#Bean
public ScriptTemplateConfigurer viewConfigurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts(SCRIPTS);
configurer.setRenderFunction("render");
configurer.setSharedEngine(false);
return configurer;
}
}
and my maven structure as:
App
|-- pom.xml
`-- src
|-- main
|-- java
| `-- config
| `-- ViewConfig.java
| `-- Application.java
|-- resources
| `-- nashorn
| `-- ejs.min.js
| `-- polyfill.js
| `-- render.js
| `-- templates
| `-- index.ejs
However, when I run with command mvn spring-boot:run I got to a problem failed to load script with root cause:
java.lang.IllegalStateException: Resource [nashorn/ejs.min.js] not found
My purpose is that I want to build a isomorphic app with Java and ReactJs but I got to these problems one week ago and until now I cannot solve these problem.
Let's assume that we have Spring Boot based web application using JSP templates. It can be even as simple as in the following example (from official Spring Projects repository)
Project structure:
|-src/main/java/
| |-sample.tomcat.jsp
| |-SampleTomcatJspApplication.java
| |-WelcomeController.java
|-src/main/resources/
| |-application.properties
|-src/test/java/
| |-...
|-src/main/webapp/
| |-WEB-INF
| |-jsp
| |-welcome.jsp
|-pom.xml
Properties file contains view prefix /WEB-INF/jsp/ and suffix .jsp and when requesting / we see properly rendered content of welcome.jsp.
WelcomeController.java
application.properties
Changes
Now let's make the following changes
Duplicate WelcomeController.java as WelcomeController2.java and change a bit request mapping, model attributes and returned view name, e.g.:
#RequestMapping("/2")
public String welcome2(Map<String, Object> model) {
model.put("message", "Hi from Welcome2");
return "welcome2";
}
Duplicate welcome.jsp as welcome2.jsp so that src/main/webapp will be like this:
|-src/main/java/
| |-sample.tomcat.jsp
| |-SampleTomcatJspApplication.java
| |-WelcomeController.java
| |-WelcomeController2.java
...
|-src/main/webapp/
| |-WEB-INF
| |-jsp
| |-welcome.jsp
| |-welcome2.jsp
Then when requesting /2 we can see properly rendered content of welcome2.jsp.
The question
What is the way of splitting such project into two maven projects, so that both WelcomeController2.java and welcome2.jsp could be moved to other project (maven dependency) and still be successfully resolved when /2 URL is requested?
Note that with Spring Boot web-fragment.xml (that could be placed in META-INF directory of dependency) is ignored.
Unfortunately, I don't know of an easy way to do this but one approach I've used is to create a Maven artifact just like normal for the main project, in your case probably a WAR artifact. This project will need to have a dependency upon your second project. Then your second project would consist of two components:
A standard Maven JAR artifact containing the compiled class files.
A Maven assembly ZIP consisting of the JSP files that need to be included in the WAR archive as well. This will be generated from the second project during the package phase, but will need to be included as a separate dependency on the main project using a zip classifier.
When the first project is built, you'll need to unpack the assembly dependency as part of the packaging process for the WAR archive. If you want this to work in an IDE, you'll probably need to unpack it in a fairly early phase, such as process-resources or generate-sources.