#Value works without configuring static PropertySourcesPlaceholderConfigurer - java

I am trying to understand the behaviour of #PropertySource annotation when not using #Autowiring and Spring Environment class. I am trying to use #Value to inject values from a properties file at runtime. From the book that I am reading and from the online sources it is required to have a static bean - PropertySourcesPlaceholderConfigurer configured in order for this to work. But for me the #Value works without PropertySourcesPlaceholderConfigurer static bean as well. Can someone point me to the right direction as to whats happening here. May be I am missing something very basic. When do we need PropertySourcesPlaceholderConfigurer and when not?
Below is the code that I am trying out -
package com.nilaysundarkar.demos;
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
AppConfig.java -
package com.nilaysundarkar.demos;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
#Configuration
#PropertySource("classpath:/com/nilaysundarkar/demos/app.properties")
public class AppConfig {
// This bean does not make any difference, or does it?
/*#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}*/
#Bean
public Person person(#Value("${person.name}") String name){
Person person = new Person();
person.setName(name);
return person;
}
}
Bootstrap -
package com.nilaysundarkar.demos;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class App {
public static void main(String[] args){
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Person person = context.getBean(Person.class);
System.out.println(person.getName());
((AnnotationConfigApplicationContext)context).close();
}
}
properties file - app.properties -
person.name=John Doe
pom -
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nilaysundarkar.demos</groupId>
<artifactId>demos-runtime-injections</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
</dependencies>
When I run App.java -

In spring boot, and spring in general, application.properties (and application.yml since spring boot) can be placed in src/main/resources and it is picked automatically by the spring environment. That means that any property from this files will be loaded to your Environment and will be ready for injection using #Value.
You can use PropertySourcesPlaceholderConfigurer in order to register more property sources like foo.properties, [NAME].properties and so on in order for the spring Environment to add them.
When you use #PropertySource you register another propery file to your spring Environment so you dont need to use the custom PropertySourcesPlaceholderConfigurer to register it again. #PropertySource make it easier to register property files that do not require some special loading like a file in your file system etc.
As long as you use the default locations (application.properties) you don't need to register a custom bean of this type.
EDIT:
Example for PropertySourcesPlaceholderConfigurer with the same functionality as #PropertySource. The example is based on a foo.properties file residing in src/main/resources:
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("foo.properties"));
return configurer;
}

Took Privotal's Spring Core course months ago (probably Feb/2018) and the student handout (Version 5.0.a) explicitly tells you that a static PropertySourcesPlaceholderConfigurer bean must be declared in order for the ${}placeholders to be resolved when using Spring Core. But, when I tested this behavior omitting the creation of the bean in question it worked as If I’d created the bean resolving the placeholders. After that, I contacted my course instructor because I thought there was something wrong with my code, but he later confirmed that my code to be "good". He proceeded to contact Pivotal and we got an official answer :
Properties files registered via #PropertySource are automatically
added to the Environment in
org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClass,
SourceClass) while processing #Configuration classes.
So, apparently Pivotal acknowledge that the documentation on this is poor, and have filed a new JIRA.
BTW, this only applies to Spring Core 4.3+ as Spring Boot creates this bean for you automatically.
EDIT:
If you're taking the certification test, it's not clear when will Pivotal make the update, but the odds of this particular issue showing up on the exam are minuscule (in case it does appears, you can appeal)

Related

Why get warning in repository "Unnecessary `#Repository`"

