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.
Related
I cannot access #Value("${app.version}") or event environment.getProperty("app.version") or any property in my controllers or services.
My project structure looks like this
src/main/java
-configuration/
AppConfig.java
EnvConfig.java
JpaConfig.java
UiConfig.java
ServicesConfig.java
UiAppInitializer.java
-repositories/
....
-models/
....
-services/
....
-controllers/
....
My UiAppInitializer is pretty straight forward,
getRootConfigClassess() returns AppConfig.class and getServletConfigClasses() returns UiConfig.class
AppConfig.java
#Configuration
#Import({
EnvConfig.class,
UiConfig.class,
ServicesConfig.class
})
public class AppConfig{}
EnvConfig
#Configuration
public class EnvConfig implements InitializingBean {
#Value("${app.version}")
private String appVersion
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
pc.setLocations(new ClassPathResource("application.properties"));
return pc;
}
#Override
public void afterpropertiesSet() throws Exception {
log.debug("App Version is " + appVersion);
}
}
A simple controller
#RestController
#RequestMapping(value = "/version")
public class VersionContoller {
#Value("${app.version}")
private String version;
#GetMapping()
public String getVersion() {
return version;
}
}
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {
"my.packages.path.ui"
})
public class UiConfig implements WebMvcConfigurer {
....
}
The controller just returns "${app.version}" but the afterpropertiesSet correctly logs the version.
What am I doing wrong here? I have other controllers that connect to the repository successfully which was setup in JpaConfig that usues #Value for all the properties also
Note not using Spring Boot
It seems the controller is getting initialised as a bean before the properties() bean has had setLocations() called.
You could remove classpath scanning (I assume you have it on to find the controller bean?) and in your EnvConfig declare a new method that is a bean declaration for the Controller that passes in the version String. Obviously requiring a change to the controller constructor too
#Configuration
public class EnvConfig implements InitializingBean {
#Value("${app.version}")
private String appVersion
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer();
pc.setLocations(new ClassPathResource("application.properties"));
return pc;
}
#Bean
public VersionContoller controller() {
return new VersionController(appVersion);
}
#Override
public void afterpropertiesSet() throws Exception {
log.debug("App Version is " + appVersion);
}
}
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.
In the property file (config.properties) I defined several properties:
my.property.first=xyz
my.property.second=12
I created a class to read these properties:
package my.package.first;
............
#Configuration
public Class MyProperties {
#Value("${my.property.first}") private String propertyFirst;
#Value ("${my.property.second}") private String propertySecond;
public String getPropertyFirst() {
return propertyFirst;
}
public int getPropertySecond() {
return propertySecond
}
#Bean
public MyProperties getInstance() {
return this;
}
}
Now I want to use these properties in a class placed in some other package:
package my.otherpackage.third;
import my.property.package.first.MyProperties;
.............................
public class GetMyProperties{
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyProperties.class);
MyProperties myProperties =context.getBean("getInstance",MyProperties.class);
//This returns ${my.property.first}
String propertyFirst = myProperties.getPropertyFirst();
// This returns ${my.property.second}
int propertySecond = context.getBeanFactory().resolveEmbeddedValue(("${my.property.first}"));
}
I try to solve this by using only Annotations.
I use Spring 4.
Thanks
MyProperties isn't really a #Configuration class, it is a bean. Give it the annotation #Component and move the #Bean declaration to another #Configuration class. It doesn't matter where the bean is defined for spring (as long as it is in a configuration class), it'll pick it up if you have the #ComponentScan or #SpringBootApplication somewhere in your app, which I'm pretty sure you do. your MyProperties class you make #Component
You'll want:
#Component
public class MyProperties {
#Value("${my.property.first}") private String propertyFirst;
#Value ("${my.property.second}") private String propertySecond;
public String getPropertyFirst() {
return propertyFirst;
}
public void setPropertyFirst(String propertyFirst){
this.propertyFirst = propertyFirst;
}
public int getPropertySecond() {
return propertySecond
}
public void setPropertySecond(String propertySecond){
this.propertySecond= propertySecond;
}
}
#Configuration
public class Config {
#Bean
public MyProperties getInstance() {
return new MyProperties();
}
}
package my.otherpackage.third;
import my.property.package.first.MyProperties;
.....
#EnableAutoConfiguration
#ComponentScan(basePackages = {"my"})
public class GetMyProperties{
public static void main(String[] args){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(GetMyProperties.class);
MyProperties myProperties =context.getBean("getInstance",MyProperties.class);
//This returns the value of ${my.property.first}
String propertyFirst = myProperties.getPropertyFirst();
// This returns the value of ${my.property.second}
int propertySecond = myProperties.getPropertySecond();
}
}
I'm a newbie to Spring. I'm facing a problem with Spring-Boot. I'm trying to autowire a field from an external config file into an autowired bean. I have the following classes
App.java
public class App {
#Autowired
private Service service;
public static void main(String[] args) {
final SpringApplication app = new SpringApplication(App.class);
//app.setShowBanner(false);
app.run();
}
#PostConstruct
public void foo() {
System.out.println("Instantiated service name = " + service.serviceName);
}
}
AppConfig.java
#Configuration
#ConfigurationProperties
public class AppConfig {
#Bean
public Service service() {
return new Service1();
}
}
Service Interface
public interface Service {
public String serviceName ="";
public void getHistory(int days , Location location );
public void getForecast(int days , Location location );
}
Service1
#Configurable
#ConfigurationProperties
public class Service1 implements Service {
#Autowired
#Value("${serviceName}")
public String serviceName;
//Available in external configuration file.
//This autowiring is not reflected in the main method of the application.
public void getHistory(int days , Location location)
{
//history code
}
public void getForecast(int days , Location location )
{
//forecast code
}
}
I'm unable to display the service name variable in the postconstruct method of the App class. Am I doing this right?
You can load properties in different ways:
Imagine the following application.properties which is automatically loaded by spring-boot.
spring.app.serviceName=Boot demo
spring.app.version=1.0.0
Inject values using #Value
#Service
public class ServiceImpl implements Service {
#Value("${spring.app.serviceName}")
public String serviceName;
}
Inject values using #ConfigurationProperties
#ConfigurationProperties(prefix="spring.app")
public class ApplicationProperties {
private String serviceName;
private String version;
//setters and getters
}
You can access to this properties from another class using #Autowired
#Service
public class ServiceImpl implements Service {
#Autowired
public ApplicationProperties applicationProperties;
}
As you can notice the prefix will be spring.app then spring-boot will match the properties prefix with that and look for serviceName and version and values will be injected.
Considering you have you class App annotated with #SpringBootApplication and App class in the top package You can put your serviceName inside application.properties and inject it using #Value("${serviceName}"). Do not use #Component on a class if you are already using #Bean on configuration it will clash, and so #Autowired with #Value
See docs for more info
You will end with something like
#Service // #Component specialization
public class Service1 implements Service {
#Value("${serviceName}")
public String serviceName;
//Available in external configuration file.
//This autowiring is not reflected in the main method of the application.
public void getHistory(int days , Location location)
{
//history code
}
public void getForecast(int days , Location location )
{
//forecast code
}
}
No need for #Bean declaration when you have #Component/#Service/#Repository
#Configuration
public class AppConfig { //other stuff here not duplicated beans }
And your main class
package com.app;
#SpringBootApplication // contains #EnableAutoConfiguration #ComponentScan #Configuration
public class App {
#Autowired
private Service service;
public static void main(String[] args) {
final SpringApplication app = new SpringApplication(App.class);
//app.setShowBanner(false);
app.run();
}
#PostConstruct
public void foo() {
System.out.println("Instantiated service name = " + service.serviceName);
}
}
I am practising on spring-social and it seems that the userConnectionRepository is not properly autowired in the following code when I do a "Run as Junit Test" in Eclipse. I get a Null pointer exception on the usersConnectionRepository when creating a new FacebookOffLine although breakpoints put in the #Bean java creation code shows that they seem to be properly created. Thanks in advance,
public class FacebookOffline {
private Facebook fb;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
public FacebookOffline(User user) {
super();
ConnectionRepository cr = usersConnectionRepository.createConnectionRepository(user.getId());
fb = cr.getPrimaryConnection(Facebook.class).getApi();
}
}
Here is the test code :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {
org.springframework.social.quickstart.config.MainConfig.class,
org.springframework.social.quickstart.config.SocialConfig.class })
public class FacebookOfflineTest {
#Test
public void test1() {
FacebookOffline essai = new FacebookOffline(new User("yves"));
And the Spring configuration classes adapted from Keith Donald Quick Start Sample :
#Configuration
#ComponentScan(basePackages = "org.springframework.social.quickstart", excludeFilters = { #Filter(Configuration.class) })
#PropertySource("classpath:org/springframework/social/quickstart/config/application.properties")
public class MainConfig {
#Bean
public DataSource datasource() {
DriverManagerDataSource toReturn = new DriverManagerDataSource("jdbc:mysql://localhost:3306/spring_social");
toReturn.setDriverClassName("com.mysql.jdbc.Driver");
toReturn.setUsername("spring");
toReturn.setPassword("spring");
return toReturn;
}
}
#Configuration
public class SocialConfig {
#Inject
private Environment environment;
#Inject
private DataSource dataSource;
#Bean
public ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(environment
.getProperty("facebook.clientId"), environment
.getProperty("facebook.clientSecret")));
return registry;
}
#Bean
public UsersConnectionRepository usersConnectionRepository() {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(
dataSource, connectionFactoryLocator(), Encryptors.noOpText());
return repository;
}
}
Actually there are 2 problems here.
Spring cannot autowire beans it doesn't control (i.e. created with new)
Dependencies aren't available in the constructor (an object instance is needed before it can be injected)
The first one can be mitigated by letting spring manage an instance of FacebookOffline (or if you need multiple instances make the bean request or session scoped).
The second is a bit harder but can probaly solved by using a method annotated with #PostConstruct (or by implementing InitializingBean from spring).
You did
FacebookOffline essai = new FacebookOffline(new User("yves"));
That means, Spring isn't managing this essai instance and thus spring can't autowire any variables in the essai.
You'll have to create bean of FacebookOffline in SocialConfig.
Then you can have
/* ... */
public class FacebookOfflineTest {
#Autowired
ApplicationContext context;
#Test
public void test1() {
FacebookOffline essai = context.getBean(FacebookOffline.class);
OR
/* ... */
public class FacebookOfflineTest {
#Autowired
FacebookOffline essai;
#Test
public void test1() {
// You can use essai now
Also, you'll need to update FacebookOffline as Dependencies ain't available in constructor.
public class FacebookOffline {
private Facebook fb;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
public FacebookOffline(User user) {
super();
}
#PostConstruct
void loadFacebook() {
ConnectionRepository cr = usersConnectionRepository.createConnectionRepository(user.getId());
fb = cr.getPrimaryConnection(Facebook.class).getApi();
}
}
Spring can't autowire fields on an instance you create via new since it doesn't know about it. Declare a bean of type FacebookOffline instead.