Upload to S3 with resourceLoader - Spring Boot - spring

I'm trying to upload a file to my s3 bucket without AWS SDK, using only Spring cloud with resourceLoader bean.
I have this code:
private fun uploadS3(awsFileName: String, content: String): String {
val writableResource = resourceLoader.getResource(awsFileName) as WritableResource
writableResource.outputStream.use { it.write(content.toByteArray()) }
return writableResource.url.toString()
}
My application.yml has this configuration:
cloud:
aws:
credentials:
accessKey: XXXXX
secretKey: XXXXX
instanceProfile: false
region:
static: us-east-1
auto: false
s3:
default-bucket: XXXXXX
My Spring Boot version is:
springBootVersion = '2.0.2.RELEASE'
But all I get is this error:
There is no EC2 meta data available, because the application is not running in the EC2 environment. Region detection is only possible if the application is running on a EC2 instance
And I just don't know how to solve this issue. Please, help me!

You could use Spring Content S3 which uses the SimpleStorageResourceLoader underneath the covers.
Add the following dependencies to your pom.xml
pom.xml
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>content-s3-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency>
Add the following configuration that creates a SimpleStorageResourceLoader bean:
#Autowired
private Environment env;
public Region region() {
return Region.getRegion(Regions.fromName(env.getProperty("AWS_REGION")));
}
#Bean
public BasicAWSCredentials basicAWSCredentials() {
return new BasicAWSCredentials(env.getProperty("AWS_ACCESS_KEY_ID"), env.getProperty("AWS_SECRET_KEY"));
}
#Bean
public AmazonS3 client(AWSCredentials awsCredentials) {
AmazonS3Client amazonS3Client = new AmazonS3Client(awsCredentials);
amazonS3Client.setRegion(region());
return amazonS3Client;
}
#Bean
public SimpleStorageResourceLoader simpleStorageResourceLoader(AmazonS3 client) {
return new SimpleStorageResourceLoader(client);
}
Create a "Store":
S3Store.java
public interface S3Store extends Store<String> {
}
Autowire this store where you need to upload resource:
#Autowired
private S3Store store;
WritableResource r = (WritableResource)store.getResource(getId());
InputStream is = // your input stream
OutputStream os = r.getOutputStream();
IOUtils.copy(is, os);
is.close();
os.close();
When your application starts it will see the dependency on spring-content-s3 and your S3Store interface and inject an implementation for you therefore you don't need to worry about implementing it yourself.
HTH

Related

Multiple Redis For Spring Cache And JWT

I have actually an application that is using Redis for Cache and keeping the JWT token into redis.
My goal is to have one redis for cache, and another one for jwt token.
I dont understand how i can achieve this.
How can i say to spring to use a specific redis (here "redis-cache") for caching ?
Actually i only put #EnableCaching and it is working properly
Thanks for any help
spring:
redis:
port: 7000
password: password123
host: 127.0.0.1
redis-cache: # New One
port: 7001
password: password123
host: 127.0.0.1
I'm using redisTemplate to keep jwt token into redis
#Configuration
public class GenericBeanConfig {
#Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
Jackson2JsonRedisSerializer<String> jrs = new Jackson2JsonRedisSerializer<String>(String.class);
template.setKeySerializer(jrs);
template.setConnectionFactory(connectionFactory);
return template;
}
}
...
#EnableCaching
public class ProjectsApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectsApplication .class, args);
}
}
Caching some endpoint
#Cacheable(value = "users-rbac")
public UserResponseDTO search(#PathVariable String email) {
return userService.search(email);
}
You can define your custom CacheManager as well, you define your cache manager as follows. Customize this as you see, for now host, port and password are fixed but you can read them from application config file using #Value .
#Bean
public CacheManager cacheManager() {
// create redis configuration
RedisStandaloneConfiguration configuration =
new RedisStandaloneConfiguration("127.0.0.1", 7001);
configuration.setPassword(RedisPassword.of("password"));
// create a connection factory, use other connection factory if you want
LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);
factory.afterPropertiesSet();
RedisCacheWriter writer = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
// define cache cnfiguration
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
// use Jackson serdes do not use JDK one
cacheConfiguration.serializeValuesWith(
SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// create cache manager
return new RedisCacheManager(writer, cacheConfiguration);
}

Spring boot application not fetching cloud configs on S3

