spring boot elastic search -configure data source - spring-boot

I am tryinging to configure spring data boot sand ES project
in my pom.xml i have :
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.yoyo.elastic.repository")
public class ElasticConfiguration {
#Bean
public NodeBuilder nodeBuilder() {
return new NodeBuilder();
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() throws IOException {
File tmpDir = File.createTempFile("elastic", Long.toString(System.nanoTime()));
System.out.println("Temp directory: " + tmpDir.getAbsolutePath());
final Client client = nodeBuilder().local(true).node().client();
return new ElasticsearchTemplate(client);
}
}
in my pom xml I have this dep :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
which should supplay the driver but i keep on getting :
Description:
Cannot determine embedded database driver class for database type NONE
Action:
If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).

I had the same issue when trying to run some exercises with Spring Boot and ElasticSearch.
Right now I figured out that if you have the
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Alongside spring-boot-starter-data-elasticsearch and don't add additional config classes (where you would configure the DataSource) spring boot will complain.
Other solution would be to actually add a datasource property to application.properties and configure standalone database (like H2)

Related

How to configure Springboot's 2.x actuator in Spring Application(not a SpringBoot Application)? [duplicate]

I've been working on a Spring/Spring MVC application and I'm looking to add performance metrics. I've come across Spring Boot Actuator and it looks like a great solution. However my application is not a Spring Boot application. My application is running in a traditional container Tomcat 8.
I added the following dependencies
// Spring Actuator
compile "org.springframework.boot:spring-boot-starter-actuator:1.2.3.RELEASE"
I created the following config class.
#EnableConfigurationProperties
#Configuration
#EnableAutoConfiguration
#Profile(value = {"dev", "test"})
#Import(EndpointAutoConfiguration.class)
public class SpringActuatorConfig {
}
I even went as far as adding #EnableConfigurationProperties on every configuration class as suggested on another post on StackOverflow. However that didn't do anything. The endpoints are still not being created and return 404s.
First let's clarify that you cannot use Spring Boot Actuator without using Spring Boot.
I was wrong about not being able to it without Spring Boot. See #stefaan-neyts
answer for an example of how to do it.
I created a sample project to show how you could convert a basic SpringMVC application using a minimal amount of Spring Boot auto-configuration.
Original source: http://www.mkyong.com/spring-mvc/gradle-spring-mvc-web-project-example
Converted source: https://github.com/Pytry/minimal-boot-actuator
I could have completely removed the dispatcher-servlet.xml and the web.xml files, but I kept them to show how to perform as minimal a change as possible and to simplify converting more complex projects.
Here is a list of steps I took to convert.
Conversion Process
Add a Java Configuration file annotated with #SpringBootApplication
Add the Application configuration file as a bean to the traditional xml configuration ( added it just after the context scan).
Move view resolvers into Application java configuration.
Alternatively, add the prefix and suffix to application.properties.
You can then inject them with #Value in your application, or delete it entirely and just use the provided spring boot view resolver.
I went with the former.
Removed Default context listener from the spring context xml.
This is important!
Since spring boot will provide one you will get an "Error listener Start" exception if you do not.
Add the spring boot plugin to your build script dependencies (I was using gradle)
Add a mainClassName property to the build file, and set to an empty String (indicates not to create an executable).
Modify dependencies for spring boot actuator
You can use actuator without spring boot.
Add this to pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>
And then in your config class
#Configuration
#EnableWebMvc
#Import({
EndpointAutoConfiguration.class , PublicMetricsAutoConfiguration.class , HealthIndicatorAutoConfiguration.class
})
public class MyActuatorConfig {
#Bean
#Autowired
public EndpointHandlerMapping endpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints) {
return new EndpointHandlerMapping(endpoints);
}
#Bean
#Autowired
public EndpointMvcAdapter metricsEndPoint(MetricsEndpoint delegate) {
return new EndpointMvcAdapter(delegate);
}
}
And then you can see the metrics in your application
http://localhost:8085/metrics
Allthough it is not a good idea to use Spring Boot features without Spring Boot, it is possible!
For example, this Java configuration makes Spring Boot Actuator Metrics available without using Spring Boot:
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfiguration;
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
#Configuration
#Import({ EndpointAutoConfiguration.class, PublicMetricsAutoConfiguration.class })
public class SpringBootActuatorConfig {
#Bean
#Autowired
public EndpointHandlerMapping endpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints) {
return new EndpointHandlerMapping(endpoints);
}
#Bean
#Autowired
public EndpointMvcAdapter metricsEndPoint(MetricsEndpoint delegate) {
return new EndpointMvcAdapter(delegate);
}
}
The Maven dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
Though the answer is already accepted, I thought of updating my experience. I did not want to convert my application to spring boot using #SpringBootApplication. Refer to another question where I have mentioned the bare minimum code required.
As we already have Spring Boot Actuator 2.x, a recipe to include actuator to an existing Spring MVC project can look like this:
#Configuration
#Import({
EndpointAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class,
InfoEndpointAutoConfiguration.class,
HealthEndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
})
#EnableConfigurationProperties(CorsEndpointProperties.class)
class ActuatorConfiguration {
#Bean //taken from WebMvcEndpointManagementContextConfiguration.class
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
EndpointMapping endpointMapping = new EndpointMapping(webEndpointProperties.getBasePath());
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
corsProperties.toCorsConfiguration(),
new EndpointLinksResolver(allEndpoints, webEndpointProperties.getBasePath()));
}
#Bean
DispatcherServletPath dispatcherServletPath() {
return () -> "/";
}
}
I did include
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<version>2.1.18.RELEASE</version>
</dependency>
for compatibility with the baseline Spring version I've been using (5.1.19.RELEASE)
If your objective is to create an endpoint with metrics for Prometheus a.k.a. OpenMetrics, you can use the Prometheus JVM client which is compatible with Spring framework.
Add dependency:
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_servlet</artifactId>
<version>0.16.0</version>
</dependency>
To collect metrics of requests, add as first filter in web-app/WEB-INF/web.xml:
<filter>
<filter-name>prometheusFilter</filter-name>
<filter-class>io.prometheus.client.filter.MetricsFilter</filter-class>
<init-param>
<param-name>metric-name</param-name>
<param-value>webapp_metrics_filter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>prometheusFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
To expose metrics as HTTP endpoint, add servlet:
<servlet>
<servlet-name>prometheus</servlet-name>
<servlet-class>io.prometheus.client.exporter.MetricsServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>prometheus</servlet-name>
<url-pattern>/metrics</url-pattern>
</servlet-mapping>
After that you can see the metrics on the /metrics endpoint.
Time passes, we have Spring 6, SpringBoot 3, JakartaEE as a baseline, but people are still looking to add actuator to legacy spring applications. So a small update: spring + actuator without spring-boot. In fact not much changes (and the changes have already been pointed out).
The dependencies
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.3</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<version>3.0.1</version>
</dependency>
The actuator configuration
#Configuration
#ImportAutoConfiguration({
EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
HealthContributorAutoConfiguration.class,
InfoEndpointAutoConfiguration.class,
HealthEndpointAutoConfiguration.class,
HeapDumpWebEndpointAutoConfiguration.class,
ThreadDumpEndpointAutoConfiguration.class,
LoggersEndpointAutoConfiguration.class,
PrometheusMetricsExportAutoConfiguration.class,
})
#EnableConfigurationProperties(CorsEndpointProperties.class)
class ActuatorConfiguration {
#Bean //taken from WebMvcEndpointManagementContextConfiguration.class
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
EndpointMapping endpointMapping = new EndpointMapping(webEndpointProperties.getBasePath());
return new WebMvcEndpointHandlerMapping(endpointMapping,
webEndpoints,
endpointMediaTypes,
corsProperties.toCorsConfiguration(),
new EndpointLinksResolver(allEndpoints, webEndpointProperties.getBasePath()),
true);
}
#Bean
DispatcherServletPath dispatcherServletPath() {
return () -> WebInitializer.APPLICATION_ROOT;
}
}
The example is easy to run directly from maven jetty plugin (mvn jetty:run-war).
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>11.0.13</version>
</plugin>
you have made the mistake by not introducing the #springboot annotation in your code.When you add #springboot ot will consider as boot program by the compiler automatically and addd the required dependency file for it and your actuator dependency file