I am working on Spring Boot project. I have repository file in my project but it will show me a warning message in repository class Unnecessary #Repository. I am extending a JpaRepository<> with my repository. My Spring version is 4 and JDK version is 17.
Here is my dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Here is my repository
#Repository // Here I get a warning to remove this annotation becasue its unnecessary
public interface CollegeRepo extends JpaRepository<College, Integer>{
}
You are extending JpaRepository<T, ID> interface, it means that spring boot must autoconfigure this repository bean for you, namely, it will be configured a proxy bean of SimpleJpaRepository<T, ID>.
In simple words, we do not just create a bean using #Repository or #Component annotation, we extend the spring-data interface and then our repository bean will be autoconfigured.
When to use #Repository
You want to provide your own implementation of how to access the data layer and what should be done. In this case marking your implementation class with #Repository will allow you to have this class managed by spring so that you can autowire necessary fields to access data layer like EntityManager , JdbcTemplate ...etc. Although Component, and #Repository in the most fundamental level just register spring beans there are some slight enhancements using #Repository which might make it neccessary to use and also best practice in the current case.
As per doc1
A class thus annotated with #Repository is eligible for Spring
DataAccessException translation when used in conjunction with a
PersistenceExceptionTranslationPostProcessor.,
and doc2
PersistenceExceptionTranslationPostProcessor
Bean post-processor that
automatically applies persistence exception translation to any bean
marked with Spring's #Repository annotation, adding a corresponding
PersistenceExceptionTranslationAdvisor to the exposed proxy
Example of above case use with #Repository.
#Repository
public class CustomCarRepositoryImpl implements CustomCarRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<CarEntity> findCarsWithSpeed(Integer speed) {
return entityManager.createQuery("Query to execute")
.setMaxResults(50).getResultList();
}
}
public interface CustomCarRepository {
List<CarEntity> findCarsWithSpeed(Integer speed);
}
Then you can autowire in your other components the CustomCarRepository and access the data layer as you have implemented.
When Not to use #Repository
When you just declare your interface and you extend from any Spring child interface of Repository from org.springframework.data.repository.
Example
public interface CarRepository extends JpaRepository<CarEntity, Long> {
List<CarEntity> findCarsWithSpeed(Integer speed);
}
In that case Spring Boot will be able to create the repository bean for you automatically from auto configuration.
The only further action needed is if your own interfaces extending from Repository do not exist in the same package or subpackage of where your #Configuration or #SpringBootApplication exists then you would need
either #EnableJpaRepositories(basePackages = {"base-package-where-repositories-exist"})
or #AutoConfigurationPackage(basePackages = {"base-package-where-repositories-exist"})
as to help spring boot identify the package it should look for the auto configuration of that repository. ( The later #AutoConfigurationPackage will affect both repositories and other things required for auto configuration like entities scan and more. So it should be used with care in a project and not just for repositories.)
The Solution depends upon what type of database you intend to use. "Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured." Error you're getting is because you have not set the url for the database in the application.properties. To solve this issue I would recommend you to Open the application.properties and add the following according to your system:
spring.datasource.url=jdbc:mysql://localhost:3306/restapi
spring.datasource.username=root
spring.datasource.password=

How to use Annotations in a Spring Boot Library (without Main Class)?

I am new to spring boot and trying to figure out some of its working. Here I am getting Null Pointer Exception for the below implementation. I am not sure can we use #Autowire annotation for a Library project without a Main class. Maybe this sounds stupid,I believe we can do a #ComponentScan for the Library Project from a Service project that's created.My Question is looking at the below implementation is there any possibility to use annotation in the below library project, because Annotations are throwing NullPointerException for the below code?
Library
The below code is a library and it Doesn't have a Main Class
#Service
class Data {
public String getData(){
return "DATA";
}
}
class Access{
#Autowired
private Data data;
public String myData(){
return data.getData(); // Null pointer exception
}
}
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Service
The below code is of a Service which is accessing the above library
Controller
#GetMapping("/")
String print(){
// Accesses the Library
Access access=new Access();
return access.myData();
}
#SpringBootApplication
#ComponentScan(basePackages = { "com.service", "com.library" }) // Hopes this Scans the library package
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
pom.xml
<dependency>
<groupId>com.library</groupId>
<artifactId>library</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
If I remove the Autowired from the library code, and create the object normally (using new keyword), everything works fine. So my question is, Main class with #SpringBootApplication is required inorder to use annotations, without a Main class can't we run it?
The best practice if you're not relying on any Spring AOP features (such as #Transactional) is don't do it at all. #Autowired on fields is fragile; instead, use a normal constructor. Spring needs no annotations to instantiate a bean if you have only a single constructor, and this makes your class usable in a different DI environment or in plain Java (such as for testing).
Similarly, don't put #Service on a class in a library; clients who want it can simply use #Import to pull it in.
The one exception to this is that if you are providing a Boot auto-configuration setup, that module will need to depend on spring-boot-autoconfigure to access the annotations. Note that it is customary to put your starter in a separate dependency that contains only the Boot classes and metafiles.
All you need to do is annotate your library Access class with #Service Or #Component
And in your Controller class create a field for Access class and autowire it.
Then use it inside your method
#Service
class Access{
#Autowired
private Data data;
public String myData(){
return data.getData();
}
}
#RestController
class YourController {
#Autowired
Access access
#GetMapping("/")
String print(){
return access.myData();
}
With SpringBootApplication annotation, It will create application context with all required beans when starting the application. The object will be injected whenever it needed.
But in the Normal java application, the Object will be created while calling the new keyword.
#SpringBootApplication it required when you have #Autowired annotation otherwise you will get error.

#RefreshScope with different *.properties files

I want to reload multiple config files when the refresh endpoint is called. It works perfectly fine with the entries in the application.properties file. Any other file does not refresh.
Here a small example:
pom.xml
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
</dependencies>
...
application.properties
greeting=Hi
management.endpoints.web.exposure.include=refresh
test.properties
test=Test
ConfigFileHolder1
#Repository
#PropertySource(value="application.properties")
#RefreshScope
public class ConfigFileHolder1 {
#Value("${greeting}")
private String greeting;
public String getGreeting() {
return greeting;
}
}
ConfigFileHolder2
#Repository
#PropertySource(value="test.properties")
#RefreshScope
public class ConfigFileHolder2 {
#Value("${test}")
private String test;
public String getTest() {
return test;
}
}
ApiController
#Controller
#RefreshScope
public class ApiController implements Api {
#Autowired
private ConfigFileHolder1 config1;
#Autowired
private ConfigFileHolder2 config2;
// do something with config1 and config2
...
}
Only ConfigFileHolder1 will refresh its value after the refresh-endpoint is called. To refresh the value of ConfigFileHolder2 the application has to restart.
What do I have to change to refresh the values of all my config-files/ConfigFileHolder?
Thanks for your help.
The #RefreshScope will only work with the properties loaded by Spring Boot, not the #PropertySources loaded later in the process. Hence you will need to tell Spring Boot to load the additional configuration files.
You can do this by either adding names (spring.config.name) or locations spring.config.additional-location.
When specifying an additional name make sure to include the default application as well else that won't be loaded anymore.
--spring.config.name=application,test
When specifying the above as a parameter all locations will be checked for both an application.properties and test.properties and also the expansion for profiles will be applied.
--spring.config.additional-location=classpath:test.properties
This will only load the test.properties from the class path and will make it more or less impossible to change the file at runtime but the file will be loaded from that exact location. No profile expansion will be applied.

How to disable spring-data-mongodb in spring-boot

I am new to SpringBoot. I have built a simple application which should use fake data in the development environment, and connect to MongoDb in the test environment. Dev environment does not have mongodb setup.
I have tried using Spring Boot qualifiers/profiles to achieve it.
I have a main class which looks like the following:
#SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
I have a DAO interface StudentDao.java
public interface StudentDao {
Student getStudentById(String id);
}
I then created a couple of implementations for the DAO, one for fake data, and one for data from Mongo
FakeStudentDaoImpl.java
#Repository
#Qualifier("fakeData")
public class FakeStudentDaoImpl implements StudentDao {
private static Map<String, Student> students;
static {
students = new HashMap<String, Student>(){
{
put("1", new Student("Ram", "Computer Science"));
}
};
}
#Override
public Student getStudentById(String id){
return this.students.get(id);
}
}
MongoStudentDaoImpl.java
#Repository
#Qualifier("mongoData")
public class MongoStudentDaoImpl implements StudentDao {
#Autowired
private MongoStudentRepo repo;
#Override
public Student getStudentById(String id) {
return repo.findById(id).get();
}
}
The MongoStudentRepo is a simple interface extending MongoRepository:
public interface MongoStudentRepo extends MongoRepository<Student, String> {
}
And my POM file has the following dependencies called out:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
Of course, I have other controller classes.
This works fine in the Test environment, where there is a MongoDb, and it is able to connect to it. However, when I am trying to start it in my local environment, it fails to start because it is not finding MongoDb on startup.
How do I disable the MongoDb part in my local environment (and just use fake data)? I want to make the same code work in both environments.
Thanks in advance.
I had the same problem, and found the solution you ask for in this other question:
Spring Boot. How to disable Initialization of JPA Conditionaliy
For example, if you want to disable spring-data-mongodb in the development environment, then, assuming that you run under a "dev" profile:
application-dev.yml:
spring:
autoconfigure:
exclude: org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
You can use an embedded MongoDB database. Here an example.
Several possible options:
1) You can use spring profiles. Map one bean with #Profile("test) and second one with #Profile("prod"). To specify which profile to use --spring.profiles.active=test
2) You can have different configurations.
application-prod.yml
--------------
mongo-url:produrl
application-test.yml
--------------
mongo-url:localhost
Use spring active profiles to select config. To use local profile you need to setup local mongo instance. And you can have several options again: just download instance, docker image, embeded mongo.