I have a Java spring boot application with gradle. I have my config file in S3. This is the file I have:
description: Service Configuration
source:
profile: prod
server:
port: 8080
servlet:
context-path: /myservice-service
firebase:
authorization-header: "Basic XYZ"
base: baseurl
token-uri: tokenurl
it is named service-dev.yml on S3
I also have the appropriate prop classes:
#Configuration
#ComponentScan
#EnableConfigurationProperties(value = {
MyProps.class
})
public class PropConfiguration {
}
#Data
#Configuration
#Primary
#ConfigurationProperties(prefix = "firebase")
public class FirebaseProps {
private String authorizationHeader;
private String base;
private String tokenUri;
}
but when I try to use the props in my code, I get the error: "could not resolve placeholder in string". For instance when I do "${firebase.base}".
When I run in intelliJ, I have the environment variables set to my S3 bucket with the password and such.
Any suggestions on where I may be going wrong?

How to create multiple beans (same type) in one Spring Boot java config class (#Configuration)?

In my yaml file, I have config values as below:
myapp:
rest-clients:
rest-templates:
- id: myService1
username: chris
password: li
base-url: http://localhost:3000/service1
read-timeout: 2s
connect-timeout: 1s
- id: myService2
username: chris
password: li
base-url: http://localhost:3000/service1
read-timeout: 2s
connect-timeout: 1s
I want to Spring Boot 2 app register a RestTemplate for each config items.
My configuration is bean is below:
#Configuration
#AllArgsConstructor
public class MyAppRestClientsConfiguration {
private MyAppRestClientsProperties properties;
private GenericApplicationContext applicationContext;
private RestTemplateBuilder restTemplateBuilder;
#PostConstruct
public void init() {
properties.getRestTemplates().forEach(this::registerRestTemplate);
}
private void registerRestTemplate(MyAppRestTemplateConfig config) {
// do some work
applicationContext.registerBean(config.getId(), RestTemplate.class, () -> restTemplate)
}
}
The problem is that when I inject my registered RestTemplate via #Autowire, this config bean has not finished init yet. So there is no RestTemplate bean could be injected.
#Autowired
#Qualifier("myService1")
private RestTemplate client1;
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
- #org.springframework.beans.factory.annotation.Qualifier(value=myService1)
Is there any correct way to implement this requirement?
The problem with registering new beans in a #PostConstruct annotated method is that Spring is already past that particular point in the Spring life cycle (more info on the Spring life cycle). Sometimes an annotation such as #DependsOn (already mentioned), #Order, or #Lazy might help. However, as you mentioned you'd rather not force (spring) implementation details upon projects that make use of your library, I've written a BeanFactoryPostProcessor that registers a RestTemplate bean:
#Component
public class DemoBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) {
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
genericBeanDefinition.setBeanClass(RestTemplate.class);
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(Integer.valueOf(configurableListableBeanFactory.resolveEmbeddedValue("${rest-templates[0].read-timeout}")));
factory.setConnectTimeout(Integer.valueOf(configurableListableBeanFactory.resolveEmbeddedValue("${rest-templates[0].connect-timeout}")));
// etc
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addGenericArgumentValue(factory);
genericBeanDefinition.setConstructorArgumentValues(constructorArgumentValues);
String beanId = configurableListableBeanFactory.resolveEmbeddedValue("${rest-templates[0].id}");
((DefaultListableBeanFactory) configurableListableBeanFactory).registerBeanDefinition(beanId, genericBeanDefinition);
}
}
application.yml:
rest-templates:
- id: myService1
username: chris
password: li
base-url: http://localhost:3000/service1
read-timeout: 2000
connect-timeout: 1000
- id: myService2
username: chris
password: li
base-url: http://localhost:3000/service1
read-timeout: 2000
connect-timeout: 1000
Accompanying test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class DemoApplicationTests {
#Autowired
#Qualifier("myService1")
private RestTemplate restTemplate;
#Test
public void demoBeanFactoryPostProcessor_shouldRegisterBean() {
String stackOverflow =
restTemplate.getForObject("https://stackoverflow.com/questions/57122343/how-to-create-multiple-beans-same-type-in-one-spring-boot-java-config-class", String.class);
Assertions.assertThat(stackOverflow).contains("How to create multiple beans (same type) in one Spring Boot java config class (#Configuration)?");
}
}
As the BeanFactoryPostProcessor is invoked before the application context is fully set up, I had to find a different way to retrieve the application properties. I used the method ConfigurableListableBeanFactory#resolveEmbeddedValue to retrieve placeholder values instead of having them injected by an #Value annotation or environment#getProperty. Furthermore, I rewrote the property value 2s to 2000 as the HttpComponentsClientHttpRequestFactory required an int value.
You can levarage the binding mechanism that Spring Boot uses for the #ConfigurationPropeties. The corresponding beans can then be generated dynamically from a map of configurations
#Configuration
public class MyConfiguration implements BeanFactoryPostProcessor, EnvironmentAware {
private Environment environment;
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) {
Binder.get(environment)
.bind("com.example", FooMapProps.class)
.get()
.getMap()
.forEach((name, props) -> configurableListableBeanFactory.registerSingleton(name + "FooService", new FooService(props.getGreeting())));
}
}
In this way you can create multiple FooServices with different configurations based on your YAML config
com:
example:
map:
hello:
greeting: 'hello there!'
hey:
greeting: 'hey ho :)'
The corresponding properties class looks like this
#ConfigurationProperties(prefix = "com.example")
public class FooMapProps {
private Map<String, FooProps> map;
public Map<String, FooProps> getMap() {
return map;
}
public void setMap(Map<String, FooProps> map) {
this.map = map;
}
public static class FooProps {
private String greeting;
public String getGreeting() {
return greeting;
}
public void setGreeting(String greeting) {
this.greeting= greeting;
}
}
}
In this example two FooService beans has been created with different greetings as constructor paramters. The beans can be accessed by the qualifiers 'helloFooService' and 'heyFooService'.

