I want to reload multiple config files when the refresh endpoint is called. It works perfectly fine with the entries in the application.properties file. Any other file does not refresh.
Here a small example:
pom.xml
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
</dependencies>
...
application.properties
greeting=Hi
management.endpoints.web.exposure.include=refresh
test.properties
test=Test
ConfigFileHolder1
#Repository
#PropertySource(value="application.properties")
#RefreshScope
public class ConfigFileHolder1 {
#Value("${greeting}")
private String greeting;
public String getGreeting() {
return greeting;
}
}
ConfigFileHolder2
#Repository
#PropertySource(value="test.properties")
#RefreshScope
public class ConfigFileHolder2 {
#Value("${test}")
private String test;
public String getTest() {
return test;
}
}
ApiController
#Controller
#RefreshScope
public class ApiController implements Api {
#Autowired
private ConfigFileHolder1 config1;
#Autowired
private ConfigFileHolder2 config2;
// do something with config1 and config2
...
}
Only ConfigFileHolder1 will refresh its value after the refresh-endpoint is called. To refresh the value of ConfigFileHolder2 the application has to restart.
What do I have to change to refresh the values of all my config-files/ConfigFileHolder?
Thanks for your help.
The #RefreshScope will only work with the properties loaded by Spring Boot, not the #PropertySources loaded later in the process. Hence you will need to tell Spring Boot to load the additional configuration files.
You can do this by either adding names (spring.config.name) or locations spring.config.additional-location.
When specifying an additional name make sure to include the default application as well else that won't be loaded anymore.
--spring.config.name=application,test
When specifying the above as a parameter all locations will be checked for both an application.properties and test.properties and also the expansion for profiles will be applied.
--spring.config.additional-location=classpath:test.properties
This will only load the test.properties from the class path and will make it more or less impossible to change the file at runtime but the file will be loaded from that exact location. No profile expansion will be applied.
Related
Edit: I uploaded a minimal example. You can build with mvn clean install and after deployment, open the following urls:
http://localhost:8080/jaxrs-test/unqualified -> response: Hello there: de.test.SomeBean#........
http://localhost:8080/jaxrs-test/qualified -> response: Hello there: null
Tested on wildfly-23.0.0.Final with openjdk11.
Original quesion:
Consider the classes:
App.java - Jaxrs application:
#ApplicationPath("/api")
public class App extends Application {
}
Q.java - qualifier annotation:
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Q {
}
X.java - some simple class:
public class X {
public String getX(){
return "some x";
}
}
Foo.java - jaxrs resource:
#Path("/foo")
#Q
public class Foo {
#Inject
private X test;
#Path("bar")
#GET
public String getBar() {
return test.getX();
}
}
The pom contains the following dependencies (and nothing else relevant):
<dependencies>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
After building this application and deploying it to a wildfly server, a reques to
http://localhost:8080/app/api/foo/bar
leads to a null pointer exception, because test in the Foo resource is null.
The request and injection of X works perfectly when removing the qualifier annotation.
Why is that? I could not find anything in the documentation suggesting that.
Is there any way to get CDI working in a qualified jaxrs-resource class?
With qualified I mean any qualifier besides #Named, since the latter does not prevent the class to get the qualifier #Default.
I am new to SpringBoot. I have built a simple application which should use fake data in the development environment, and connect to MongoDb in the test environment. Dev environment does not have mongodb setup.
I have tried using Spring Boot qualifiers/profiles to achieve it.
I have a main class which looks like the following:
#SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
I have a DAO interface StudentDao.java
public interface StudentDao {
Student getStudentById(String id);
}
I then created a couple of implementations for the DAO, one for fake data, and one for data from Mongo
FakeStudentDaoImpl.java
#Repository
#Qualifier("fakeData")
public class FakeStudentDaoImpl implements StudentDao {
private static Map<String, Student> students;
static {
students = new HashMap<String, Student>(){
{
put("1", new Student("Ram", "Computer Science"));
}
};
}
#Override
public Student getStudentById(String id){
return this.students.get(id);
}
}
MongoStudentDaoImpl.java
#Repository
#Qualifier("mongoData")
public class MongoStudentDaoImpl implements StudentDao {
#Autowired
private MongoStudentRepo repo;
#Override
public Student getStudentById(String id) {
return repo.findById(id).get();
}
}
The MongoStudentRepo is a simple interface extending MongoRepository:
public interface MongoStudentRepo extends MongoRepository<Student, String> {
}
And my POM file has the following dependencies called out:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
Of course, I have other controller classes.
This works fine in the Test environment, where there is a MongoDb, and it is able to connect to it. However, when I am trying to start it in my local environment, it fails to start because it is not finding MongoDb on startup.
How do I disable the MongoDb part in my local environment (and just use fake data)? I want to make the same code work in both environments.
Thanks in advance.
I had the same problem, and found the solution you ask for in this other question:
Spring Boot. How to disable Initialization of JPA Conditionaliy
For example, if you want to disable spring-data-mongodb in the development environment, then, assuming that you run under a "dev" profile:
application-dev.yml:
spring:
autoconfigure:
exclude: org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
You can use an embedded MongoDB database. Here an example.
Several possible options:
1) You can use spring profiles. Map one bean with #Profile("test) and second one with #Profile("prod"). To specify which profile to use --spring.profiles.active=test
2) You can have different configurations.
application-prod.yml
--------------
mongo-url:produrl
application-test.yml
--------------
mongo-url:localhost
Use spring active profiles to select config. To use local profile you need to setup local mongo instance. And you can have several options again: just download instance, docker image, embeded mongo.
I am trying to understand the behaviour of #PropertySource annotation when not using #Autowiring and Spring Environment class. I am trying to use #Value to inject values from a properties file at runtime. From the book that I am reading and from the online sources it is required to have a static bean - PropertySourcesPlaceholderConfigurer configured in order for this to work. But for me the #Value works without PropertySourcesPlaceholderConfigurer static bean as well. Can someone point me to the right direction as to whats happening here. May be I am missing something very basic. When do we need PropertySourcesPlaceholderConfigurer and when not?
Below is the code that I am trying out -
package com.nilaysundarkar.demos;
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
AppConfig.java -
package com.nilaysundarkar.demos;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
#Configuration
#PropertySource("classpath:/com/nilaysundarkar/demos/app.properties")
public class AppConfig {
// This bean does not make any difference, or does it?
/*#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}*/
#Bean
public Person person(#Value("${person.name}") String name){
Person person = new Person();
person.setName(name);
return person;
}
}
Bootstrap -
package com.nilaysundarkar.demos;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
public static void main(String[] args){
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Person person = context.getBean(Person.class);
System.out.println(person.getName());
((AnnotationConfigApplicationContext)context).close();
}
}
properties file - app.properties -
person.name=John Doe
pom -
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nilaysundarkar.demos</groupId>
<artifactId>demos-runtime-injections</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
</dependencies>
When I run App.java -
In spring boot, and spring in general, application.properties (and application.yml since spring boot) can be placed in src/main/resources and it is picked automatically by the spring environment. That means that any property from this files will be loaded to your Environment and will be ready for injection using #Value.
You can use PropertySourcesPlaceholderConfigurer in order to register more property sources like foo.properties, [NAME].properties and so on in order for the spring Environment to add them.
When you use #PropertySource you register another propery file to your spring Environment so you dont need to use the custom PropertySourcesPlaceholderConfigurer to register it again. #PropertySource make it easier to register property files that do not require some special loading like a file in your file system etc.
As long as you use the default locations (application.properties) you don't need to register a custom bean of this type.
EDIT:
Example for PropertySourcesPlaceholderConfigurer with the same functionality as #PropertySource. The example is based on a foo.properties file residing in src/main/resources:
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("foo.properties"));
return configurer;
}
Took Privotal's Spring Core course months ago (probably Feb/2018) and the student handout (Version 5.0.a) explicitly tells you that a static PropertySourcesPlaceholderConfigurer bean must be declared in order for the ${}placeholders to be resolved when using Spring Core. But, when I tested this behavior omitting the creation of the bean in question it worked as If I’d created the bean resolving the placeholders. After that, I contacted my course instructor because I thought there was something wrong with my code, but he later confirmed that my code to be "good". He proceeded to contact Pivotal and we got an official answer :
Properties files registered via #PropertySource are automatically
added to the Environment in
org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClass,
SourceClass) while processing #Configuration classes.
So, apparently Pivotal acknowledge that the documentation on this is poor, and have filed a new JIRA.
BTW, this only applies to Spring Core 4.3+ as Spring Boot creates this bean for you automatically.
EDIT:
If you're taking the certification test, it's not clear when will Pivotal make the update, but the odds of this particular issue showing up on the exam are minuscule (in case it does appears, you can appeal)
I'm trying to create a SpringBoot application which uses solr repositories. I'm following this tutorial:
http://docs.spring.io/spring-data/solr/docs/current/reference/html/#solr.repositories
which says to configure my application with the following class (Example 43):
#Configuration
#EnableSolrRepositories
class ApplicationConfig {
#Bean
public SolrClient solrClient() {
EmbeddedSolrServerFactory factory = new EmbeddedSolrServerFactory("classpath:com/acme/solr");
return factory.getSolrServer(); // getSolrServer does not exist
}
#Bean
public SolrOperations solrTemplate() {
return new SolrTemplate(solrClient());
}
}
The problem is if I do that it doesn't recognise getSolrServer() as a method of factory. Indeed, if you look at the most recent API for EmbeddedSolrServerFactory you don't find that method, but it apparently existed in a previous version of the same class.
Maybe it was renamed from getSolrServer to getSolrClient, for some reason, from one version to another.
Here's my dependencies in the pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Anyway, I tried to change getSolrServer to getSolrClient, but the return type, i.e. SolrClient, is now incompatible. If I try to return org.apache.solr.client.solrj.embedded.EmbeddedSolrServer, it gives me an error because it doesn't find org.apache.solr.client.solrj.embedded...
Another problem using this would be that SolrTemplate doesn't require a EmbeddedSolrServer, so this is not a good option...
I am using eclipse not spring suite, Assuming spring suite using latest version of spring-boot-starter-data-solr ( 1.4.2 ) and you need to add an entry for solr-core 5.x in your pom.
Since EmbeddedSolrServer is extending SolrClient, follows Java IS-A relationship and it should be compatible with SolrClient. This binary is part of solr-core.
Your code need to use getSolrClient itself and it should be compatible with SolrClient
Dependencies in pom.xml is as follows
Here we go with our code base without any errors.
Instead of creating SolrClient bean, create EmbeddedSolrServerFactoryBean object and pass that object to solr template to create SolrTemplate object. Here is my config file:
#Configuration
#EnableSolrRepositories(basePackages = "com.ida.*.repository")
#Profile("dev")
public class SolrConfigDev {
#Autowired
private Environment environment;
#Bean
public EmbeddedSolrServerFactoryBean solrServerFactoryBean() {
EmbeddedSolrServerFactoryBean factory = new EmbeddedSolrServerFactoryBean();
factory.setSolrHome(environment.getRequiredProperty("solr.solr.home"));
return factory;
}
#Bean
public SolrTemplate solrTemplate() throws Exception {
return new SolrTemplate(solrServerFactoryBean().getObject());
}
}
In addition, you have to add solr-core to you pom.xml file.
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-core</artifactId>
<version>5.5.3</version>
</dependency>
More information about this topic, you can find here in this blog by Petri Kainulainen.
I am writing a new app and trying to do BDD using cucumber and Spring Boot 1.4. Working code is as shown below:
#SpringBootApplication
public class Application {
#Bean
MyService myService() {
return new MyService();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
public class MyService {}
Test code is as shown below:
#RunWith(Cucumber.class)
public class RunFeatures {}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Application.class, loader = SpringApplicationContextLoader.class)
public class MyStepDef {
#Autowired
MyService myService;
#Given("^Some initial condition$")
public void appIsStarted() throws Throwable {
if (service == null) throw new Exception("Dependency not injected!");
System.out.println("App started");
}
#Then("^Nothing happens$")
public void thereShouldBeNoException() throws Throwable {
System.out.println("Test passed");
}
}
Feature file is as shown below:
Feature: Test Cucumber with spring
Scenario: First Scenario
Given Some initial condition
Then Nothing happens
When I run the above as is, all works well and dependency (MyService) is injected into MyStepDef with no issues.
If I replace this code:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Application.class, loader = SpringApplicationContextLoader.class)
With the code below (New way to handle it in Spring Boot 1.4):
#RunWith(SpringRunner.class)
#SpringBootTest
Then the dependency (MyService) never gets injected. Am I missing something perhaps?
Thanks in advance for your help!!!
I had the same problem. The comment from above directed me to the solution
The problematic code in cucumber-spring seems to be this github.com/cucumber/cucumber-jvm/blob/master/spring/src/main/…
After adding the annotation #ContextConfiguration the tests are working as expected.
So what i've got is the following...
#RunWith(Cucumber.class)
#CucumberOptions(plugin = {"json:target/cucumber.json", "pretty"}, features = "src/test/features")
public class CucumberTest {
}
#ContextConfiguration
#SpringBootTest
public abstract class StepDefs {
}
public class MyStepDefs extends StepDefs {
#Inject
Service service;
#Inject
Repository repository;
[...]
}
I hope this helps you further
I got it working with Spring Boot 1.5.x and 2.0 and then wrote a blog post to try to clarify this since it's tricky.
First, even if it's obvious, you need to have the right dependencies included in your project (being cucumber-spring the important one here). For example, with Maven:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>2.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>2.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<version>2.3.1</version>
<scope>test</scope>
</dependency>
Now, the important part to make it work, summarized:
The entry point to your test should be a class annotated with #RunWith(Cucumber.class.
This class will use the steps definitions, which are normally in a separated class with annotated methods (#Given, #When, #Then, etc.).
The trick is that this class should extend a base class annotated with #SpringBootTest, #RunWith(SpringRunner.class) and any other configuration you need to run your test with Spring Boot. For instance, if you're implementing an integration test without mocking other layers, you should add the webEnvironment configuration and set it to RANDOM_PORT or DEFINED_PORT.
See the diagram and the code skeleton below.
The entry point:
#RunWith(Cucumber.class)
#CucumberOptions(features = "src/test/resources/features/bag.feature", plugin = {"pretty", "html:target/cucumber"})
public class BagCucumberIntegrationTest {
}
The Spring Boot base test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class SpringBootBaseIntegrationTest {
}
The step definitions class:
#Ignore
public class BagCucumberStepDefinitions extends SpringBootBaseIntegrationTest {
// #Given, #When, #Then annotated methods
}
This is what you need to make DI work. For the full code example, just check my blog post or the code in GitHub.
Prior to Spring Boot 1.4 you can use
#ContextConfiguration(classes = {YourSpringConfiguration.class}, loader = SpringApplicationContextLoader.class)
From Spring Boot 1.4 onwards SpringApplicationContextLoader is deprecated so you should use SpringBootContextLoader.class instead
Really just adding #SpringBootTest (with an optional configuration class) should work on its own, but if you look at the code in cucumber.runtime.java.spring.SpringFactory method annotatedWithSupportedSpringRootTestAnnotations it's not checking for that annotation, which is why simply adding that annotation in conjunction with #SpringBootTest works.
Really the code in cucumber-spring needs to change. I'll see if I can raise an issue as in the Spring docs it states that SpringApplicationContextLoader should only be used if absolutely necessary.I'll try and raise an issue for this for the cucumber spring support.
So as it stands stripwire's answer using a combination of #SpringBootTest and #ContextConfiguration is the best workaround.
This is my configuration, you can try it
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#ContextConfiguration(classes = {Application.class})
I've got it working in Spring Boot 1.5. I want to share the configuration with you:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
...
<dependencies>
...
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-spring</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
</dependencies>
...
</project>
Feature file
Feature: User should be greeted
Background:
Given The database is empty
Then All connections are set
Scenario: Default user is greeted
Given A default user
When The application is started
Then The user should be greeted with "Hello Marc!"
Cucumber hook
#RunWith(Cucumber.class)
#CucumberOptions(features = "src/test/resources", strict = true)
public class CucumberTests { // Classname should end on *Tests
}
Abstract Spring Configuration
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration
abstract class AbstractSpringConfigurationTest {
}
Glue
class CucumberGlue : AbstractSpringConfigurationTest() {
#Autowired
lateinit var restTemplate: TestRestTemplate
#Autowired
lateinit var restController: RestController
#Autowired
lateinit var personRepository: PersonRepository
#Autowired
lateinit var entityManager: EntityManager
private var result: String? = null
#Given("^The database is empty$")
fun the_database_is_empty() {
personRepository.deleteAll()
}
#Then("^All connections are set$")
fun all_connections_are_set() {
assertThat(restTemplate).isNotNull()
assertThat(entityManager).isNotNull()
}
#Given("^A default user$")
fun a_default_user() {
}
#When("^The application is started$")
fun the_application_is_started() {
result = restController.testGet()
}
#Then("^The user should be greeted with \"([^\"]*)\"$")
fun the_user_should_be_greeted_with(expectedName: String) {
assertThat(result).isEqualTo(expectedName)
}
}