How can I check, if ${service.property} is not an empty string and if so, throw some kind of readable exception? It must happen during Bean creation.
#Component
public class Service {
#Value("${service.property}")
private String property;
}
I am looking for the easiest way (least written code). Would be great if by using annotations.
My current solution is to perform "handwritten" validation inside setter for the property, but is a little too much code for such easy thing.
Hint: I looked for some way to use the SpEL, since I use it already inside #Value, but as far I have found out, it would not be that easy/clean. But could have overlooked something.
Clarification: Expected behaviour is, that the application will not start up. The goal is to assure, that all properties are set, and especially, that string properties are not empty. Error should say clearily, what is missing. I don't want to set any defaults! User must set it all.
You can use the component as a property placeholder itself. And then you may use any validation that you want.
#Component
#Validated
#PropertySource("classpath:my.properties")
#ConfigurationProperties(prefix = "my")
public class MyService {
#NotBlank
private String username;
public void setUsername(String username) {
this.username = username;
}
...
}
And your my.properties file will look like this:
my.username=felipe
What you have there will work. If you don't include the property in your properties file you will receive a org.springframework.beans.factory.BeanCreationException exception on server start up.
Apr 22, 2015 9:47:37 AM org.apache.catalina.core.ApplicationContext log
SEVERE: StandardWrapper.Throwable
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.lang.String com.util.Service.property; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'service.property' in string value "${service.property}"
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:306)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1146)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:519)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
The alternative would be to use an initProperty to handle or set the value, here is where you could throw some kind of readable exception.
#Component
public class Service {
private String property;
#Autowired
public void initProperty(#Value("${service.property}") String property) {
if(property == null) {
// Error handling here
}
}
}
It really depends if you want the your application to start up regardless if the property is set and if not, throw a readable exception to the log or console then set it with a default value or if you want the error to be thrown at server start-up and bean creation.
I guess the third option would be to just set the value if none was given by using the default setter.
#Component
public class Service {
#Value("${service.property:'This is my default setter string'}")
private String property;
}
Here my solution, just put that class in your code (just fix the "my.package" String):
/**
* Validates the environment-dependent properties during application start. Finds all spring beans, which classes are in
* defined package, validates them and in case of error tries to log the property name (not class field name), taken
* from {#link Value} annotation.
*
* #author Tomasz
*/
#Component
public class ConfigurationChecker implements ApplicationListener<ContextRefreshedEvent> {
private static final Logger LOG = LoggerFactory.getLogger(ConfigurationChecker.class);
// this is a property, that is set in XML, so we bind it here to be found by checker. For properties wired directly in Beans using #Value just add validation constraints
#Value("${authorization.ldap.url}")
#NotBlank
private String ldapUrl;
private static final String FAIL_FAST_PROPERTY = "hibernate.validator.fail_fast";
private Validator validator = Validation.byDefaultProvider().configure().addProperty(FAIL_FAST_PROPERTY, "false")
.buildValidatorFactory().getValidator();
/**
* Performs the validation and writes all errors to the log.
*/
#SneakyThrows
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
LOG.info("Validating properties");
Set<ConstraintViolation<Object>> allViolations = new HashSet<>();
// Find all spring managed beans (including ConfigurationChecker)...
for (String beanName : event.getApplicationContext().getBeanDefinitionNames()) {
Object bean = event.getApplicationContext().getBean(beanName);
// ...but validate only ours.
if (bean.getClass().getCanonicalName().startsWith("my.package")) {
Set<ConstraintViolation<Object>> viol = this.validator.validate(bean);
LOG.info("Bean '" + beanName + "': " + (viol.isEmpty() ? " OK" : viol.size() + " errors found"));
allViolations.addAll(viol);
} else {
continue;
}
}
// if any error found...
if (allViolations.size() > 0) {
for (ConstraintViolation<Object> violation : allViolations) {
// ...extract "property.name" from field annotation like #Value("${property.name}")
String propertyName = violation.getLeafBean().getClass()
.getDeclaredField(violation.getPropertyPath().toString()).getAnnotation(Value.class).value();
propertyName = StringUtils.substring(propertyName, 2, -1);
// .. log it ..
LOG.error(propertyName + " " + violation.getMessage());
}
// ... and do not let the app start up.
throw new IllegalArgumentException("Invalid configuration detected. Please check the log for details.");
}
}
}
And here the test for it:
#RunWith(EasyMockRunner.class)
public class ConfigurationCheckerTest extends EasyMockSupport {
#TestSubject
private ConfigurationChecker checker = new ConfigurationChecker();
#Mock
private ContextRefreshedEvent event;
#Mock
private ApplicationContext applicationContext;
#Test(expected = IllegalArgumentException.class)
public void test() {
expect(this.event.getApplicationContext()).andReturn(this.applicationContext).anyTimes();
expect(this.applicationContext.getBeanDefinitionNames()).andReturn(new String[] { "configurationChecker" });
expect(this.applicationContext.getBean("configurationChecker")).andReturn(this.checker);
replayAll();
this.checker.onApplicationEvent(this.event);
verifyAll();
}
}
Related
I have following class, which publishes the spring event.
#Component
public class ApplicationReadyEventListener {
Boolean isHit = false;
#EventListener
public void handle(final ApplicationReadyEvent applicationReadyEvent) {
applicationReadyEvent.getSpringApplication().getClass().toGenericString()));
isHit = true; // This needs to be replaced with CustomLoggerComponent
}
}
As I need to publish the event and need to check failure and success event, I have following test:
#ExtendWith({SpringExtension.class})
class ApplicationReadyEventListenerTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner();
//Success Test
#Test
void loggerShouldLogWhenApplicationIsReady() {
SpringApplciation application = new SpringApplication(ApplicationReadyEventListener.class);
application.setWebApplicationType(WebApplicationType.NONE);
final ApplicationReadyEvent event = new ApplicationReadyEvent(application, null, mock(ConfigurableApplicationContext.class));
runner.withBean(ApplicationReadyEventListener.class)
.run(context -> {
context.publishEvent(event);
final ApplicationReadyEventListener applicationStartedListener = context.getBean(ApplicationReadyEventListener.class);
MatcherAssert.assertThat(applicationStartedListener.isHit(), is(true));
});
}
//FailureTest
#Test
void shouldNotCallApplicationStarted() {
SpringApplciation application = new SpringApplication(ApplicationReadyEventListener.class);
application.setWebApplicationType(WebApplicationType.NONE);
final RuntimeException runtimeException = new RuntimeException("Some Error Occurred");
final ApplicationEvent event = new ApplicationFailedEvent(application, null, mock(ConfigurableApplicationContext.class), runtimeException);
runner.withBean(ApplicationReadyEventListener.class)
.run(context -> {
context.publishEvent(event);
final ApplicationReadyEventListener applicationStartedListener = context.getBean(ApplicationReadyEventListener.class);
MatcherAssert.assertThat(applicationStartedListener.isHit(), is(false));
});
}
}
This is working fine as of now as the class (ApplicationReadyEventListener) does not have any bean. I want to have a custom logger for this one, and instead of isHit, I would be checking the side effect of custom logger's method getting called.
However, I could not add any dependency, so I tried to isolate the problem by creating a separate application which contains the subject under test ApplicationReadyEvent and to have CustomLoggerBean created, used following one:
#Configuration
public class CustomLogMockProvider {
#Bean
public Logger logger() {
return Mockito.mock(Logger.class);
}
}
And when I write this test for the same:
#Test
void tesCustomLoggerBeanPresence() {
SpringApplciation application = new SpringApplication(CustomLogger.class);
application.setWebApplicationType(WebApplicationType.NONE);
runner.withBean(CustomLogMockProvider.class)
.run(context -> {
String[] beanNamesForType = context.getBeanNamesForType(Logger.class);
Arrays.stream(beanNamesForType).forEach(System.out::println);
});
}
Getting UnsatisfiedDependencyException for the above one.
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'applicationReadyEventListener': Unsatisfied dependency expressed through constructor parameter 0: Could not convert argument value of type [java.lang.Class] to required type [com.priti.com.common.config.CustomLogger]: Failed to convert value of type 'java.lang.Class' to required type 'com.priti.com.common.config.CustomLogger'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Class' to required type 'com.priti.com.common.config.CustomLogger': no matching editors or conversion strategy found
Any lead on this would be helpful.
I think your test setup is the problem. You're doing far to much things by hand instead of using the proper Spring tooling.
If you want to test your ApplicationReadyEventListener the test should look like this:
#ExtendWith({SpringExtension.class})
#ContextConfiguration(classes = ApplicationReadyEventListener.class)
class ApplicationReadyEventListenerTest {
#MockBean
private SpringApplication springApplicationMock;
#MockBean
private CustomLogger customLoggerMock;
#Autowired
private ApplicationEventPublisher publisher;
#Test
void name() {
publisher.publishEvent(new ApplicationReadyEvent(springApplicationMock, null, null));
verify(customLoggerMock).doSomething();
}
}
You run a Spring test by using SpringExtension.class. You mock your CustomLogger as well as the SpringApplication. Now you can publish a ApplicationReadyEvent and verify that your listener has invoked the CustomLogger.
I have two spring bean. They are name is same but the class is defferent.
Here is the bean definitions.
This one is the first.
#Bean(name = "approve_sign_up_project_request|Task_1tm7e53")
public StudentTaskToResponseDataConverter perfectUserProfileVO() {
return studentTaskVO -> {
ResponseVo vo = toResponseVO(studentTaskVO);
vo.setData(new PerfectUserProfileVO());
return vo;
};
}
This one is the second
#Bean(name = "approve_sign_up_project_request|Task_1tm7e53")
public UserTaskCompleter perfectUserProfile() {
return new UserTaskCompleter() {
#Override
public void validate(Task task, CompleteUserTaskDTO dto) throws RuntimeException {
Long studentId = getStudentId(task);
UserProfileIntegrityValidatedResultDTO results = userService.
validateTheIntegrityOfUserProfile(studentId);
if (results.getComplete()) {
//nothing to do for now
}else {
LOGGER.error("Validated failed cause the student -- {} not yet perfected the profile",
studentId);
throw new UserProfileImperfectException("Missing fields are " + results.getMissingFields());
}
}
#Override
public void executeBusinessLogic(Task task, CompleteUserTaskDTO dto) {
}
#Override
public Map<String, Object> getTheVariablesForCompleterUserTask(Task task, CompleteUserTaskDTO dto) {
return null;
}
};
}
And When I use below code to get the bean the spring will throw an excetpion. But I did not understand the reason.
I think the spring will give me the right bean when I use the bean name and bean class to get it. But actully I am wrong the spring did not give it.
Here is the code of to get bean
private UserTaskCompleter getBean(CompleteUserTaskDTO dto) {
String beanName = dto.getProcessDefinitionKey() + "|" + dto.getActivityId();
return applicationContext.getBean(beanName, UserTaskCompleter.class);
}
Here is the exception
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'approve_sign_up_project_request|Task_1tm7e53' is expected to be of type 'com.hikedu.backend.camunda.beanconfig.taskcompleter.UserTaskCompleter' but was actually of type 'com.hikedu.backend.camunda.beanconfig.tasktoresponsedatecoverter.signupprojectprocess.SignUpProjectProcessTaskConverterConfig$$Lambda$625/484805627'
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:384)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1091)
at com.hikedu.backend.camunda.beanconfig.taskcompleter.BaseUserTaskCompleter.getBean(BaseUserTaskCompleter.java:45)
at com.hikedu.backend.camunda.beanconfig.taskcompleter.BaseUserTaskCompleter.validate(BaseUserTaskCompleter.java:29)
at com.hikedu.backend.service.impl.camunda.signupprojectprocess.BaseSignUpProjectProcessServiceImpl.completeUserTask(BaseSignUpProjectProcessServiceImpl.java:165)
at com.hikedu.backend.controller.SignUpProjectProcessUserTaskController.completerStudentTask(SignUpProjectProcessUserTaskController.java:93)
at com.hikedu.backend.controller.SignUpProjectProcessUserTaskController$$FastClassBySpringCGLIB$$a695dddd.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
Can someone tell me how to get the right bean by name when have two same name and different calss bean.
The answer that Himly quotes, does not let Spring create beans with the same name.
It actually prevents it from starting up, since building the application will fail.
If multiple beans are defined with the same name, then the one defined later will override the one defined earlier. As a result, in your case only one bean named approve_sign_up_project_request|Task_1tm7e53 will exist, unless you disable the bean definition overriding.
I already understaned the reason.
When definitioned two same name and different type bean. The spring will choose the last one to overriding others.
In my case there are just one bean named "approve_sign_up_project_request|Task_1tm7e53" and the type is StudentTaskToResponseDataConverter.
When I use the name and the UserTaskCompleter type to get the bean form beanFactory
the spring cannot find it and then the spring will throw the excetpion.
How to allow the spring create same name bean ?
I find the answer from here
Here is the import part of the answer
You may use an initializer when building your Spring Boot app:
#SpringBootApplication
public class SpringBootApp {
public static void main(String... args) {
new SpringApplicationBuilder(SpringBootApp.class)
.initializers(new ApplicationContextInitializer<GenericApplicationContext>() {
#Override
public void initialize(GenericApplicationContext applicationContext) {
applicationContext.setAllowBeanDefinitionOverriding(false);
}
})
.run(args);
}
}
Or with java 8:
new SpringApplicationBuilder(SpringBootApp.class)
.initializers((GenericApplicationContext c) -> c.setAllowBeanDefinitionOverriding(false) )
.run(args);
I'm writing some unit tests for a reactive web application that uses couchbase repositories, however the ApplicationContext fails to load due to a NoSuchBeanDefinitionException being thrown when trying to #Autowire a ReactiveCouchbaseRepository.
From src/main/java, the application runs just fine, and I can send requests from PostMan and get valid responses. No configuration class is necessary. My application.properties is set up like this:
spring.couchbase.bootstrap-hosts=subdomain.domain.com
spring.couchbase.bucket.name=some_bucket_name
spring.couchbase.bucket.password=a_password
spring.couchbase.env.timeouts.connect=10000
I created a src/test/resources folder, marked it as the testing resources folder (in IntelliJ), and copied over the application.properties, but still the same exception is thrown. I also created a couchbase config class in the test folder that looks like this:
#Configuration
#EnableCouchbaseRepositories
public class CouchbaseConfiguration extends AbstractCouchbaseConfiguration {
#Value("${spring.couchbase.bootstrap-hosts}")
private String hosts;
#Value("${spring.couchbase.bucket.name}")
private String bucketName;
#Value("${spring.couchbase.bucket.password}")
private String password;
#Override
protected List<String> getBootstrapHosts() {
return Collections.singletonList(hosts);
}
#Override
protected String getBucketName() {
return bucketName;
}
#Override
protected String getBucketPassword() {
return password;
}
}
My test class looks like this:
#RunWith(SpringRunner.class)
#WebFluxTest
public class ReactiveSpringApplicationTests {
#Autowired
WebTestClient webTestClient;
#Test
public void create_Test_Post_Event() throws Exception {
EventData eventData = new EventData();
eventData.setIdentifier("1111");
webTestClient.post().uri("/ad/acc")
.contentType(MediaType.APPLICATION_JSON_UTF8) // Set the media type of the body, as specified by the Content-Type header.
.accept(MediaType.APPLICATION_JSON_UTF8) // Set the list of acceptable media types, as specified by the Accept header.
.header("Corr", "0000")
.body(Mono.just(eventData), EventData.class) // Set the body of the request to the given asynchronous Publisher.
.exchange() // Perform the exchange without a request body.
.expectStatus().isCreated()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.expectBody(EventData.class);
}
}
The following exceptions are thrown (full exception stack omitted for brevity, only the first and last exception shown):
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.amex.gar.accounts.repository.CouchBaseDocRepository<?, ?>' available: expected at least 1 bean which qualifies as autowire candidate.
(CouchBaseDocRepository extends ReactiveCouchBaseRepository)
How do I get the ApplicationContext to properly recognize the couchbase repositories from the test directory?
I am trying to write up an integration test case with Spring Boot Test.
I customize the ConversionService to know about the new java.time types:
#Configuration
public class ConversionServiceConfiguration {
#Bean
public static ConversionService conversionService() {
final FormattingConversionService reg = new DefaultFormattingConversionService();
new DateTimeFormatterRegistrar().registerFormatters(reg);
return reg;
}
}
and then later expect it to work:
#Component
class MyServiceConfig {
#Value("${max-watch-time:PT20s}")
private Duration maxWatchTime = Duration.ofSeconds(20);
}
When running under the normal SpringApplication.run this seems to work fine. However, in my test case:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT, classes= {
MyServiceMain.class,
AttachClientRule.class
})
public class MyTest {
#Inject
#Rule
public AttachClientRule client;
#Test(expected=IllegalArgumentException.class)
public void testBad() throws Exception {
client.doSomethingIllegal();
}
}
it blows up:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AttachClientRule': Unsatisfied dependency expressed through constructor parameter 0:
Error creating bean with name 'MyServiceConfig': Unsatisfied dependency expressed through field 'maxWatchTime': Failed to convert value of type [java.lang.String] to required type [java.time.Duration];
nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [java.time.Duration]: no matching editors or conversion strategy found;
Peering deep into the guts of the TypeConverterDelegate that does the actual conversion, it seems to capture the ConversionService used from a field on the DefaultListableBeanFactory. Setting a watchpoint on where that field is set, I find the AbstractApplicationContext.refresh() method:
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh(); // <--- MyServiceConfig initialized here
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory); // <--- DefaultListableBeanFactory.conversionService set here!!!
// Last step: publish corresponding event.
finishRefresh();
So the #Value injection is happening before the ConversionService is applied to the BeanFactory. No bueno!
I've found what seems to be a workaround:
#Configuration
public class ConversionServiceConfiguration implements BeanFactoryPostProcessor {
#Bean
public static ConversionService conversionService() {
final FormattingConversionService reg = new DefaultFormattingConversionService();
new DateTimeFormatterRegistrar().registerFormatters(reg);
return reg;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.setConversionService(conversionService());
}
}
This forces the initialization to happen earlier on, but doesn't feel like the right solution (at least it's not documented as such).
Where have I gone wrong?
Spring 4.3.0, Spring Boot 1.4.0M3
EDIT
And now I've discovered another way for it to fail! Without making the same configuration class implement EnvironmentAware:
#Override
public void setEnvironment(Environment environment) {
((AbstractEnvironment) environment).setConversionService(conversionService());
}
I find that the PropertySourcesPropertyResolver uses the wrong (default) ConversionService. This is driving me mad!
Caused by: java.lang.IllegalArgumentException: Cannot convert value [PT15s] from source type [String] to target type [Duration]
at org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(PropertySourcesPropertyResolver.java:94)
at org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(PropertySourcesPropertyResolver.java:65)
at org.springframework.core.env.AbstractPropertyResolver.getProperty(AbstractPropertyResolver.java:143)
at org.springframework.core.env.AbstractEnvironment.getProperty(AbstractEnvironment.java:546)
at com.mycorp.DoSomething.go(DoSomething.java:103)
The Spring Boot developers have confirmed that this is poorly documented and does not work as specified: https://github.com/spring-projects/spring-boot/issues/6222
Try to remove static keyword from conversionService bean definition.
I am trying to write a unit test for the following method in my controller.
#Autowired
private ApplicationContext context;
private String getProperty() {
try {
Properties props = context.getBean("myProperties", Properties.class);
String val = props.getProperty("myProperty");
......
The Bean is declared like this in my applicationContext:
<util:properties id="myProperties" scope="prototype" location="file:${catalina.base}/webapps/myProperties.properties"/>
How can I mock this so that I can test different values of the val variable?
I thought about creating a test properties file and mocking it like this:
context = Mockito.mock(ApplicationContext.class);
Mocikto.when(context.getBean("myProperties", Properties.class)).thenReturn(some test file)
but then I would have to declare the test file as a bean somewhere.
I was wondering if there was an easier way to do this?
Thanks
If you're using spring-3, you can do:
<context:property-placeholder location="myprops.properties" />
And in your code:
#Value("${myProperty}")
private String myProp;
public String getMyProp() {
return myProp;
}
This causes myprops.properties to be made available for variable substitutions via ${...} expressions, and the #Value annotation allows value injection of properties. Then in your unit test you can simply set different values of myProp.
The easier way is to use a org.springframework.beans.factory.config.PropertyPlaceholderConfigurer instead of pulling the properties explicitly from the spring application context. The PropertyPlaceholderConfigurer injects your bean with the properties you specify. Then you don't need Mockito at all, in the test you set the property value in the Controller to whatever you want it to be.
So you'd set up the configurer in the application context xml:
<context:property-placeholder
location="file:${catalina.base}/webapps/myProperties.properties"/>
and add some configuration for your controller (I expect there's a way to do this with annotations but don't know it):
<bean id="whateverMyControllerIdIs" class="com.initech.foobar.MyControllerImpl">
<property name="quux"><value>${myProperty}</value></property>
</bean>
where the controller has an instance variable that you want to populate with the property, with a setter, like this:
String quux;
public void setQuux(String quux) {this.quux = quux;}
Just saw a blog post on Spring 3.1 enhancements, here's the new xml-free way to do this:
#Configuration
#PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
#Autowired
Environment env;
#Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
So I can test without having to load the Spring Context I use a Config class for accessing all of the properties file(s) values from within code. The benefits are:
1) Spring doesn't load in your unit tests
2) You can force an Exception if the property is missing and it is required
3) You can return strongly type property values from the getter() methods (i.e. convert to a Date)
4) The "key" values expected from your properties files are documented in a single Java class (i.e. public static final PROP_XXX)
#Component
public class Config {
public static final String PROP_USER_NAME = "user.name";
private Properties applicationProperties;
/*** Functional methods ***/
/**
* Helper method to ensure consistent Exception handling where requested property values are missing
* from the properties files and they are "required" for the application to function correctly.
*
* #param key
* #return The String value of the property requested
*/
private String readPropertyRequired(String key) {
String value = readProperty(key);
if(StringUtils.isBlank(value)) {
throw new PropertyNotFoundException(key);
}
return value;
}
/**
* Helper method to return String values from the properties files that have been loaded
*
* #param key
* #return The String value of the property requested or NULL if empty
*/
private String readProperty(String key) {
return applicationProperties.getProperty(key);
}
/*** Getters & Setters ***/
#Autowired
public void setApplicationProperties(Properties applicationProperties) {
this.applicationProperties = applicationProperties;
}
public String getUserName() {
return readPropertyRequired(PROP_USER_NAME);
}
}
You can then unit test this class by simply injecting a standard java.util.Properties