I'm trying to load properties once in my springboot application.
Actually, I have created a class to do that :
#Configuration
#PropertySource(value = { "classpath:parameters.properties", "classpath:iot.properties" })
public class PropertiesHelper {
#Autowired
protected Environment env;
private static Environment properties;
#PostConstruct
public void init() {
properties = env;
}
public static String getProperty(final String propertyName) {
return properties.getProperty(propertyName);
}
}
This class work fine but this is not clean code (sonar hate my static variable).
So, how can I load in my springboot application all my properties correctly and only once ?
#PropertySource("classpath:parameters.properties")
#PropertySource("classpath:iot.properties")
public class PropertiesHelper {
#Value( "${Value.you.need}" )
private String valueYouNeed;
}
parameters.properties
Value.you.need=12345
Make it something like this, it should be work in your scenario.
An elegant solution
If you just want to eliminate the sonar alarm(sonar hate my static variable)
// application.properties
spring.profiles.active=test1010
// IOC config bean container
#Configuration
#Data
public class PropertiesConfig{
#Value("${spring.profiles.active:preline}")
private String profiles;
}
//Use
#Autowired
private PropertiesConfig propertiesConfig;
#GetMapping("/getPropertiesConfig")
public String getPropertiesConfig(){
return propertiesConfig.getProfiles();
}
I think the above scheme is a more elegant way~,Do you have a better solution?
Related
I want to change the value of application.yaml at loading time.
ex) application.yaml
user.name: ${name}
Here, I want to put this value by calling an external API such as a vault, rather than a program argument when the jar is executed with the name value.
First of all, I think I need to write code that implements EnvironmentPostProcessor and calls external API, but I don't know how to inject that value. can I get help?
public class EnvironmentConfig implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// API CAll
// how can inject yaml value??
}
}
I don't know which way to orient myself.
OPTION 1: doing it via EnvironmentPostProcessor:
assuming you have registered you EnvironmentPostProcessor in /resources/META-INF/spring.factories file:
org.springframework.boot.env.EnvironmentPostProcessor=package.to.environment.config.EnvironmentConfig
all you need is to add your custom PropertySource:
public class EnvironmentConfig implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
environment.getPropertySources()
.addFirst(new CustomPropertySource("customPropertySource"));
}
}
public class CustomPropertySource extends PropertySource<String> {
public CustomPropertySource(String name) {
super(name);
}
#Override
public Object getProperty(String name) {
if (name.equals("name")) {
return "MY CUSTOM RUNTIME VALUE";
}
return null;
}
}
OPTION 2: doing it via PropertySourcesPlaceholderConfigurer:
A class that is responsible for resolving these palceholders is a BeanPostProcessor called PropertySourcesPlaceholderConfigurer (see here).
So you could override it and provide you custom PropertySource that would resolve your needed property like so:
#Component
public class CustomConfigurer extends PropertySourcesPlaceholderConfigurer {
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, ConfigurablePropertyResolver propertyResolver) throws BeansException {
((ConfigurableEnvironment) beanFactoryToProcess.getBean("environment"))
.getPropertySources()
.addFirst(new CustomPropertySource("customPropertySource"));
super.processProperties(beanFactoryToProcess, propertyResolver);
}
}
use ConfigurationProperties for your properties and change it via an api like this:
#Component
#ConfigurationProperties(prefix = "user")
public class AppProperties {
private String name;
//getter and setter
}
#RestController
public class AppPropertiesController {
#Autowire
AppProperties prop;
#PostMapping("/changeProp/{name}")
public void change(#PathVariable String name){
prop.setName(name);
}
}
I have a strange problem reading configuration, none of solutions I've seen seem to work. Here is my code:
#SpringBootApplication
#EnableConfigurationProperties
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Here is my properties class
#Component
#ConfigurationProperties(prefix = "my")
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public class MyProperties {
private String host;
private int port;
}
I then use MyProperties class in my class using #Autowired:
#Autowired
private MyProperties props;
However, I'm getting null for my props object.
Strangely, this is passing the tests just perfectly:
#SpringBootTest
class ApplicationTests {
#Autowired
private MyProperties props;
#Test
void test_configuration() {
Assertions.assertEquals(props.getHost(), "xx.xx.xx.xx");//pass!
Assertions.assertEquals(props.getPort(), xxxxxx);//pass!
}
}
It has totally refused to work, and so has #Value injection. What could I be missing?
EDIT
Here's complete code of how I'm using #Autowired on MyProperties (I've included #Value which is also not working)
#Slf4j
#Component //also tried #Configurable, #Service
public class MyService {
#Autowired
private MyProperties props;
#Value("localhost")
public String host;
public void post() {
log.info(host + props);// =null and null
}
}
EDIT2
However, I've noticed that on the controller, it works perfectly okay:
#Slf4j
#RestController
#Service
public class Main {
#Autowired
private MyProperties props;
#Value("localhost")
private String host;
#GetMapping("/post")
public void post() {
log.info(host + props);//=it's perfect!
new MyService().post();// calling MyService - where #Autowired or #Value is failing
}
}
The reason this isn't working is because the MyService you're using isn't a Spring bean, but an instance you created by yourself (using new MyService()).
To make this work, you should autowire MyService, in stead of creating your own instance:
#Slf4j
#RestController
public class Main {
#Autowired // Autowire MyService
private MyService myService;
#GetMapping("/post")
public void post() {
myService.post(); // Use the myService field
}
}
For more information, look at this Q&A: Why is my Spring #Autowired field null.
UPDATE:
new MyService() is not a "spring bean", thus can't be auto-wired with anything!;)
1. Lombok
Some people use Project Lombok to add getters and setters automatically. Make sure that Lombok does not generate any particular constructor for such a type, as it is used automatically by the container to instantiate the object.
With "such a type" ConfigurationProperties is referred in Externalized Configuration (one of my favorite chapters;) More Exact: 2.8.1. JavaBean properties binding, at the bottom of second "Note!" ;)
So this could be a reason (for strange behavior).
Since according to the docs #Component registers beans for the Spring container I'm trying to create a simple example of dependency injection using the following code:
package pl.playground;
//...
#SpringBootApplication
public class PlaygroundApplication {
#Autowired
private static Building building;
public static void main(String[] args) {
building.setBuildingSize(12L);
System.out.println(building.monthlyHeatingCost());
}
}
package pl.playground.facade;
//...
#Component
public class Building {
private HeatingService service;
private Long buildingSize;
#Autowired
public Building(HeatingService service) {
this.service = service;
}
public Double monthlyHeatingCost() {
return service.getMonthlyHeatingCost(buildingSize);
}
// getters & setters...
}
package pl.playground.service;
public interface HeatingService {
Double getMonthlyHeatingCost(Long size);
}
package pl.playground.service;
//...
#Component
public class HeatingServiceImpl implements HeatingService {
private final Double CUBIC_PRICE = 2.3;
public HeatingServiceImpl() {}
#Override
public Double getMonthlyHeatingCost(Long size) {
return size * CUBIC_PRICE;
}
}
It builds and runs, but there is a NullPointerException at building.setBuildingSize(12L);. However the one below works without any issues:
//PlaygroundApplication.java
package pl.playground;
//...
#SpringBootApplication
public class PlaygroundApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Building building = context.getBean(Building.class);
building.setBuildingSize(12L);
System.out.println(building.monthlyHeatingCost());
}
}
package pl.playground.config;
//...
#Configuration
public class Config {
#Bean
public Building building(HeatingService service) {
return new Building(service);
}
#Bean
public HeatingServiceImpl heatingServiceImpl() {
return new HeatingServiceImpl();
}
}
The rest is the same as before.
Why is #Component not creating Beans?
It is working the way I think it should when used inside a #Controller of a web app, does that make a difference? How does exactly #Bean and #Component differ?
What am I failing to understand?
EDIT
Consider the following scenario:
package pl.playground;
//...
#SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
package pl.playground.controller;
//...
#Controller
public class Controller {
private Facade facade;
#Autowired
public Controller(Facade facade) {
this.facade = facade;
}
#GetMapping("/")
public String getIndexPage(Model model) {
return "index";
}
}
package pl.playground.facade;
//...
#Component
public class Facade {
private PostsService postService;
private UserService userService;
private TagService tagService;
#Autowired
public Facade(PostsService retrieve, UserService user, TagService tag) {
this.postService = retrieve;
this.userService = user;
this.tagService = tag;
}
//...
}
I don't need #Configuration here for it to work. That's my concern.
The problem with your code is that you are trying to #Autowire on a static field. You simply cannot do that. Look here: Can you use #Autowired with static fields?
It fails to work because the PlaygroundApplication class is not being created and managed by spring. The injection works only inside instances managed by spring. You can treat class annotated with #SpringBootApplication as configuration classes. Spring creates instances of those classes and injection works inside them but only on instance fields.
The second example shows the correct way to access spring beans from main method of the application.
Well. I used your original question and is working without any issues. #cezary-butler pointed out in the comments you can autowire into PlaygroundApplication but you can get hold of it easily in the static main method using context.getBean(Building.class)
#SpringBootApplication
public class PlaygroundApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(PlaygroundApplication.class);
Building building = context.getBean(Building.class);
building.setBuildingSize(12L);
System.out.println(building.monthlyHeatingCost());
}
}
Here is the sample repo https://github.com/kavi-kanap/stackoverflow-63072236
TLDR;
A Spring context needs to be created before any bean can be injected. In the first scenario, just the fact of having a #SpringBootApplication decorator does not ensure a context in the scope of the class it decorates.
SpringApplication.run(ExampleApplication.class, args); instantiates a context (and e.g. a web server among other things)
var context = new AnnotationConfigApplicationContext(Config.class); instantiates a scoped context
Thus the first example had null inside of Building as there was no context with the bean to inject.
I am having a simple RestController:
#RestController
#PropertySource("classpath:application.properties")
public class Word2VecRestController {
private final static Logger LOGGER = Logger.getLogger(Word2VecRestController.class);
// #Resource is not working as well
#Autowired
Environment env;
// This is working for some reason
// but it's null inside the constructor
#Value("${test}")
String test;
public Word2VecRestController() {
LOGGER.info(env.getProperty("test"));
System.out.println("");
}
#GetMapping("/dl4j/getWordVector")
public ResponseEntity<List<Double[]>> getWordVector(String word) {
return null;
}
}
The problem is, that env is always null. I've seen somewhere that I could try to use #Resource instead of #Autowired but that didn't help.
application.properties:
test=helloworld
I've tried to use
#Value("${test}")
String test;
but the problem here is that these are null during construction of the object where I need it.
Field injection is done by Spring after the constructor is called. This is why Environment is null in the Word2VecRestController constructor. You can try constructor injection if you need it in the constructor:
#RestController
#PropertySource("classpath:application.properties")
public class Word2VecRestController {
private final static Logger LOGGER = Logger.getLogger(Word2VecRestController.class);
#Autowired
public Word2VecRestController(Environment env, #Value("${test}") String test) {
LOGGER.info(env.getProperty("test"));
System.out.println("");
}
#GetMapping("/dl4j/getWordVector")
public ResponseEntity<List<Double[]>> getWordVector(String word) {
return null;
}
}
PS: if you use Spring Boot, you do not need the #PropertySource("classpath:application.properties"), this is automatically done for you.
Add
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
to enable the PropertySourcesPlaceholderConfigurer to your configuration. Important this must been a static method!
For example:
#Configuration
#PropertySource("classpath:application.properties")
public class SpringConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
I'm stuck! If I skip tests and deploy to tomcat auto wiring the configuration properties file works. In my test, it fails! I'm not sure what I'm missing.
Here is my setup:
Spring Boot v 1.2.5.RELEASE
Application.yml
git:
localRepo: './powershell-status-scripts/'
remoteRepo: 'https://github.com/...'
RepositoryProperties this class has getters and setters for the properties
#Configuration
#ConfigurationProperties(locations = "classpath:application.yml", prefix = "git", ignoreUnknownFields = false)
public class RepositoryProperties {
private String localRepo;
private String remoteRepo;
public RepositoryProperties() {
}
public String getLocalRepo() {
return localRepo;
}
public void setLocalRepo(String localRepo) {
this.localRepo = localRepo;
}
public String getRemoteRepo() {
return remoteRepo;
}
public void setRemoteRepo(String remoteRepo) {
this.remoteRepo = remoteRepo;
}
}
Application.java
#EnableAutoConfiguration
#EnableConfigurationProperties
#ComponentScan(basePackages = "com.sendash.admin")
#EnableJpaRepositories("com.sendash.admin.dao.jpa")
#EnableSwagger
public class Application extends SpringBootServletInitializer {
private static final Class<Application> applicationClass = Application.class;
private static final Logger log = LoggerFactory.getLogger(applicationClass);
public static void main(String[] args) {
SpringApplication.run(applicationClass, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass);
}
}
GitService - Autowiring the properties works on tomcat!
#Service
#EnableConfigurationProperties
public class GitService {
#Autowired
private RepositoryProperties repositoryProperties;
public void updateLocalRepository() {
...
}
GitServiceTest this class fails on init because of a NPE. Properties is null.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = Application.class)
#Profile("test")
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class })
public class GitServiceTest {
#Autowired
private static GitService manager;
#Autowired
private static RepositoryProperties properties;
private static final String localRepoLocation = properties.getLocalRepo();
I do realize after pasting this that #EnableConfigurationProperties is on both the Application.java and the GitService.java class. Stopping the duplication does not fix the problem.
If you want to use Spring Boot in your tests, you should configure the tests accordingly. To do that, remove the ContextConfiguration and add the following:
#SpringApplicationConfiguration(classes = Application.class, initializers = ConfigFileApplicationContextInitializer.class)
This should enable injecting the configuration properties.
I did change my ContextConfiguration as suggested, but my main problem was trying to autowire a static field. It was static for #BeforeClass test setup logic so I needed to move things around a bit, but I got it working. Thanks for the suggestion.