I'm using Spring (without spring-boot). I want to build standalone application that can be run with default configuration (logback.xml and application.properties in resource folder) or with -Dconfig.folder=/path/to/custom/external/directory
(logback.xml and application.properties in /path/to/custom/external/directory). When application will be run with -Dconfig.folder param AppConfig should load both logback and properties from external directory.
Is there anyway to make external folder act like a resource folder?
If not, what is a common solution for this?
My current implementation (using default resource folder only):
App.java
public class App {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
SampleAction p = context.getBean(SampleAction.class);
p.performTask();
}
}
AppConfig.java
#ComponentScan
#PropertySource("classpath:application.properties")
class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
SampleAction.java
#Component
public class SampleAction {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Value("${sample.prop}")
private String sampleProp;
public void performTask(){
logger.debug(sampleProp);
}
}
logback.xml and application.properties are not relevant to the problem
Unlike the other answer suggests, if you use file prefix in #PropertySource, you're screwed because it won't be able to load the default application.properties from the jar. What you should do is the following:
#PropertySource("${config.folder:'classpath:'}/application.properties")
public class AppConfig
For logback.xml:
#Value("${config.folder}:")
private String configFolder;
InputStream = Optional.of(new ClassPathResource(configFolder + "/logback.xml"))
.filter(r -> r.exists())
.orElse(new ClassPathResource("classpath:/logback.xml"))
.getInputStream();
In both cases, I gave preference to the command line argument over the default packaged files. Of course, I didn't compile the above, so there may be typos or minor errors, but you get the idea.
Edit:
Since OP claims to not understand where to run the above code -
public class AppConfig {
#PostConstruct
void init() {
// init logback here
}
}
For log4j.xml
-Dlog4j.configuration=C:\neon\log4j.xml as VM argument
In main() method:
String filename = System.getProperty("log4j.configuration");
DOMConfigurator.configure(filename);
For external properties file:
-Dext.prop.dir=C:\neon as VM argument
Change in your AppConfig class will be like
#PropertySource("file:///${ext.prop.dir}/application.properties")
public class AppConfig{
}
Run App class with VM arguments as below and both file will be use from external location
-Dlog4j.configuration=C:\neon\log4j.xml -Dext.prop.dir=C:\neon
Related
I have the following in configuration for my Spring Boot project that serves static files from the local filesystem:
#Configuration
public class StaticResourceConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
System.out.println("adding resource handler");
registry.addResourceHandler("/myfiles/**").addResourceLocations("file:///C:/Users/Pepria/Downloads/static_files/");
}
}
Above config works fine but I want to change the resource location dynamically at runtime. As I understand, above code runs before any of my logic executes. How can I go about doing this ?
You can add a ResourceHandler with your desired path like this:
registry.addResourceHandler("/myfiles/**").addResourceLocations("file:" + Strings.filePath);
You can set Strings.filePath in you application at any time.
public class Strings {
public static String filePath;
//or maybe setters getters
}
I have a basic SpringBoot app., embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
I have this main class
package com.tdk.iot;
#SpringBootApplication
#Import({SecurityConfig.class })
public class TdkApplication {
public static void main(String[] args) {
SpringApplication.run(TdkApplication.class, args);
}
}
and this one
package com.tdk.iot.config;
#Configuration
#Profile("dev")
#PropertySource("file:///${user.home}/.tdk/application-dev.properties")
public class DevelopmentConfig {
#Bean
public EmailService emailService() {
return new MockEmailService();
}
#Bean
public ServletRegistrationBean h2ConsoleServletRegistration() {
ServletRegistrationBean bean = new ServletRegistrationBean(new WebServlet());
bean.addUrlMappings("/console/*");
return bean;
}
}
and this value in the application.properties:
spring.profiles.active=dev
But it seems that is not working since I can't access to the page /console/*
You should move #PropertySource to your SecurityConfig or TdkApplication classes, because you try to load profile property "dev" in class which will be included to context only when profile is "dev" - so Spring can't load property file.
I found the error. In the eclipse -> Run configurations -> Arguments the value was also overwritten with other value
-Dspring.profiles.active=acc
I'm currently learning how to work with Spring Boot. Until now I never used Frameworks like Spring and used files directly (FileInputStream, etc.)
So here is the case: I have some dynamic configuration values like OAuth tokens. I want to use them inside of my application but I have no clue how to realize this with Spring.
Here is some code to make clear what I'm searching for:
#Config("app.yaml")
public class Test {
#Value("app.token")
private String token;
private IClient client;
public Test(String token) {
this.client = ClientFactory.build(token).login();
}
}
Sure, this example is very plain. Here I want to get the value "token" dynamically from a YAML configuration file. This file must be accessible for the user and not included in the JAR file.
I also found that doc: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html but I have now idea how to apply this to my project.
How can I achive this? Thank you in advance :)
Edit:
Here are some parts of my code:
WatchdogBootstrap.java
package de.onkelmorph.watchdog;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
#SpringBootApplication
#ImportResource("classpath:Beans.xml")
public class WatchdogBootstrap {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(WatchdogBeans.class);
app.setBannerMode(Mode.OFF);
app.setWebEnvironment(false);
app.run(args);
}
}
Beans.xml (Located in default package)
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config></context:annotation-config>
</beans>
Watchdog.java
package de.onkelmorph.watchdog;
// Imports ...
#Component
#PropertySource("file:/watchdog.yml")
public class Watchdog {
// ...
// Configuration
#Value("${watchdog.token}")
private String token;
public Watchdog() {
System.out.println(this.token);
System.exit(0);
}
// ...
}
watchdog.yml (Located in src/main/resources)
watchdog:
token: fghaepoghaporghaerg
First of all your Test class should be annotated with #Component in order for it to be registered as a bean by spring (also make sure all your classes are under your main package - the main package is where a class that is annotated with #SpringBootApplication reside).
Now you should either move all your properties to application.yml (src/main/resources/application.yml), that is picked automatically by spring boot (note that it should be .yml instead of .yaml or register a custom PropertySourcesPlaceholderConfigurer.
Example for PropertySourcesPlaceholderConfigurer:
#Bean
public static PropertySourcesPlaceholderConfigurer PropertySourcesPlaceholderConfigurer() throws IOException {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
MutablePropertySources propertySources = new MutablePropertySources();
Resource resource = new DefaultResourceLoader().getResource("classpath:application.yml");
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
PropertySource<?> yamlProperties = sourceLoader.load("yamlProperties", resource, null);
propertySources.addFirst(yamlProperties);
configurer.setPropertySources(propertySources);
return configurer;
}
Now your properties should be loaded to spring's environment and they will be available for injection with #Value to your beans.
You basically got three easy options.
Use application.properties which is Springs internal configuration file.
Load your own configuration file using --spring.config.name as VM parameter.
You can use #PropertySource to load either an internal or external configuration. #PropertySource only works with .properties config files. There is currently an open Jira ticket to implement yaml support. You can follow the progress here: https://jira.spring.io/browse/SPR-13912
Notice, if you are using multiple yaml and/or properties files which contain common keys, the it will always use the definition of the key which was loaded last. This is why the below example uses two different keys. If it used the same key, then it would print out PROPERTIES FILE twice.
Short simple code snippet:
#Component
#PropertySource("file:/path/to/config/app.properties")
class Address{
#Value("${addr.street}")
private String street;
#Value("${addr.city}")
private String city;
}
app.properties
addr.street=Abbey Road
addr.city=London
Extensive Example
DemoApplication.java
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//Call class with properties
context.getBean(WatchdogProperties.class).test();
//Call class with yaml
context.getBean(WatchdogYaml.class).test();
}
//Define configuration file for yaml
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("watchdog.yml"));
propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
return propertySourcesPlaceholderConfigurer;
}
}
WatchdogProperties.java
#Component
//PropertySource only works for .properties files
#PropertySource("classpath:watchdog.properties")
public class WatchdogProperties{
//Notice the key name is not the same as the yaml key
#Value("${watchdog.prop.token}")
private String token;
public void test(){
System.out.println(token);
}
}
WatchdogYaml.java
#Component
class WatchdogYaml{
//Notice the key name is not the same as the properties key
#Value("${watchdog.token}")
private String token;
public void test(){
System.out.println(token);
}
}
Properties and Yaml files
Both of these files are located in src/main/resources
watchdog.yml:
watchdog:
token: YAML FILE
watchdog.properties:
watchdog.prop.token=PROPERTIES FILE
Output
PROPERTIES FILE
YAML FILE
Spring Boot uses a PropertySource order that is designed to allow sensible overriding of values, properties are considered in the following order:
Command line arguments.
Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property)
JNDI attributes from java:comp/env.
Java System properties (System.getProperties()).
OS environment variables.
A RandomValuePropertySource that only has properties in random.*.
Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants)
Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants)
Application properties outside of your packaged jar (application.properties and YAML variants).
Application properties packaged inside your jar (application.properties and YAML variants).
#PropertySource annotations on your #Configuration classes.
Default properties (specified using SpringApplication.setDefaultProperties).
But I don't like this. How can I change it?
I found a way to achieve this. open source!!!!
App.java (main method)
public class App {
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(AppConfig.class);
SpringApplication app = builder.web(true).listeners(new AppListener()).build(args);
app.run();
}
}
AppListener.java
public class AppListener implements GenericApplicationListener {
public static final String APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME = "applicationConfigurationProperties";
#Override
public boolean supportsEventType(ResolvableType eventType) {
return ApplicationPreparedEvent.class.getTypeName().equals(eventType.getType().getTypeName());
}
#Override
public boolean supportsSourceType(Class<?> sourceType) {
return true;
}
#Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationPreparedEvent) {
ApplicationPreparedEvent _event = (ApplicationPreparedEvent) event;
ConfigurableEnvironment env = _event.getApplicationContext().getEnvironment();
// change priority order application.properties in PropertySources
PropertySource ps = env.getPropertySources().remove(APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME);
env.getPropertySources().addFirst(ps);
// logging.config is my testing property. VM parameter -Dlogging.config=xxx will be override by application.properties
System.out.println(env.getProperty("logging.config"));
}
}
#Override
public int getOrder() {
return 0;
}
}
I have 2 jars with a dependency:
clientKit.jar - contains a FeignClient.java and spring #Configuration file defining the #Bean for creating the FeignClient.java:
#Configuration
public class ConnectorConfig{
#Value("${FeignClient.Host}")
private String FeignClientHost;
#Bean
public FeignClientCls feignClientCls()
{
FeignClientCls connector = Feign.builder()
.target(FeignClientCls.class, feignClientnHost);
return connector;
}
}
services.jar - contains logic. #Inject feignClient.java and uses #PropertySource to specify the .properties file containing FeignClientHost
Configuration File:
#Configuration
#PropertySource("file:/C:/projects-path/app.properties")
public class AppConfig
{
#Inject
ApplicationContext applicationContext;
// omitted code
}
Injecting the FeignClientCls (in services.jar)
public class CallFeignClient{
#Inject
protected FeignClientCls connector;
public void execute(){
connector.someMethod();
}
}
and an external app.property file defining the host and port:
FeignClient.Host=https://localhost:8444
I'm getting below exception when spring starts:
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'FeignClient.Host' in string value "${FeignClient.Host}"
I checked in debug and i see that Spring doesn't have the app.properties in the PropertySourcesPropertyResolver.propertySources list when trying to obtain the value.
My question is - Can this be done and is this the correct way?
if so, why do i get the exception...
I have a similar code implemented in a different project and it works.
Thanks!