Spring #PropertySource using YAML

Spring Boot allows us to replace our application.properties files with YAML equivalents. However, I seem to hit a snag with my tests. If I annotate my TestConfiguration (a simple Java config), it is expecting a properties file.
For example this doesn't work:
#PropertySource(value = "classpath:application-test.yml")
If I have this in my YAML file:
db:
url: jdbc:oracle:thin:#pathToMyDb
username: someUser
password: fakePassword
And I'd be leveraging those values with something like this:
#Value("${db.username}") String username
However, I end up with an error like so:
Could not resolve placeholder 'db.username' in string value "${db.username}"
How can I leverage the YAML goodness in my tests as well?
Spring-boot has a helper for this, just add
#ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
at the top of your test classes or an abstract test superclass.
Edit: I wrote this answer five years ago. It doesn't work with recent versions of Spring Boot. This is what I do now (please translate the Kotlin to Java if necessary):
#TestPropertySource(locations=["classpath:application.yml"])
#ContextConfiguration(
initializers=[ConfigFileApplicationContextInitializer::class]
)
is added to the top, then
#Configuration
open class TestConfig {
#Bean
open fun propertiesResolver(): PropertySourcesPlaceholderConfigurer {
return PropertySourcesPlaceholderConfigurer()
}
}
to the context.
As it was mentioned #PropertySource doesn't load yaml file. As a workaround load the file on your own and add loaded properties to Environment.
Implemement ApplicationContextInitializer:
public class YamlFileApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
Resource resource = applicationContext.getResource("classpath:file.yml");
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
PropertySource<?> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
applicationContext.getEnvironment().getPropertySources().addFirst(yamlTestProperties);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Add your initializer to your test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
#Test
public test(){
// test your properties
}
}
#PropertySource can be configured by factory argument. So you can do something like:
#PropertySource(value = "classpath:application-test.yml", factory = YamlPropertyLoaderFactory.class)
Where YamlPropertyLoaderFactory is your custom property loader:
public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
if (resource == null){
return super.createPropertySource(name, resource);
}
return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null);
}
}
Inspired by https://stackoverflow.com/a/45882447/4527110
Another option is to set the spring.config.location through #TestPropertySource:
#TestPropertySource(properties = { "spring.config.location = classpath:<path-to-your-yml-file>" }
#PropertySource only supports properties files (it's a limitation from Spring, not Boot itself). Feel free to open a feature request ticket in JIRA.
From Spring Boot 1.4, you can use the new #SpringBootTest annotation to achieve this more easily (and to simplify your integration test setup in general) by bootstrapping your integration tests using Spring Boot support.
Details on the Spring Blog.
As far as I can tell, this means you get all the benefits of Spring Boot's externalized config goodness just like in your production code, including automatically picking up YAML config from the classpath.
By default, this annotation will
... first attempt to load #Configuration from any inner-classes, and if that fails, it will search for your primary #SpringBootApplication class.
but you can specify other configuration classes if required.
For this particular case, you can combine #SpringBootTest with #ActiveProfiles( "test" ) and Spring will pick up your YAML config, provided it follows the normal Boot naming standards (i.e. application-test.yml).
#RunWith( SpringRunner.class )
#SpringBootTest
#ActiveProfiles( "test" )
public class SpringBootITest {
#Value("${db.username}")
private String username;
#Autowired
private MyBean myBean;
...
}
Note: SpringRunner.class is the new name for SpringJUnit4ClassRunner.class
The approach to loading the yaml properties, IMHO can be done in two ways:
a. You can put the configuration in a standard location - application.yml in the classpath root - typically src/main/resources and this yaml property should automatically get loaded by Spring boot with the flattened path name that you have mentioned.
b. The second approach is a little more extensive, basically define a class to hold your properties this way:
#ConfigurationProperties(path="classpath:/appprops.yml", name="db")
public class DbProperties {
private String url;
private String username;
private String password;
...
}
So essentially this is saying that load the yaml file and populate the DbProperties class based on the root element of "db".
Now to use it in any class you will have to do this:
#EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {
#Autowired private DbProperties dbProperties;
}
Either of these approaches should work for you cleanly using Spring-boot.
Since Spring Boot 2.4.0 you can use ConfigDataApplicationContextInitializer as follows:
#SpringJUnitConfig(
classes = { UserAccountPropertiesTest.TestConfig.class },
initializers = { ConfigDataApplicationContextInitializer.class }
)
class UserAccountPropertiesTest {
#Configuration
#EnableConfigurationProperties(UserAccountProperties.class)
static class TestConfig { }
#Autowired
UserAccountProperties userAccountProperties;
#Test
void getAccessTokenExpireIn() {
assertThat(userAccountProperties.getAccessTokenExpireIn()).isEqualTo(120);
}
#Test
void getRefreshTokenExpireIn() {
assertThat(userAccountProperties.getRefreshTokenExpireIn()).isEqualTo(604800);
}
}
See also: https://www.baeldung.com/spring-boot-testing-configurationproperties#YAML-binding
I found a workaround by using #ActiveProfiles("test") and adding an application-test.yml file to src/test/resources.
It ended up looking like this:
#SpringApplicationConfiguration(classes = Application.class, initializers = ConfigFileApplicationContextInitializer.class)
#ActiveProfiles("test")
public abstract class AbstractIntegrationTest extends AbstractTransactionalJUnit4SpringContextTests {
}
The file application-test.yml just contains the properties that I want to override from application.yml (which can be found in src/main/resources).
I have tried all of the listed questions, but all of them not work for my task: using specific yaml file for some unit test.
In my case, it works like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(initializers = {ConfigFileApplicationContextInitializer.class})
#TestPropertySource(properties = {"spring.config.location=file:../path/to/specific/config/application.yml"})
public class SomeTest {
#Value("${my.property.value:#{null}}")
private String value;
#Test
public void test() {
System.out.println("value = " + value);
}
}
it's because you have not configure snakeyml.
spring boot come with #EnableAutoConfiguration feature.
there is snakeyml config too when u call this annotation..
this is my way:
#Configuration
#EnableAutoConfiguration
public class AppContextTest {
}
here is my test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(
classes = {
AppContextTest.class,
JaxbConfiguration.class,
}
)
public class JaxbTest {
//tests are ommited
}
I needed to read some properties into my code and this works with spring-boot 1.3.0.RELEASE
#Autowired
private ConfigurableListableBeanFactory beanFactory;
// access a properties.yml file like properties
#Bean
public PropertySource properties() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("properties.yml"));
propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
// properties need to be processed by beanfactory to be accessible after
propertySourcesPlaceholderConfigurer.postProcessBeanFactory(beanFactory);
return propertySourcesPlaceholderConfigurer.getAppliedPropertySources().get(PropertySourcesPlaceholderConfigurer.LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME);
}
Loading custom yml file with multiple profile config in Spring Boot.
1) Add the property bean with SpringBootApplication start up as follows
#SpringBootApplication
#ComponentScan({"com.example.as.*"})
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean
#Profile("dev")
public PropertySourcesPlaceholderConfigurer propertiesStage() {
return properties("dev");
}
#Bean
#Profile("stage")
public PropertySourcesPlaceholderConfigurer propertiesDev() {
return properties("stage");
}
#Bean
#Profile("default")
public PropertySourcesPlaceholderConfigurer propertiesDefault() {
return properties("default");
}
/**
* Update custom specific yml file with profile configuration.
* #param profile
* #return
*/
public static PropertySourcesPlaceholderConfigurer properties(String profile) {
PropertySourcesPlaceholderConfigurer propertyConfig = null;
YamlPropertiesFactoryBean yaml = null;
propertyConfig = new PropertySourcesPlaceholderConfigurer();
yaml = new YamlPropertiesFactoryBean();
yaml.setDocumentMatchers(new SpringProfileDocumentMatcher(profile));// load profile filter.
yaml.setResources(new ClassPathResource("env_config/test-service-config.yml"));
propertyConfig.setProperties(yaml.getObject());
return propertyConfig;
}
}
2) Config the Java pojo object as follows
#Component
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(Include.NON_NULL)
#ConfigurationProperties(prefix = "test-service")
public class TestConfig {
#JsonProperty("id")
private String id;
#JsonProperty("name")
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3) Create the custom yml (and place it under resource path as follows,
YML File name : test-service-config.yml
Eg Config in the yml file.
test-service:
id: default_id
name: Default application config
---
spring:
profiles: dev
test-service:
id: dev_id
name: dev application config
---
spring:
profiles: stage
test-service:
id: stage_id
name: stage application config
<dependency>
<groupId>com.github.yingzhuo</groupId>
<artifactId>spring-boot-stater-env</artifactId>
<version>0.0.3</version>
</dependency>
Welcome to use my library. Now yaml, toml, hocon is supported.
Source: github.com
This is not an answer to the original question, but an alternative solution for a need to have a different configuration in a test...
Instead of #PropertySource you can use -Dspring.config.additional-location=classpath:application-tests.yml.
Be aware, that suffix tests does not mean profile...
In that one YAML file one can specify multiple profiles, that can kind of inherit from each other, read more here - Property resolving for multiple Spring profiles (yaml configuration)
Then, you can specify in your test, that active profiles (using #ActiveProfiles("profile1,profile2")) are profile1,profile2 where profile2 will simply override (some, one does not need to override all) properties from profile1.
project demo url: https://github.com/Forest10/spring-boot-family/tree/spring-boot-with-yml
I run this answer in my prod env!!! so if you against this ans. please test first!!!
There is no need to add like YamlPropertyLoaderFactory or YamlFileApplicationContextInitializer. You should convert your idea
Follow these steps:
Just add applicationContext.xml like
#ImportResource({"classpath:applicationContext.xml"})
to your ApplicationMainClass.
and your applicationContext.xml should write like this
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-autowire="byName"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:property-placeholder location="classpath*:*.yml"/>
</beans>
This can help scan your application-test.yml
db:
url: jdbc:oracle:thin:#pathToMyDb
username: someUser
password: fakePassword
Enhancing Mateusz Balbus answer.
Modified YamlFileApplicationContextInitializer class where YAML location is defined per test class. It does not work per test, unfortunately.
public abstract class YamlFileApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
/***
* Return location of a YAML file, e.g.: classpath:file.yml
*
* #return YAML file location
*/
protected abstract String getResourceLocation();
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
Resource resource = applicationContext.getResource(getResourceLocation());
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
PropertySource<?> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
applicationContext.getEnvironment().getPropertySources().addFirst(yamlTestProperties);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Usage:
Create subclass of YamlFileApplicationContextInitializer with defined getResourceLocation() method and add this subclass into #SpringApplicationConfiguration annotation.
This way it is easiest to make the test class itself.
#RunWith(SpringRunner.class)
#SpringApplicationConfiguration(classes = Application.class, initializers = SimpleTest.class)
public class SimpleTest extends YamlFileApplicationContextInitializer {
#Override
protected String getResourceLocation() {
return "classpath:test_specific.yml";
}
#Test
public test(){
// test your properties
}
}
Here's an improved version of YamlPropertyLoaderFactory which supports the new PropertySource.ignoreResourceNotFound, based on this answer:
Java:
public final class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
private final YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader();
#NotNull
public PropertySource createPropertySource(
#Nullable String name,
#NotNull EncodedResource resource
) {
try {
String parsedName;
if (name != null && !name.equals(""))
parsedName = name;
else parsedName = resource.getResource().getFilename();
return yamlPropertySourceLoader.load(parsedName, resource.getResource()).get(0);
} catch (Exception e) {
Exception possibleFileNotFoundException = ExceptionUtils.throwableOfType(e, FileNotFoundException.class);
throw possibleFileNotFoundException != null ? possibleFileNotFoundException : e;
}
}
}
// Usage
#PropertySource(
value = {"file:./my-optional-config.yml"},
factory = YamlPropertyLoaderFactory.class,
ignoreResourceNotFound = true
)
Kotlin:
class YamlPropertyLoaderFactory : DefaultPropertySourceFactory() {
private val yamlPropertySourceLoader = YamlPropertySourceLoader()
override fun createPropertySource(
name: String?,
resource: EncodedResource
): PropertySource<*> = try {
(
yamlPropertySourceLoader.load(
if (name != null && name.isNotBlank()) name else resource.resource.filename,
resource.resource
)
)[0]
} catch (e: Exception) {
throw ExceptionUtils.throwableOfType(e, FileNotFoundException::class.java) ?: e
}
}
// Usage
#PropertySource(
value = ["file:/my-optional-config.yml"],
factory = YamlPropertyLoaderFactory::class,
ignoreResourceNotFound = true
)