Spring Data Solr repositories using JavaConfig

I'm trying to create a SpringBoot application which uses solr repositories. I'm following this tutorial:
http://docs.spring.io/spring-data/solr/docs/current/reference/html/#solr.repositories
which says to configure my application with the following class (Example 43):
#Configuration
#EnableSolrRepositories
class ApplicationConfig {
#Bean
public SolrClient solrClient() {
EmbeddedSolrServerFactory factory = new EmbeddedSolrServerFactory("classpath:com/acme/solr");
return factory.getSolrServer(); // getSolrServer does not exist
}
#Bean
public SolrOperations solrTemplate() {
return new SolrTemplate(solrClient());
}
}
The problem is if I do that it doesn't recognise getSolrServer() as a method of factory. Indeed, if you look at the most recent API for EmbeddedSolrServerFactory you don't find that method, but it apparently existed in a previous version of the same class.
Maybe it was renamed from getSolrServer to getSolrClient, for some reason, from one version to another.
Here's my dependencies in the pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Anyway, I tried to change getSolrServer to getSolrClient, but the return type, i.e. SolrClient, is now incompatible. If I try to return org.apache.solr.client.solrj.embedded.EmbeddedSolrServer, it gives me an error because it doesn't find org.apache.solr.client.solrj.embedded...
Another problem using this would be that SolrTemplate doesn't require a EmbeddedSolrServer, so this is not a good option...
I am using eclipse not spring suite, Assuming spring suite using latest version of spring-boot-starter-data-solr ( 1.4.2 ) and you need to add an entry for solr-core 5.x in your pom.
Since EmbeddedSolrServer is extending SolrClient, follows Java IS-A relationship and it should be compatible with SolrClient. This binary is part of solr-core.
Your code need to use getSolrClient itself and it should be compatible with SolrClient
Dependencies in pom.xml is as follows
Here we go with our code base without any errors.
Instead of creating SolrClient bean, create EmbeddedSolrServerFactoryBean object and pass that object to solr template to create SolrTemplate object. Here is my config file:
#Configuration
#EnableSolrRepositories(basePackages = "com.ida.*.repository")
#Profile("dev")
public class SolrConfigDev {
#Autowired
private Environment environment;
#Bean
public EmbeddedSolrServerFactoryBean solrServerFactoryBean() {
EmbeddedSolrServerFactoryBean factory = new EmbeddedSolrServerFactoryBean();
factory.setSolrHome(environment.getRequiredProperty("solr.solr.home"));
return factory;
}
#Bean
public SolrTemplate solrTemplate() throws Exception {
return new SolrTemplate(solrServerFactoryBean().getObject());
}
}
In addition, you have to add solr-core to you pom.xml file.
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-core</artifactId>
<version>5.5.3</version>
</dependency>
More information about this topic, you can find here in this blog by Petri Kainulainen.

Categories

Resources