We have an existing spring web app deployed as a WAR file into Amazon Elastic Beanstalk. Currently we load properties files as http resources to give us a single source of property placeholder config resolution. Im investigating replacing this with the new spring cloud configuration server to give us the benefits of git versioning etc.
However the documentation (http://cloud.spring.io/spring-cloud-config/spring-cloud-config.html) only seems to describe a Spring Boot client application. Is it possible to set up the Spring Cloud Config Client in an existing web app? Do I need to manually set up the Bootstrap parent application context etc - are there any examples of this? Our current spring configuration is XML based.
Refrenced: https://wenku.baidu.com/view/493cf9eba300a6c30d229f49.html
Root WebApplicationContext and the Servlet WebApplicationContext uses Environment and initializes PropertySources based on the spring profile. For non-spring boot apps, we need to customize these to get the properties from Config Server and to refresh the beans whenever there is a property change. Below are the changes that needs to happen to get the config working in SpringMVC. You will also need a system property for spring.profile.active
Create a CustomBeanFactoryPostProcessor and set lazyInit on all bean definitions to true to initialize all bean lazily i.e. beans are initialized only upon a request.
#Component
public class AddRefreshScopeProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
private static ApplicationContext applicationContext;
#SuppressWarnings("unchecked")
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
String[] beanNames = applicationContext.getBeanDefinitionNames();
for(int i=0; i<beanNames.length; i++){
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanNames[i]);
beanDef.setLazyInit(true);
beanDef.setScope("refresh");
}
}
#Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
applicationContext = context;
}
/**
* Get a Spring bean by type.
*
* #param beanClass
* #return
*/
public static <T> T getBean(Class<T> beanClass) {
return applicationContext.getBean(beanClass);
}
/**
* Get a Spring bean by name.
*
* #param beanName
* #return
*/
public static Object getBean(String beanName) {
return applicationContext.getBean(beanName);
}
}
Create a custom class extending StandardServletEnvironment and overriding the initPropertySources method to load additional PropertySources (from config server).
public class CloudEnvironment extends StandardServletEnvironment {
#Override
public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
super.initPropertySources(servletContext,servletConfig);
customizePropertySources(this.getPropertySources());
}
#Override
protected void customizePropertySources(MutablePropertySources propertySources) {
super.customizePropertySources(propertySources);
try {
PropertySource<?> source = initConfigServicePropertySourceLocator(this);
propertySources.addLast(source);
} catch (
Exception ex) {
ex.printStackTrace();
}
}
private PropertySource<?> initConfigServicePropertySourceLocator(Environment environment) {
ConfigClientProperties configClientProperties = new ConfigClientProperties(environment);
configClientProperties.setUri("http://localhost:8888");
configClientProperties.setProfile("dev");
configClientProperties.setLabel("master");
configClientProperties.setName("YourApplicationName");
System.out.println("##################### will load the client configuration");
System.out.println(configClientProperties);
ConfigServicePropertySourceLocator configServicePropertySourceLocator =
new ConfigServicePropertySourceLocator(configClientProperties);
return configServicePropertySourceLocator.locate(environment);
}
}
Create a custom ApplicatonContextInitializer and override the initialize method to set the custom Enviroment instead of the StandardServletEnvironment.
public class ConfigAppContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.setEnvironment(new CloudEnvironment());
}
}
Modify web.xml to use this custom context initializer for both application context and servlet context.
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.my.context.ConfigAppContextInitializer</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.my.context.ConfigAppContextInitializer</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</context-param>
To refresh the beans created a refresh endpoint you will also need to refresh the application Context.
#Controller
public class RefreshController {
#Autowired
private RefreshAppplicationContext refreshAppplicationContext;
#Autowired
private RefreshScope refreshScope;
#RequestMapping(path = "/refreshall", method = RequestMethod.GET)
public String refresh() {
refreshScope.refreshAll();
refreshAppplicationContext.refreshctx();
return "Refreshed";
}
}
RefreshAppplicationContext.java
#Component
public class RefreshAppplicationContext implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void refreshctx(){
((XmlWebApplicationContext)(applicationContext)).refresh();
}
}
I have similar requirement; I have a Web Application that uses Spring XML configuration to define some beans, the value of the properties are stored in .property files. The requirement is that the configuration should be loaded from the hard disk during the development, and from a Spring Cloud Config server in the production environment.
My idea is to have two definition for the PropertyPlaceholderConfigurer; the first one will be used to load the configuration from the hard disk :
<bean id="resources" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" doc:name="Bean">
<property name="locations">
<list>
<value>dcm.properties</value>
<value>post_process.properties</value>
</list>
</property>
</bean>
The second one will load the .properties from the Spring Config Server :
<bean id="resources" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" doc:name="Bean">
<property name="locations">
<list>
<value>http://localhost:8888/trunk/dcm-qa.properties</value>
</list>
</property>
</bean>
Everything that "just works" with Spring Boot is actually no more than some configuration. It's all just a Spring application at the end of the day. So I believe you can probably set everything up manually that Boot does for you automatically, but I'm not aware of anyone actually trying this particular angle. Creating a bootstrap application context is certainly the preferred approach, but depending on your use case you might get it to work with a single context if you make sure the property source locators are executed early enough.
Non Spring (or non Spring Boot) apps can access plain text or binary files in the config server. E.g. in Spring you could use a #PropertySource with a resource location that was a URL, like http://configserver/{app}/{profile}/{label}/application.properties or http://configserver/{app}-{profile}.properties. It's all covered in the user guide.
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
It should be easily updated to match your needs and using a Spring Cloud Config Server (the CloudEnvironement class needs to be updated to load the file from the server instead of Zookeeper)
First, create a CloudEnvironement class that will create a PropertySource (ex 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 bootstrap 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>
Posting as an answer because I don't have enough points to comment on Dave Syer's excellent answer. We have implemented his proposed solution in production and it is working as expected. Our newer apps are being written using Boot, while our legacy apps use Spring, but not boot. We were able to use Spring Cloud Config to create a property service that serves properties for both. The changes were minimal. I moved the legacy property files out of the war file to the property service git repository, and changed the property definition from a classpath reference to a URL as Dave describes and inserted our system environment variable just as we did for classpath. It was easy and effective.
<util:properties id="envProperties" location="https://properties.me.com/property-service/services-#{envName}.properties" />
<context:property-placeholder properties-ref="envProperties" ignore-resource-not-found="true" ignore-unresolvable="true" order="0" />
<util:properties id="defaultProperties" location="https://properties.me.com/property-service/services-default.properties" />
<context:property-placeholder properties-ref="defaultProperties" ignore-resource-not-found="true" ignore-unresolvable="true" order="10" />
Related
I have an existing Spring web service with a web.xml file and a combination of xml-config and java-config. (Primarily xml config because my coworkers still think it's 2009.)
I'm trying to add a dynamic filter registration based on properties. Namely, if a particular boolean property is set, it needs to add one kind of filter, and if it's not set, it'll add a different filter.
I'm trying to do this by implementing WebApplicationInitializer.
The implementation looks like this:
#Configuration
#EnableWebMvc
#ComponentScan(basePackageClasses=Foo.class)
#PropertySource("classpath:svc.properties")
public class WebConfig implements WebApplicationInitializer {
#Value("${dev.mode:false}")
private boolean isDevMode;
#Autowired
Foo foo;
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
// use the web.xml as the base
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("/WEB-INF/web.xml");
if(isDevMode) {
FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("jwtFilter", devModeFilter());
filterRegistration.addMappingForUrlPatterns(null,true,"/blah/*");
} else {
FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("jwtFilter", jwtFilter());
filterRegistration.addMappingForUrlPatterns(null, true, "/blah/*");
}
}
#Bean
public DevModeFilter devModeFilter() {
DevModeFilter filter = new DevModeFilter(foo);
// config here
return filter;
}
#Bean
public DevModeFilter jwtFilter() {
JwtFilter filter = new JwtFilter(foo);
// config here
return filter;
}
}
The filter registration is working -- I can put breakpoints into it and it works great.
The problem is that foo is always null, and the #Value annotated property always takes the default value. (Foo is a bean, annotated with #Component with an autowired constructor that as a parameter an instance of another bean that's declared in an #Bean elsewhere in the app.) I can put breakpoints both in the filters themselves and in the onStartup method and they trigger as expected, right up to the point where they need to interact with foo (which is always null).
The web.xml is normal and otherwise functions. It does declare the contextConfigLocation of the spring xml config files in it, which have the appropriate <context:component-scan />, <context:annotation-config />, <mvc:annotation-driven />, configs etc.
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="FooApp" version="3.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/service-base.xml</param-value>
</context-param>
<!-- listeners, servlets, and filters config'd here -->
</web-app>
How can I get my properties file and autowired dependency here so I can register a (spring bean) filter dynamically? I realize that this is probably due to my use of WebApplicationInitializer, which is managed by the servlet and not by Spring, but I'm having difficulties finding Spring-based alternatives.
I'm not allowed to use Spring-Boot or profiles. (Policy, not my own personal decision.)
I have several instances of a Spring Boot app deployed in a unique Tomcat.
Each app is configured with a context.xml file which contains a customer code
<Context path="/myApp1" reloadable="false">
<Parameter name="CUSTOMER_CODE" value="CUSTOMER1" />
</Context>
I wish that each customer has a separate log based on code defined in context.xml.
Unfortunately this config doesn't work in my logback-config.xml:
<property name="LOG_FILE" value="${ROOT_LOG}/${CUSTOMER_CODE}/myApp.log}"/>
A folder CUSTOMER_CODE_IS_UNDEFINED is created in "ROOT_LOG" directory. "ROOT_LOG" is provided by a system property.
Is there any way to make this logback configuration working?
The use of properties defined in application.properties works well (I renamed my logback.xml to logback-spring.xml). It seems to me that Spring boot does not set Tomcat context parameters in Environnement before initialize logging. Any idea for a workaround? Thanks.
I finally found a solution to get my customer code available in Spring Environment bean before logging initialization. Not very pretty but it's working:
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application extends SpringBootServletInitializer {
public static String APP_CUSTOMER_CODE;
/**
* Initialization of Spring Boot App with context param customer code
*/
#Override
protected SpringApplicationBuilder configure(final SpringApplicationBuilder builder) {
final Map<String, Object> initProps = new HashMap<>();
initProps.put("CUSTOMER_CODE", APP_CUSTOMER_CODE);
return builder.properties(initProps).sources(Application.class);
}
/**
* Method called before Spring Initialization
*/
#Override
public void onStartup(final ServletContext servletContext) throws ServletException {
APP_CUSTOMER_CODE = servletContext.getInitParameter("CUSTOMER_CODE");
super.onStartup(servletContext);
}
}
In addidtion, i must declare a springProperty tag in logback-spring.xml to use the variable:
<springProperty name="CUSTOMER_CODE" source="CUSTOMER_CODE"/>
I have a Servlet 3.0 web app that uses both Spring and Jersey. I currently have it set up using the SpringServlet configured as a filter in web.xml, and the resource classes annotated with both #Path and #Component. Here's the web.xml snippet:
<filter>
<filter-name>jersey-serlvet</filter-name>
<filter-class>
com.sun.jersey.spi.spring.container.servlet.SpringServlet
</filter-class>
<init-param>
<param-name>
com.sun.jersey.config.property.packages
</param-name>
<param-value>com.foo;com.bar</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.feature.FilterForwardOn404</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jersey-serlvet</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
This setup works, but I really want to get this set up with annotations only - no web.xml config. My first attempt at this was to remove the above SpringServlet configuration and create a class that extends Application. Here's a snippet of that:
#ApplicationPath("/*")
public class MyApplication extends PackagesResourceConfig {
public MyApplication() {
super("com.foo;com.bar");
HashMap<String, Object> settings = new HashMap<String, Object>(1);
settings.put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
this.setPropertiesAndFeatures(settings);
}
}
This works in that the JAX-RS resources are registered and I can hit them at their URLs, but they throw NullPointerExceptions when they try and use their autowired properties... this makes sense because I'm guessing the resources are now being loaded by Jersey and are not Spring managed beans, therefore no autowiring.
Despite a fair bit of searching around I cannot find any way of loading the Jersey resources as Spring beans with annotations only. Is there such a way? I don't really want to have to write a bunch of code for the resources to manually fetch the Spring context and invoke the DI if I can help it.
If annotations-only isn't going to work, then I can live with the filter config in web.xml if I can specify an Application class to load instead of a list of packages to scan. If I can get rid of the package list in there and just specify an Application class instance then I'll be content.
Obviously it would be great if someone had a definitive answer for me but I'd also be grateful for any pointers or hints of where else I could look or things to try.
Thanks,
Matt
Below is part of my app, which uses Servlet 3.0, Spring, Jersey 1.8 and it has no web.xml:
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
final AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("com.myapp.config");
final FilterRegistration.Dynamic characterEncodingFilter = servletContext.addFilter("characterEncodingFilter", new CharacterEncodingFilter());
characterEncodingFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
characterEncodingFilter.setInitParameter("encoding", "UTF-8");
characterEncodingFilter.setInitParameter("forceEncoding", "true");
final FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy());
springSecurityFilterChain.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
servletContext.addListener(new ContextLoaderListener(context));
servletContext.setInitParameter("spring.profiles.default", "production");
final SpringServlet servlet = new SpringServlet();
final ServletRegistration.Dynamic appServlet = servletContext.addServlet("appServlet", servlet);
appServlet.setInitParameter("com.sun.jersey.config.property.packages", "com.myapp.api");
appServlet.setInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters", "com.myapp.api.SizeLimitFilter");
appServlet.setLoadOnStartup(1);
final Set<String> mappingConflicts = appServlet.addMapping("/api/*");
if (!mappingConflicts.isEmpty()) {
throw new IllegalStateException("'appServlet' cannot be mapped to '/' under Tomcat versions <= 7.0.14");
}
}
}
I haven't been able to get my ideal result but I have been able to make some progress, so I'll post here in case it helps anyone else. I was able to use the Spring Servlet to specify my application class, thereby removing the package list from the web.xml.
The web.xml changes required are in the init params (the filter mapping is not shown but is still required):
<filter>
<filter-name>jersey-serlvet</filter-name>
<filter-class>
com.sun.jersey.spi.spring.container.servlet.SpringServlet
</filter-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name> <!-- Specify application class here -->
<param-value>com.foo.MyApplication</param-value>
</init-param>
</filter>
And then in the application class I had to change the way I called the super constructor slightly:
public MyApplication() {
super("com.foo", "com.bar"); // Pass in packages as separate params
HashMap<String, Object> settings = new HashMap<String, Object>(1);
settings.put(ServletContainer.FEATURE_FILTER_FORWARD_ON_404, true);
this.setPropertiesAndFeatures(settings);
}
Still not exactly what I was after but at least this pulls a little more config into Java code and out of the web.xml, which is important for me as I'm trying to hide this detail.
Two options spring to mind (no pun intended).
Maybe you could extend SpringServlet with your own class and add appropriate servlet 3.0 annotations to it.
Going along with your approach of switching from the SpringServlet to an Application class, you could solve the no-autowiring problem by enabling Spring build-time or load-time bytecode weaving. That enables Spring to inject objects instantiated by anywhere instead of only objects created by Spring. See "Using AspectJ to dependency inject domain objects with Spring".
First of all, in a servlet 3.0 container you don't really need a web.xml.
But with Jersey 2.0 you can set a flag to scan the whole web app for annotated resources:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>jersey</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.servlet.provider.webapp</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Spring will be enabled automatically if you include this jar:
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>2.3.1</version>
</dependency>
I used Jersey with my previously made project using SpringMVC. I based my code on the Spring's official documentation.
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
// Don't create the Listener that Jersey uses to create.
// There can only be one linstener
servletContext.setInitParameter("contextConfigLocation", "<NONE>");
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// Add app config packages
context.setConfigLocation("config.package");
// Add listener to the context
servletContext.addListener(new ContextLoaderListener(context));
// Replacing:
// <servlet-name>ServletName</servlet-name>
// <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
// <init-param>
// <param-name>com.sun.jersey.config.property.packages</param-name>
// <param-value>webservices.packages</param-value>
// </init-param>
// <load-on-startup>1</load-on-startup>
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("ServletName", new DispatcherServlet(dispatcherContext));
appServlet.setInitParameter("com.sun.jersey.config.property.packages", "org.sunnycake.aton.controller");
appServlet.setLoadOnStartup(1);
appServlet.addMapping("/RootApp");
}
}
The configuration classes in config.package are:
// Specifies that there will be bean methods annotated with #Bean tag
// and will be managed by Spring
#Configuration
// Equivalent to context:component-scan base-package="..." in the xml, states
// where to find the beans controlled by Spring
#ComponentScan(basePackages = "config.package")
public class AppConfig {
/**
* Where will the project views be.
*
* #return ViewResolver como el XML
*/
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
return viewResolver;
}
}
Hibernate configuration
// Specifies that there will be bean methods annotated with #Bean tag
// and will be managed by Spring
#Configuration
// Equivalent to Spring's tx in the xml
#EnableTransactionManagement
// Equivalent to context:component-scan base-package="..." in the xml, states
// where to find the beans controlled by Spring
#ComponentScan({"config.package"})
// Here it can be stated some Spring properties with a properties file
#PropertySource(value = {"classpath:aplicacion.properties"})
public class HibernateConfig {
/**
* Inyected by Spring based on the .properties file in the
* \#PropertySource tag.
*/
#Autowired
private Environment environment;
/**
* Here it's created a Session Factory, equivalent to the Spring's config file one.
*
* #return Spring Session factory
*/
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
// Uses the datasource
sessionFactory.setDataSource(dataSource());
// Indicates where are the POJOs (DTO)
sessionFactory.setPackagesToScan(new String[]{"dto.package"});
// Se asignan las propiedades de Hibernate
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
/**
* Propiedades de la base de datos (Según environment)
*
* #return Nuevo DataSource (Configuración de la base de datos)
*/
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
return dataSource;
}
/**
* Hibernate properties
*
* #return Properties set with the configuration
*/
private Properties hibernateProperties() {
Properties properties = new Properties();
// Dialect (Mysql, postgresql, ...)
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
// Show SQL query
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
return properties;
}
/**
* Inyected by sessionFactory
*/
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
}
This is a full example. First of all - don't use any web.xml. Use only code below.
Rest resource:
#Path("hello")
public class HelloResource {
#GET
#Produces(MediaType.TEXT_PLAIN)
public Response hello() {
String output = "Hello World!";
return Response.status(200).entity(output).build();
}
}
Rest application (note "core" in package name)
#ApplicationPath("rest")
public class RestApplication extends javax.ws.rs.core.Application {
public RestApplication() {
}
#Override public Set<Class<?>> getClasses() {
return Set.of(
HelloResource.class
);
}
}
Spring web configuration.
#Configuration
#EnableWebMvc
#ComponentScan(basePackageClasses = {
})
public class WebConfig implements WebMvcConfigurer {
private static final Logger logger = LoggerFactory.getLogger(WebConfig.class);
#Autowired
private ApplicationContext applicationContext;
public WebConfig() {
}
}
Spring initializer
//This #Order is required!!!
#Order(Ordered.HIGHEST_PRECEDENCE)
public class MyWebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
var ctx = new AnnotationConfigWebApplicationContext();
//spring WebMvcConfigurer
ctx.register(WebConfig.class);
ctx.setServletContext(servletContext);
//Spring servlet
var servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/");
// Register Jersey 2.0 servlet
ServletRegistration.Dynamic jerseyServlet = servletContext.addServlet("jerseyServlet",
"org.glassfish.jersey.servlet.ServletContainer");
//note "javax.ws.rs.Application" doesn't have "core"
jerseyServlet.setInitParameter("javax.ws.rs.Application", RestApplication.class.getName());
jerseyServlet.addMapping("/rest/*");
jerseyServlet.setLoadOnStartup(1);
}
}
And it must work on, for example, http://127.0.0.1:8080/rest/hello
I am updating an existing Java EE web application that uses Spring.
In my web.xml, there is a servlet defined as follows:
<servlet>
<display-name>My Example Servlet</display-name>
<servlet-name>MyExampleServlet</servlet-name>
<servlet-class>com.example.MyExampleServlet</servlet-class>
</servlet>
now, in this class I need to add an #Autowite annotation:
class MyExampleServlet extends HttpServlet {
#Autowired (required = true)
MyExampleBean myExampleBean;
[...]
}
the problem is that MyExampleBean is initialized by the Application Server
(in my case, weblogic.servlet.internal.WebComponentContributor.getNewInstance...)
so, Spring is not aware of that, and Spring does not have a chance to wire "myExampleBean".
How to solve that?
that is, how I need to modify web.xml or MyExampleServlet so that MyExampleServlet gets the reference to myExampleBean?
A possibility would be to add this init code inside MyExampleServlet,
but it requires a reference to servletContext. How to get a reference to servletContext?
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
myExampleBean = (MyExampleBean) context.getBean("myExampleBean");
I see, HttpServlet/GenericServlet has a getServletContext() method,
(and the application server calls first the servlet's init(ServletConfig config), and config contains a reference to servletContext).
See http://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/GenericServlet.html
The code modified:
class MyExampleServlet extends HttpServlet {
MyExampleBean myExampleBean;
#Override
public void init() throws ServletException {
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
myExampleBean = (MyExampleBean) context.getBean("myExampleBean");
}
[...]
}
in your application context xml, you need something like
<bean id="myExampleBean" class="path/to/myExampleBean">
We use the new environment profiles feature of spring 3.1. We currently set the active profile by setting the environment variable spring.profiles.active=xxxxx on the server to which we deploy the application.
We think this is a suboptimal solution as the war file we want to deploy should just have an additional properties file which sets the environment in which the spring app context should load so the deployment is not dependent on some env var set on the server.
I tried to figure out how to do that and found:
ConfigurableEnvironment.setActiveProfiles()
which I can use to programmatically set the profile but then I still don't know where and when to execute this code. Somewhere where the spring context loads up? Can I load the parameter I want to pass to the method from a properties file?
UPDATE: I just found at docs which I might be able to implement to set the active profile?
In web.xml
<context-param>
<param-name>spring.profiles.active</param-name>
<param-value>profileName</param-value>
</context-param>
Using WebApplicationInitializer
This approach is used when you don't have a web.xml file in Servlet 3.0 environment and are bootstrapping the Spring completely from Java:
class SpringInitializer extends WebApplicationInitializer {
void onStartup(ServletContext container) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.getEnvironment().setActiveProfiles("profileName");
rootContext.register(SpringConfiguration.class);
container.addListener(new ContextLoaderListener(rootContext));
}
}
Where SpringConfiguration class is annotated with #Configuration.
The answer from Thomasz is valid as long as the profile name can be provided statically in the web.xml or one uses the new XML-less configuration type where one could programmatically load the profile to set from a properties file.
As we still use the XML version I investigated further and found the following nice solution where you implement your own ApplicationContextInitializer where you just add a new PropertySource with a properties file to the list of sources to search for environment specific configuration settings. in the example below one could set the spring.profiles.active property in the env.properties file.
public class P13nApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static Logger LOG = LoggerFactory.getLogger(P13nApplicationContextInitializer.class);
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
try {
environment.getPropertySources().addFirst(new ResourcePropertySource("classpath:env.properties"));
LOG.info("env.properties loaded");
} catch (IOException e) {
// it's ok if the file is not there. we will just log that info.
LOG.info("didn't find env.properties in classpath so not loading it in the AppContextInitialized");
}
}
}
You then need to add that initializer as a parameter to the ContextLoaderListener of spring as follows to your web.xml:
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>somepackage.P13nApplicationContextInitializer</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
You can also apply it to DispatcherServlet:
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextInitializerClasses</param-name>
<param-value>somepackage.P13nApplicationContextInitializer</param-value>
</init-param>
</servlet>
For some reason only one way works for me
public class ActiveProfileConfiguration implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
System.setProperty(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, "dev");
System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "dev");
}
....
<listener>
<listener-class>somepackahe.ActiveProfileConfiguration</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Here is a variation on the P13nApplicationContextInitializer approach. However, this time we obtain the path to env properties from JNDI. In my case I set a JNDI global environment variable as coacorrect/spring-profile = file:/tmp/env.properties
In tomcat/tomee server.xml add this: <Environment name="coacorrect/spring-profile" type="java.lang.String" value="/opt/WebSphere/props"/>
Further, in tomcat/tomee, add to the WAR's META-INF/context.xml <ResourceLink global="coacorrect/spring-profile" name="coacorrect/spring-profile" type="java.lang.String"/>
In any container, add appropriate in web.xml
public class SpringProfileApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>{
public static final Logger log = LoggerFactory.getLogger(SpringProfileApplicationContextInitializer.class);
private static final String profileJNDIName="coacorrect/spring-profile";
private static final String failsafeProfile="remote-coac-dbserver";
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
try {
InitialContext ic = new InitialContext();
Object r1 = ic.lookup(profileJNDIName);
if (r1 == null) {
// try the tomcat variant of JNDI lookups in case we are on tomcat/tomee
r1 = ic.lookup("java:comp/env/"+profileJNDIName);
}
if (r1 == null) {
log.error("Unable to locate JNDI environment variable {}", profileJNDIName);
return;
}
String profilePath=(String)r1;
log.debug("Found JNDI env variable {} = {}",r1);
environment.getPropertySources().addFirst(new ResourcePropertySource(profilePath.trim()));
log.debug("Loaded COAC dbprofile path. Profiles defined {} ", Arrays.asList(environment.getDefaultProfiles()));
} catch (IOException e) {
// it's ok if the file is not there. we will just log that info.
log.warn("Could not load spring-profile, defaulting to {} spring profile",failsafeProfile);
environment.setDefaultProfiles(failsafeProfile);
} catch (NamingException ne) {
log.error("Could not locate JNDI variable {}, defaulting to {} spring profile.",profileJNDIName,failsafeProfile);
environment.setDefaultProfiles(failsafeProfile);
}
}
}