How to configure DataSource for a Spring-Boot Application in a Standalone (war) and in an embedded Tomcat?

I have a Spring-Boot-Aplication with the following dependencyManagement:
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
and the following dependencies:
spring-boot-starter-jersey
spring-boot-starter-jdbc(exclusion:tomcat-jdbc)
HikariCP(version:3.3.1)
ojdbc7
On Tomcat I configured a JNDI-Datasource as:
<Resource name="jdbc/myDS"
type="javax.sql.DataSource"
driverClassName="oracle.jdbc.driver.OracleDriver"
username="Superuser"
password="secret"
url="jdbc:oracle:thin:#xxxDbX"
../>
In the .properties-file I added the following properties:
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
spring.datasource.jndi-name=jdbc/myDS
As Spring-Boot is able to configure a DataSource from the properties, I let it do so and I do write no extra code for a DataSource.
Deployed in a Standalone Tomcat it works perfectly.
Logically Spring Boot can not find the JNDI-Resource in an embedded Tomcat and starting the application as a Spring-Boot-Application I got:
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to bind properties under 'spring.datasource.type' to java.lang.Class<javax.sql.DataSource>:
Property: spring.datasource.type
Value: org.apache.tomcat.jdbc.pool.DataSource
Origin: class path resource [application.properties]:12:24
Reason: No converter found capable of converting from type [java.lang.String] to type [java.lang.Class<javax.sql.DataSource>]
Action:
Update your application's configuration
I would like to be able to start the application as a Spring-Boot-Application and also build a war-file which can be deployed in any Standalone Tomcat.
Is this possible by adding properties for a second DataSource in case the application is started as a Spring-Boot-Application or I am obliged to have a second .properties file?
The solution that worked for me is to add a custom-properties to use for the DataSource in the embedded Tomcat Server like so:
# for a dedicated Tomcat
spring.datasource.jndi-name=jdbc/dirserver
# for the embedded Tomcat
embedded.datasource.driver-class-name=oracle.jdbc.OracleDriver
embedded.datasource.url=jdbc:oracle:thin:#//myServer:1521/xxxxx
embedded.datasource.username=superuser
embedded.datasource.password=topsecret
and to define #Bean DataSource in the class annotated with #SpringBootApplication:
#SpringBootApplication
public class MySbApplication extends SpringBootServletInitializer {
private static final Logger lg = LoggerFactory.getLogger(MySbApplication.class);
#Value("${embedded.datasource.username}")
String username;
#Value("${embedded.datasource.password}")
String password;
#Value("${embedded.datasource.driver-class-name}")
String driverClassName;
#Value("${embedded.datasource.url}")
String url;
#Bean(destroyMethod = "")
public DataSource oracledataSoutŕce() throws SQLException {
final OracleDataSource dataSource = new OracleDataSource();
dataSource.setUser(username);
dataSource.setPassword(password);
dataSource.setURL(url);
dataSource.setImplicitCachingEnabled(true);
dataSource.setFastConnectionFailoverEnabled(true);
return dataSource;
}
}
I willl add a link to a sample project in Github.