Can a PropertiesFactoryBean read a value from application.yml?

My project has a dependency that requires a set a properties object that can be read by #Value annotations:
#Value("#{myProps['property.1']}")
To do this in JavaConfig, I'm using the following:
#Bean(name="myProps")
public static PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("myprops.properties"));
return bean;
}
This works as expected. My properties look as follows:
property.1=http://localhost/foo/bar
property.2=http://localhost/bar/baz
property.3=http://localhost/foo/baz
I'm using Spring Boot for this project, so I'd love to be able to do something like the following:
myprops.properties:
property.1=${base.url}/foo/bar
property.2=${base.url}/bar/baz
property.3=${base.url}/foo/baz
Then I could configure the base.url based on different profiles.
application.yml:
base:
url: http://localhost
---
spring:
profiles: staging
base:
url: http://staging
---
spring:
profiles: production
base:
url: http://production
I've tried to do this and it doesn't work. As a workaround, I've created three different .properties files (myprops.properties, myprops-staging.properties, etc.) and loaded them with three different #Configuration classes. This works, but seems cumbersome.
#Configuration
public class DefaultConfiguration {
#Bean(name="myProps")
public static PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("myprops.properties"));
return bean;
}
}
#Configuration
#Profile("staging")
public class StagingConfiguration {
#Bean(name="myProps")
public static PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("myprops-staging.properties"));
return bean;
}
}
#Configuration
#Profile("production")
public class ProductionConfiguration {
#Bean(name="myProps")
public static PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("myprops-production.properties"));
return bean;
}
}
Is it possible to configure my PropertiesFactoryBean to read values from application.yml? If not, is there an easier way to configure properties with JavaConfig?
I ended up doing this programmatically and it gives me the behavior I was looking for:
#Value("${base.url}")
private String baseUrl;
#Bean(name = "myProps")
public PropertiesFactoryBean mapper() throws IOException {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("myprops.properties"));
bean.afterPropertiesSet();
// replace ${base.url} in values
Properties props = bean.getObject();
Enumeration names = props.propertyNames();
while (names.hasMoreElements()) {
String name = names.nextElement().toString();
String value = props.getProperty(name);
if (value.contains("${base.url}")) {
props.setProperty(name, value.replace("${base.url}", baseUrl));
}
}
bean.setLocalOverride(true);
bean.setProperties(props);
bean.afterPropertiesSet();
if (log.isDebugEnabled()) {
log.debug("Base URL: " + baseUrl);
}
return bean;
}
I'm not 100% sure what you are really looking do there, and you don't really say what doesn't work, but it looks like you want to mix YAML and properties formats for your external config? Why not just use "application.yml"? If I were doing something like that, and I needed to use a properties file for some reason as well, then I would use #PropertySource on one of my SpringApplication source files (that way the placeholders should be replaced when the values are resolved I think).

Resources