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.
Related
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.
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.
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.
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)
I am working on a Java Rest Service with Spring MVC (4.2.0.RELEASE) including a Neo4j database. Therefore Spring Data Neo4j (4.1.1.RELEASE) is used.
The SDN configuration looks like this:
#Configuration
#ComponentScan(basePackages = { "com.xxx.yyy" })
#EnableNeo4jRepositories(basePackages = "com.xxx.yyy.dao.repo")
#EnableTransactionManagement
public class Neo4jConfig extends Neo4jConfiguration {
#Autowired
private ApplicationProperties properties;
#Bean
public SessionFactory getSessionFactory() {
return new SessionFactory(getConfiguration(), "com.xxx.yyy.dao.beans");
}
#Bean
public Neo4jOperations neo4jTemplate() throws Exception {
return new Neo4jTemplate(getSession());
}
#Bean
public org.neo4j.ogm.config.Configuration getConfiguration() {
org.neo4j.ogm.config.Configuration config = new org.neo4j.ogm.config.Configuration();
config.driverConfiguration().setDriverClassName(this.properties.getNeo4jDriver())
.setURI(this.properties.getNeo4jEndpoint())
.setCredentials(this.properties.getNeo4jUser(), this.properties.getNeo4jPassword());
return config;
}
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public Session getSession() throws Exception {
return super.getSession();
}
}
The application is deployed on a Tomcat7 in production environment. Everything works like a charm if there is only one version of the application deployed and the version flag is not filled in.
For a zero downtime deployment I want to use the version flag on the tomcat to deploy multiple versions. If I do so the application is not working anymore because of a NullPointerException in the org.neo4j.ogm.context.RestModelMapper.
Stacktrace:
java.lang.NullPointerException
at org.neo4j.ogm.context.RestModelMapper.mapEntity(RestModelMapper.java:153) ~[neo4j-ogm-core-2.0.1.jar:?]
at org.neo4j.ogm.context.RestModelMapper.map(RestModelMapper.java:76) ~[neo4j-ogm-core-2.0.1.jar:?]
at org.neo4j.ogm.session.delegates.ExecuteQueriesDelegate.query(ExecuteQueriesDelegate.java:94) ~[neo4j-ogm-core-2.0.1.jar:?]
at org.neo4j.ogm.session.delegates.ExecuteQueriesDelegate.query(ExecuteQueriesDelegate.java:73) ~[neo4j-ogm-core-2.0.1.jar:?]
at org.neo4j.ogm.session.Neo4jSession.query(Neo4jSession.java:313) ~[neo4j-ogm-core-2.0.1.jar:?]
This problem occurs only if I use the version flag on tomcat. Does anyboby know what is the problem here?
I finally solved this issue by changing the version of the OGM HTTP Driver and the Spring Data Neo4j dependency.
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-http-driver</artifactId>
<version>2.0.5</version>
</dependency>
With this settings the deployment with the Tomcat version flag https://tomcat.apache.org/tomcat-7.0-doc/config/context.html is now working.