unittesting with ServletContextListener - spring

i have an ServletContextListener which initializes my DB. and i've addid it in my web.xml:
<listener>
<listener-class>util.MySessionListener</listener-class>
</listener>
When i startup the server everything is fine.
but when i run my AbstractTransactionalJUnit4SpringContextTests-tests it is not called. what can i do?

Is this ServletContextListener or HttpSessionListener? Your naming is confusing.
Nevertheless, simply run this in #Before:
MockServletContext mockServletContext = new MockServletContext()
new MySessionListener().contextInitialized(
new ServletContextEvent(mockServletContext)
)
Where MockServletContext comes from Spring. If your listener uses WebApplicationContextUtils, you will need to add this before running contextInitialized():
mockServletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
yourTestApplicationContext
);
Where yourTestApplicationContext is an instance of ApplicationContext you can simply inject in your test case:
#Autowired
private ApplicationContext yourTestApplicationContext;

i solved it with the
ApplicationContextAware
interface:
<bean id="springApplicationContext" class="server.utils.SpringApplicationContext" />
and in the
public void setApplicationContext(ApplicationContext context) throws BeansException {
i can do my initialisation, hibernate and everything is ready at this point

Related

Spring Cloud Config Client Without Spring Boot

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" />

how to get beans from application context loaded by contextloaderlistener?

I'm new to Spring/Spring Mvc and here's my problem. In my webapp, besides the spring-servlet.xml , I have a jdbc.xml which define beans like datasource, dao ... Before using contextloaderlistener , I load my jdbc.xml inside the Controller's constructor like this ApplicationContext context = new ClassPathXmlApplicationContext("jdbcbeans.xml") then get the beans from that. But since I'm using contextloaderlistener to load the file, how can I get the reference to the context ? I was able to set up everything using those #Autowired things but I just want to know is there any way to do that ?
You can use WebApplicationContextUtils.
ApplicationContext context;
context = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
See here for details.
You can do the following to get an instance of Application Context
in case of a container managed bean use ApplicationContextAware interface
public class MyBean implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext acontext) throws BeansException {
context = context;
}
public static ApplicationContext getApplicationContext() {
return context;
}
}
Or you can write the following
#Autowired
private ApplicationContext Context;
An instance of the Application Context will be autowired.

Spring scheduled-tasks datasource

I want to make applicationContext inside scheduler method. it is possible ? )
<task:annotation-driven executor="executor" scheduler="scheduler"/>
<task:executor id="executor" pool-size="5"/>
<task:scheduler id="scheduler" pool-size="10"/>
And my class :
#Scheduled(fixedDelay=100)
public void doSomething() {
}
How me add that ? :
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
That use it code :
JDBCEntityDAO obj = (JDBCEntityDAO) applicationContext.getBean("taEntityDAO");
Help please
Just add #Autowired or any other dependency injection annotation to your job classes (the ones with #Scheduled - annotated methods). If you need a reference to the ApplicationContext, make your job implement the ApplicationContextAware interface.
This will work with the Spring native scheduler.
If you use Quartz, you'll have to do a little more work, see here inject bean reference into a Quartz job in Spring? .
By the way, you can use #Resourse annotation to inject beans by name.

init a spring bean from web.xml

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">

Spring applicationcontext loading hooks

Are there any hooks into the Spring ApplicationContext loading process?
I want to run a piece of code just before the application context loads (before any beans/properties/aspects/etc... are instantiated).
thanks in advance
Maybe BeanFactoryPostProcessors will suite your needs? They are run after the whole XML configuration files are read, but before any (other) beans are instantiated.
You can also use the ApplicationListener to receive notification of events like ContextClosedEvent, ContextStartedEvent or ContextStoppedEvent.
More information in the IoC Container chapter.
I just declared my own ContextLoaderListener in order to perform the desired work before loading the Spring context. It suits for web-apps, just declare it before the Spring context listener:
public class MyServletContextListener implements ServletContextListener {
#Override
public void contextDestroyed(ServletContextEvent arg0) {
}
#Override
public void contextInitialized(ServletContextEvent arg0) {
//Perform your stuff here
}
}
<listener>
<listener-class>
com.myCompany.listeners.MyServletContextListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Resources