#SpringBootApplication
#EnableConfigurationProperties({GlobalProperties.class})
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
GlobalProperties globalProperties = context.getBean(GlobalProperties.class);
System.out.println(globalProperties);
}
}
#PropertySource("classpath:global.properties")
#ConfigurationProperties("app")
public class GlobalProperties {
private String error;
private List<Menu> menus = new ArrayList<>();
private Compiler compiler = new Compiler();
//getters and setters and tostring
public static class Menu {
private String name;
private String path;
private String title;
//getters and setters and tostring
}
public static class Compiler {
private String timeout;
private String outputFolder;
//getters and setters and tostring
}
#Override
public String toString() {
return "GlobalProperties [error=" + error + ", menus=" + menus + ", compiler=" + compiler + "]";
}
}
global.properties is in src/main/resources
#Logging
logging.level.org.springframework.web=ERROR
logging.level.com.mkyong=DEBUG
#Global
email=test#mkyong.com
thread-pool=10
#App
app.menus[0].title=Home
app.menus[0].name=Home
app.menus[0].path=/
app.menus[1].title=Login
app.menus[1].name=Login
app.menus[1].path=/login
app.compiler.timeout=5
app.compiler.output-folder=/temp/
app.error=/error/
This is the result when i run the above Application program
GlobalProperties [error=null, menus=[], compiler=Compiler{timeout='null', outputFolder='null'}]
But if i comment out #EnableConfigurationProperties({GlobalProperties.class}) annotation on Application class and add #Component annotation on GlobalProperties i am getting expected result
GlobalProperties [error=/error/, menus=[Menu{name='Home', path='/', title='Home'}, Menu{name='Login', path='/login', title='Login'}], compiler=Compiler{timeout='5', outputFolder='/temp/'}]
Why can't i use enableconfigurationproperty annotation here?
This should work
#PropertySource("classpath:global.properties")
#Configuration
#ConfigurationProperties("app")
public class GlobalProperties {
......
}
You have to use #ConfigurationProperties in conjuction with #Configuration or #Component.
Why ?
Its simple. Unless you have one of these annotations(#Configuration, #Component, #Service ... etc), it is a regular java class and not a spring bean, hencespring will not scan this class during startup. Which means, it cannot inject the properties.
#PropertySource annotation doesn't work without any of #Component or #Configuration annotations. You must properly register your properties file. You can do somethink like this:
#SpringBootApplication
#PropertySource("classpath:global.properties")
#EnableConfigurationProperties({GlobalProperties.class})
public class Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
GlobalProperties globalProperties = context.getBean(GlobalProperties.class);
System.out.println(globalProperties);
}
}
Or you can move related properties to application.properties file, that configured in spring boot by default.
GlobalProperties is a POJO the way you defined it, not a Spring Bean.
Annotating the class with #Component or #Configuration will make the class a Spring Bean and you will be able to retrieve it from the Spring Context.
To see how to use #EnableConfigurationProperties I recommend you to check this article: https://spring.io/blog/2013/10/30/empowering-your-apps-with-spring-boot-s-property-support.
Related
I do have ServiceImpl which looks like this:
#Service
#RequiredArgsConstructor
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
And I would like to inject a field value to fieldA in an Application.java from application.yml like this:
#EnableSwagger2
#SpringBootApplication
public class Application {
#Value("${fieldA}")
private String fieldA;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServiceA serviceA() {
return new ServiceAImpl(fieldA);
}
But I receive the following error when running SpringBoot app:
Error creating bean with name 'serviceAImpl' defined in URLNo qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Do you have any solution for that?
You annotated your class with #Service and defined it manually as a bean with the #Bean annotation. I do think the second is the way you planned to use it.
The #Service annotation will make this class get picked up by Spring's component scan and additionally create an instance of it.
Of course it tries to resolve the parameters and fails when it tries to find a matching "bean" for the String field because there is no simple String bean (and should not :) ).
Remove the #Service annotation and everything should work as expected.
Try this
#Service
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Autowire
public ServiceAImpl(#Value("${fieldA}") String fieldA){
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
}
and this
#EnableSwagger2
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You should not use #Service and #Bean for the same class!
Spring is not so smart :)
You should annotate your bean like:
#RequiredArgsConstructor
public class ServiceAImpl {
#Value("${fieldA}")
private final String something;
...
But I'm not sure it will work with the #RequiredFieldsConstructor, it would be simpler for you write down the constructor annotated with #Autowired and using the #Value annotation for the String parameter:
#Autowired
public ServiceAImpl(#Value("${aProp}") String string) {
You're using two bean declaration mechanisms:
You're registering your bean using #Service
You're registering a bean using #Bean
This means that your service will be created twice. The one defined using #Bean works properly, since it uses the #Value annotation to inject the proper value in your service.
However, the service created due to #Service doesn't know about the #Value annotation and will try to find any bean of type String, which it can't find, and thus it will throw the exception you're seeing.
Now, the solution is to pick either one of these. If you want to keep the #Bean configuration, you should remove the #Service annotation from ServiceAImpl and that will do the trick.
Alternatively, if you want to keep the #Service annotation, you should remove the #Bean declaration, and you should write your own constructor rather than relying on Lombok because this allows you to use the #Value annotation within the constructor:
#Service
public class ServiceAImpl implements ServiceA {
private final String fieldA;
/**
* This constructor works as well
*/
public ServiceAImpl(#Value("${fieldA}") String fieldA) {
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text){
return fieldA.equals(text);
}
}
If you want to declare ServiceAImpl as a Spring bean in your Java Configuration file, you should remove the #Service annotation from the class declaration. These annotations doesn't work well together.
ServiceAImpl.java
import org.springframework.beans.factory.annotation.Autowired;
public class ServiceAImpl implements ServiceA {
private final String fieldA;
#Autowired
public ServiceAImpl(String fieldA) {
this.fieldA = fieldA;
}
#Override
public boolean isFieldA(String text) {
return fieldA.equals(text);
}
}
Application.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class Application {
#Value("${fieldA}")
private String fieldA;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServiceA serviceA() {
return new ServiceAImpl(fieldA);
}
}
Your application.properties
fieldA=value
The below implementation works well for me. You have two issues, first you have to choose between #Service and #Bean and the other issue I've seen in your code was the #Value annotation, you have to use only to inject a value from the properties.
#SpringBootApplication
public class TestedValueApplication {
#Autowired
void printServiceInstance(ServiceA service) {
System.out.println("Service instance: " + service);
System.out.println("value==value? " + service.isFieldA("value"));
}
public static void main(String[] args) {
SpringApplication.run(TestedValueApplication.class, args);
}
#Bean
public ServiceA serviceA(#Value("${fieldA}") String fieldA) {
return new ServiceAImpl(fieldA);
}
}
Service:
public class ServiceAImpl implements ServiceA {
private String fieldA;
ServiceAImpl(String fieldA) {
this.fieldA = fieldA;
}
public boolean isFieldA(String text) {
return fieldA.equals(text);
}
}
application.properties:
fieldA=value
I have a Spring-Boot-Application as a multimodule-Project in maven. The structure is as follows:
Parent-Project
|--MainApplication
|--Module1
|--ModuleN
In the MainApplication project there is the main() method class annotated with #SpringBootApplication and so on. This project has, as always, an application.properties file which is loaded automatically. So I can access the values with the #Value annotation
#Value("${myapp.api-key}")
private String apiKey;
Within my Module1 I want to use a properties file as well (called module1.properties), where the modules configuration is stored. This File will only be accessed and used in the module. But I cannot get it loaded. I tried it with #Configuration and #PropertySource but no luck.
#Configuration
#PropertySource(value = "classpath:module1.properties")
public class ConfigClass {
How can I load a properties file with Spring-Boot and access the values easily? Could not find a valid solution.
My Configuration
#Configuration
#PropertySource(value = "classpath:tmdb.properties")
public class TMDbConfig {
#Value("${moviedb.tmdb.api-key}")
private String apiKey;
public String getApiKey() {
return apiKey;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Calling the Config
#Component
public class TMDbWarper {
#Autowired
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
public TMDbWarper(){
tmdbApi = new TmdbApi(tmdbConfig.getApiKey());
}
I'm getting an NullPointerException in the constructor when I autowire the warper.
For field injection:
Fields are injected right after construction of a bean, before any config methods are invoked. Such a config field does not have to be public. Refer Autowired annotation for complete usage. Use constructor injection in this case like below:
#Component
public class TMDbWarper {
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
#Autowired
public TMDbWarper(final TMDbConfig tmdbConfig){
this.tmdbConfig = tmdbConfig;
tmdbApi = new TmdbApi(tmdbConfig.getApiKey());
}
(or)
Use #PostConstruct to initialise like below:
#Component
public class TMDbWarper {
#Autowired
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
#PostConstruct
public void init() {
// any initialisation method
tmdbConfig.getConfig();
}
Autowiring is performed just after the creation of the object(after calling the constructor via reflection). So NullPointerException is expected in your constructor as tmdbConfig field would be null during invocation of constructor
You may fix this by using the #PostConstruct callback method as shown below:
#Component
public class TMDbWarper {
#Autowired
private TMDbConfig tmdbConfig;
private TmdbApi tmdbApi;
public TMDbWarper() {
}
#PostConstruct
public void init() {
tmdbApi = new TmdbApi(tmdbConfig.getApiKey());
}
public TmdbApi getTmdbApi() {
return this.tmdbApi;
}
}
Rest of your configuration seems correct to me.
Hope this helps.
Here is a Spring Boot multi-module example where you can get properties in different module.
Let's say I have main application module, dataparse-module, datasave-module.
StartApp.java in application module:
#SpringBootApplication
public class StartApp {
public static void main(String[] args) {
SpringApplication.run(StartApp.class, args);
}
}
Configuration in dataparse-module. ParseConfig.java:
#Configuration
public class ParseConfig {
#Bean
public XmlParseService xmlParseService() {
return new XmlParseService();
}
}
XmlParseService.java:
#Service
public class XmlParseService {...}
Configuration in datasave-module. SaveConfig.java:
#Configuration
#EnableConfigurationProperties(ServiceProperties.class)
#Import(ParseConfig.class)//get beans from dataparse-module - in this case XmlParseService
public class SaveConfig {
#Bean
public SaveXmlService saveXmlService() {
return new SaveXmlService();
}
}
ServiceProperties.java:
#ConfigurationProperties("datasave")
public class ServiceProperties {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
application.properties in datasave-module in resource/config folder:
datasave.message=Multi-module Maven project!
threads.xml.number=5
file.location.on.disk=D:\temp\registry
Then in datasave-module you can use all your properties either through #Value.
SaveXmlService.java:
#Service
public class SaveXmlService {
#Autowired
XmlParseService xmlParseService;
#Value("${file.location.on.disk: none}")
private String fileLocation;
#Value("${threads.xml.number: 3}")
private int numberOfXmlThreads;
...
}
Or through ServiceProperties:
Service.java:
#Component
public class Service {
#Autowired
ServiceProperties serviceProperties;
public String message() {
return serviceProperties.getMessage();
}
}
I had this situation before, I noticed that the properties file was not copied to the jar.
I made the following to get it working:
In the resources folder, I have created a unique package, then stored my application.properties file inside it. e.g: com/company/project
In the configuration file e.g: TMDBConfig.java I have referenced the full path of my .properties file:
#Configuration
#PropertySource("classpath:/com/company/project/application.properties")
public class AwsConfig
Build and run, it will work like magic.
You could autowire and use the Enviornment bean to read the property
#Configuration
#PropertySource(value = "classpath:tmdb.properties")
public class TMDbConfig {
#Autowired
private Environment env;
public String getApiKey() {
return env.getRequiredProperty("moviedb.tmdb.api-key");
}
}
This should guarantee that property is read from the context when you invoke the getApiKey() method regardless of when the #Value expression is resolved by PropertySourcesPlaceholderConfigurer.
I'm developing a web application with spring. I've had no problem autowiring and using database #Service classes. Now I'm trying to read a global property file and provide the values to all classes that need them. The solution I've come up with so far seem to be overly complicated (too many classes - AppConfig, ServerConfig iface, ElasticServerConfig) for such a trivial task but I could live with it if it worked.
my applicationContext.xml contains
<context:component-scan base-package="my.package" />
AppConfig.java:
package my.package.configuration;
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
}
ServerConfig.java:
public interface ServerConfig {
String getUrl();
String getUser();
String getPassword();
}
ElasticSearchConfig.java:
package my.package.configuration;
#Component(value = "elasticServerConfig")
public class ElasticServerConfig implements ServerConfig {
private static final Logger LOGGER = LogManager.getLogger(ElasticServerConfig.class);
private String url;
private String user;
private String password;
#Autowired
public ElasticServerConfig(final Environment env) {
this.url = env.getProperty("elastic_server.url");
this.user = env.getProperty("elastic_server.user");
this.password = env.getProperty("elastic_server.password");
LOGGER.debug("url=" + url + "; user=" + user + "; password=" + password); // this works!
}
#Override
public final String getUrl() {
return url;
}
#Override
public final String getUser() {
return user;
}
#Override
public final String getPassword() {
return password;
}
}
When the web application boots, the ElasticServerConfig constructor prints out the correct url/user/pwd as read from application.properties. However an instance of ElasticServerConfig is not injected into a Search object:
package my.package.util;
public class Search {
#Autowired
#Qualifier("elasticServerConfig")
private ServerConfig elasticServerConfig;
public final List<Foobar> findByPatternAndLocation() {
if (elasticServerConfig == null) {
LOGGER.error("elasticServerConfig is null!");
}
// and i get a NullPointerException further on
// snip
}
}
You have to register the Search class as a Spring Bean and take it from the Spring context when you want to use it. It's important to get the bean from the spring context. If you create an object of that class with new, Spring has no way to know about that class and mange it's dependencies.
You can get get a bean from the Spring context by #Autowire it somewhere or by accessing an instance of the context and use the getBean method:
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(AppConfig.class, args);
ctx.getBean...
}
}
Either use #Component annotation on the class and make sure that the class is in package thats under my.package
or register it in the configuration class
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
#Bean
public Search search(){
return new Search();
}
}
I am new to Spring & WebService and trying a few guides on Spring.io.
I planned to create a basic RESTful WebService which consumes Google Direction API and returns just the status.
Here are the classes:
Resource
#JsonIgnoreProperties(ignoreUnknown=true)
public class Direction {
// getters & setters
public Direction() {
super();
}
private String status;
public String toString() {
return status;
}
}
Controller
#Controller
public class Consumer {
public Consumer() {
super();
}
#Resource
private String url;
#Resource
private RestTemplate client;
#Resource
private String apiKey;
#RequestMapping(value = "/directions", method=RequestMethod.GET)
public #ResponseBody Direction consume(#RequestParam(value="source") String source, #RequestParam(value="destination") String destination) {
return consumeDirections(buildURI(source, destination));
}
// Builds URI
private String buildURI(...) {
...
}
private Direction consumeDirections(final String requestURI) {
return client.getForObject(requestURI, Direction.class);
}
}
Configuration v1
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Consumer.class, args);
}
}
Springconfig
http://pastebin.com/dsNVBWQq
Spring returns that No qualifying bean of type [java.lang.String] found for dependency.
This happens for all the beans in Consumer.
However, this works Configuration v2
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
#Resource
private Consumer consumer;
public void execute() {
System.out.println(consumer.consume("x", "z"));
}
public static void main(String[] args) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("application-config.xml");
context.getBean(Application.class).execute();
}
}
Some observations
#Resouce(Explicitly define bean) doesnt work for v1
SpringApplication is not aware of the Springconfig and fails during bean instantiation
I would like to understand why this issue crops up and how to resolve it?
The reason is very easy, the xml config is not loaded. have a look at Spring-Boot: XML Config
if you don't wanna touch existing xml, you need another #configuration annotated class and #ImportResource to load the xml configuration, just like the document says.
IMO, you don't need apiKey and url in the config, you should annotate them with #value, and define them in a .properties file. There are also default settings of spring boot, you get take advantage of it. like, name the properities application.properities and put it on classpath, spring boot will load it automatically.
I am trying to get a Spring-boot application going and I am not sure what I am doing wrong here. I have a application.properties file at src/main/resources & src/test/resources. I have an #Bean for my ConfigurationSettings so that I can use them throughout my application:
#Component
public class ConfigurationSettings {
private String product;
private String version;
private String copyright;
private String appName;
private String appDescription;
...
// getters and setters
}
Here is how I kick the application off:
#Configuration
#EnableJpaRepositories
#EnableAutoConfiguration
#EnableConfigurationProperties
#PropertySources(value = {#PropertySource("classpath:application.properties")})
#ComponentScan(basePackages = "com.product")
#EnableScheduling
public class OFAC {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run( OFAC.class, args );
}
And here is my configuration class:
#Configuration
#ComponentScan(basePackages = {"com.product"})
#PropertySources(value = {#PropertySource("classpath:application.properties")})
public class OFAConfiguration {
#Autowired
private Environment env;
#Bean
public ConfigurationSettings configurationSettings() {
ConfigurationSettings configurationSettings = new ConfigurationSettings();
configurationSettings.setAppDescription( env.getRequiredProperty("app.description" ) );
configurationSettings.setAppName( env.getRequiredProperty( "app.name" ) );
configurationSettings.setServerPort( env.getRequiredProperty( "server.port" ) );
return configurationSettings;
}
I am trying to use it in a controller:
#RestController
public class AboutController {
#Autowired
private ConfigurationSettings configurationSettings;
#RequestMapping(value = "/about", method = RequestMethod.GET)
public About index() {
String product = configurationSettings.getProduct();
String version = configurationSettings.getVersion();
String copyright = configurationSettings.getCopyright();
return new About( product, version, copyright );
}
}
However, when step thru this, all the values of ConfigurationSettings are null. I do have a test that successfully loads the values:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {OFAConfiguration.class})
public class OFAConfigurationTest {
#Autowired
private Environment environment;
#Autowired
private ConfigurationSettings configurationSettings;
#Test
public void testConfigurationLoads() {
assertNotNull(environment);
Assert.assertNotNull(configurationSettings);
}
#Test
public void testConfigurationSettingValues() {
assertEquals("Product Name", configurationSettings.getProduct());
assertEquals("0.0.1", configurationSettings.getVersion());
assertEquals("2014 Product", configurationSettings.getCopyright());
}
Can anyone see why the ConfigurationSettings are not being populated in my Controller?
Your configuration leads to 2 instances of the ConfigurationSettings class and probably one instance overrides the other.
The 'ConfigurationSettings' has the #Component annotation as you are scanning for components (#ComponentScan) this will lead to an instance. You also have a #Bean annotated method which also leads to an instance. The latter is overridden with the first.
In short remove the #Component annotation as that isn't needed because you already have a factory method for this class.
public class ConfigurationSettings { ... }
You should also remove the #PropertySource annotations as Spring-Boot will already load the application.properties for you.
Finally you should not use the #ContextConfiguration annotation on your test class but the #SpringApplicationConfiguration and pass in your application class (not your configuration class!).
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes=OFAC.class)
public class OFAConfigurationTest {
#Autowired
private Environment environment;
#Autowired
private ConfigurationSettings configurationSettings;
#Test
public void testConfigurationLoads() {
assertNotNull(environment);
assertNotNull(configurationSettings);
}
#Test
public void testConfigurationSettingValues() {
assertEquals("Product Name", configurationSettings.getProduct());
assertEquals("0.0.1", configurationSettings.getVersion());
assertEquals("2014 Product", configurationSettings.getCopyright());
}
This will fix your runtime configuration problems and will let your test use the power of Spring Boot to configure your application.