Adding a dependency to a working Spring Boot project invalidates all JUnit

I have 2 Eclipse projects and each one is has services managed by Spring. I use Spring Boot starter dependencies for each of them. Each one works properly, and can be tested with JUnit launched via SpringRunner.class and #SpringBootTest.
Now, I want to call some services from project 1 in project 2, so I add a dependency in project 2 pom.xml and I add
#ComponentScan(basePackages="com.project1")
From then on, I can't launch any JUnit, it complains about dataSource not being set, like if configs where mixing randomly.
My question is : what are the recommended practices when you create a Spring Boot App and you want to isolate some features in a separate project (here XML features) ? If u can't have 2 spring boot app with one dependant of the other, what are the spring dependencies you need so the spring boot project can deal with the non spring boot dependency, and so that u can still launch JUnit using Spring runner locally ?
Do I need to pick Spring dependencies one by one (core, bean, context, test, log4j, slf4j, junit, hamcrest, ...) like before Spring boot exist to do this ?
See my comment on why the possible duplicate is different.
After removing all Spring boot dependencies from my module project, I still have the error as soon as I add the "ComponentScan" to scan the module services.
Here is my DB config (main project depending on a xml module) to be clear on the package config. This config WORKS perfectly until I add the ComponentScan on a package from the module project :
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages="fr.my.project.repository")
class PersistenceContext {
private static final String[] ENTITY_PACKAGES = { "fr.my.project.model" };
private static final String PROP_DB_DRIVER_CLASS = "db.driver";
private static final String PROP_DB_PASSWORD = "db.password";
private static final String PROP_DB_URL = "db.url";
private static final String PROP_DB_USER = "db.username";
private static final String PROP_HIBERNATE_DIALECT = "hibernate.dialect";
private static final String PROP_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
private static final String PROP_HIBERNATE_HBM2DDL_AUTO = "hibernate.hbm2ddl.auto";
private static final String PROP_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
/**
* Creates and configures the HikariCP datasource bean.
*
* #param env
* The runtime environment of our application.
* #return
*/
#Bean(destroyMethod = "close")
DataSource dataSource(Environment env) {
HikariConfig dataSourceConfig = new HikariConfig();
dataSourceConfig.setDriverClassName(env.getRequiredProperty(PROP_DB_DRIVER_CLASS));
dataSourceConfig.setJdbcUrl(env.getRequiredProperty(PROP_DB_URL));
dataSourceConfig.setUsername(env.getRequiredProperty(PROP_DB_USER));
dataSourceConfig.setPassword(env.getRequiredProperty(PROP_DB_PASSWORD));
return new HikariDataSource(dataSourceConfig);
}
/**
* Creates the bean that creates the JPA entity manager factory.
*
* #param dataSource
* The datasource that provides the database connections.
* #param env
* The runtime environment of our application.
* #return
*/
#Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, Environment env) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setPackagesToScan(ENTITY_PACKAGES);
Properties jpaProperties = new Properties();
// Configures the used database dialect. This allows Hibernate to create SQL
// that is optimized for the used database.
jpaProperties.put(PROP_HIBERNATE_DIALECT, env.getRequiredProperty(PROP_HIBERNATE_DIALECT));
// Specifies the action that is invoked to the database when the Hibernate
// SessionFactory is created or closed.
jpaProperties.put(PROP_HIBERNATE_HBM2DDL_AUTO, env.getRequiredProperty(PROP_HIBERNATE_HBM2DDL_AUTO));
// If the value of this property is true, Hibernate writes all SQL
// statements to the console.
jpaProperties.put(PROP_HIBERNATE_SHOW_SQL, env.getRequiredProperty(PROP_HIBERNATE_SHOW_SQL));
// If the value of this property is true, Hibernate will use prettyprint
// when it writes SQL to the console.
jpaProperties.put(PROP_HIBERNATE_FORMAT_SQL, env.getRequiredProperty(PROP_HIBERNATE_FORMAT_SQL));
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
/**
* Creates the transaction manager bean that integrates the used JPA provider with the Spring transaction mechanism.
*
* #param entityManagerFactory
* The used JPA entity manager factory.
* #return
*/
#Bean
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
and after adding :
#ComponentScan(basePackages="fr.my.module.xml.service")
I get this error when launching any Junit :
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.tomcat.jdbc.pool.DataSource]: Factory method 'dataSource' threw exception; nested exception is org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Cannot determine embedded database driver class for database type NONE. If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
Here is a temporary answer on how to configure the dependency project, but I hope some easier way benefiting of Spring Boot shortcuts for all app modules exist.
pom.xml with manual minimal dependencies :
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.14.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.11</version>
</dependency>
Manual test config :
#RunWith(SpringRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes=AppConfig.class)
public class XmlTest {
Manual app config :
#Configuration
#ComponentScan(basePackages="my.package.xml")
public class AppConfig {
}
Sooooooo after doing all these tries, Spring Boot may not be the cause of this problem at all.
The thing is I was adding #ComponentScan(basePackages="fr.package.xml") hoping to complete the default package scanning, but it was overriding it.
The proper way to add a package, is to redeclare explicitely the default package before adding the new package :
#ComponentScan(basePackages={"fr.package.xml", "fr.package.persistence"})
My other answer was about setting up manual minimal dependencies for a module in a Spring Boot app. But here is an example of using Spring boot special dependencies in the module which is not the main app :
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
</dependencies>
Then, you don't declare "#SpringBootApplication" in a main class in src/main/java where it may break the global packaging, but you set it up inside your test class :
#RunWith(SpringRunner.class)
#SpringBootTest("service.message=Hello")
public class MyServiceTest {
#Autowired
private MyService myService;
#Test
public void contextLoads() {
assertThat(myService.message()).isNotNull();
}
#SpringBootApplication
static class TestConfiguration {
}
}
source : https://github.com/spring-guides/gs-multi-module/tree/master/complete

Spring property placeholders in application-context.xml inside dependency jar

For my spring boot application I use annotation based configuration and a WebApplicationInitalizer.
One of my dependencies provides a spring configuration in an xml, included in the jar. I use #ImportResource to load the context xml. This seems to work, except for the fact that inside this xml, there are property placeholders, for example ${poolsize:10}
Apparently, spring does not automatically replace these placeholders (I get a NumberFormatException). Is there some extra configuration I need to add?
Our startup class:
public class Application implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
// config
rootContext.register(JmsConfiguration.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
}
}
And the configuration class (we use spring-jms):
#Configuration
#EnableJms
#ComponentScan(basePackages = { "..." })
public class JmsConfiguration implements JmsListenerConfigurer {
// config for jms listener and jaxb, nothing to do with property handling
}
Perhaps I'm mistaken in thinking that using a WebapplicationInitializer is how to use spring boot. Perhaps we don't even need spring boot? The only spring boot related dependency we use is:
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Spring dependencies we use:
org.springframework:spring-context:jar:4.3.8.RELEASE:compile
org.springframework:spring-jms:jar:4.3.8.RELEASE:compile
org.springframework:spring-oxm:jar:4.3.8.RELEASE:compile
org.springframework:spring-context:jar:4.3.8.RELEASE:compile
org.springframework:spring-beans:jar:4.3.8.RELEASE:compile
I figured it out thanks #M. Deinum
We don't need spring boot to use the WebApplicationInitializer, but (at least without spring boot) we have to declare our own PropertySourcesPlaceholderConfigurer:
#Configuration
#ImportResource(locations = {"classpath*:/library-context.xml"})
public class MyConfiguration {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
NB. This works out of the box in spring boot thanks to #EnableAutoConfiguration and #PropertyPlaceholderAutoConfiguration which contains the exact same PropertySourcesPlaceholderConfigurer bean

Library for distributed spring config (outside springboot)

I am looking for solution for distributed spring configuration. I am thinking of storing it in zookeeper. https://github.com/spring-cloud/spring-cloud-zookeeper does have that functionality but apparently it requires to use spring-boot.
Is there any similar library that I can use outside spring-boot
Consul by HashiCorp
Consul is a popular option because it is:
Open Source
Includes Service Discovery & Configuration
Support Multi-Datacenter out of the box
Etc.
It doesn't require you to use Spring Boot, it just provides the auto-configurations in case you do decide to go with Spring Boot. In other words, if you're not using Spring Boot, none of the configurations will apply automatically, you'll have to provide the configuration yourself.
Zookeeper is a good option, go for it.
EDIT:
To use Zookeeper without Spring Boot, you'd need to register the appropriate beans either manually or by importing the auto-configuration classes that Spring Boot would import for you implicitly. This rule of thumb generally applies to all Spring Boot-enabled modules.
In your case, you'd most likely need to import just the ZookeeperConfigBootstrapConfiguration and ZookeeperConfigAutoConfiguration. The classes are to be found within spring-cloud-zookeeper-config module so no Spring Boot dependencies needed.
Alternatively, you should look at those classes and their #Imports and declare the beans manually.
I found a solution for using spring-cloud-zookeeper without Spring Boot, based on the idea provided here https://wenku.baidu.com/view/493cf9eba300a6c30d229f49.html
First, create a CloudEnvironement class that will create a PropertySource from Zookeeper :
CloudEnvironement.java
public class CloudEnvironment extends StandardServletEnvironment {
#Override
protected void customizePropertySources(MutablePropertySources propertySources) {
super.customizePropertySources(propertySources);
try {
propertySources.addLast(initConfigServicePropertySourceLocator(this));
}
catch (Exception ex) {
logger.warn("failed to initialize cloud config environment", ex);
}
}
private PropertySource<?> initConfigServicePropertySourceLocator(Environment environment) {
ZookeeperConfigProperties configProp = new ZookeeperConfigProperties();
ZookeeperProperties props = new ZookeeperProperties();
props.setConnectString("myzookeeper:2181");
CuratorFramework fwk = curatorFramework(exponentialBackoffRetry(props), props);
ZookeeperPropertySourceLocator propertySourceLocator = new ZookeeperPropertySourceLocator(fwk, configProp);
PropertySource<?> source= propertySourceLocator.locate(environment);
return source ;
}
private CuratorFramework curatorFramework(RetryPolicy retryPolicy, ZookeeperProperties properties) {
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder();
builder.connectString(properties.getConnectString());
CuratorFramework curator = builder.retryPolicy(retryPolicy).build();
curator.start();
try {
curator.blockUntilConnected(properties.getBlockUntilConnectedWait(), properties.getBlockUntilConnectedUnit());
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
return curator;
}
private RetryPolicy exponentialBackoffRetry(ZookeeperProperties properties) {
return new ExponentialBackoffRetry(properties.getBaseSleepTimeMs(),
properties.getMaxRetries(),
properties.getMaxSleepMs());
}
}
Then create a custom XmlWebApplicationContext class : it will enable to load the PropertySource from Zookeeper when your webapplication start and replace the bootsrap magic of Spring Boot:
MyConfigurableWebApplicationContext.java
public class MyConfigurableWebApplicationContext extends XmlWebApplicationContext {
#Override
protected ConfigurableEnvironment createEnvironment() {
return new CloudEnvironment();
}
}
Last, in your web.xml file add the following context-param for using your MyConfigurableWebApplicationContext class and bootstraping your CloudEnvironement.
<context-param>
<param-name>contextClass</param-name>
<param-value>com.kiabi.config.MyConfigurableWebApplicationContext</param-value>
</context-param>
If you use a standard property file configurer, it should still be loaded so you can have properties in both a local file and Zookeeper.
For all this to work you need to have spring-cloud-starter-zookeeper-config and curator-framework jar in your classpath with their dependancy, if you use maven you can add the following to your pom.xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-dependencies</artifactId>
<version>1.1.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-config</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
</dependencies>

Resources