Skip to main content

Load your Spring beans as plug-ins

There are different ways of loading Spring beans in a web application – viz using the dispatcher servlet or the context loader listener. A more modular and flexible approach (which also allows you to minimize coupling and dependencies in your applications) is to load the barebones of your application with the core context loaders and load the rest of your beans in a pluggable way. For example you may be interested in loading your page controllers, business tier and data access code as three separate modules. Off course you can do this and then use import in a main spring config file or a comma separate list in your web.xml where you configure the spring bean definition xmls. But what if you just wanted to drop in a jar with Spring beans and be sure they will be loaded and registered in the container. This also eases the pain of swapping an implementation. The goal is to put all your Spring beans in a particular layer, package it as a jar with one or more configuration file(with one following a special naming convention which is used to load them). You can also choose to go for feature based plugin – for example for show employee details use case you can package your view (freemarker template + javascript + css = I will show you how to do this in a later post), page controller (Spring MVC annotated controller), business service (interface + pojo implementation) and data access (interface + implementation) in a jar with configuration file. But my experience with this is if you go for feature based plugin – you can very soon land up with tons of plugins difficult to manage. For the moment lets just focus on the class that makes this pluggable design possible.

/**
* @author dhrubo
*/
package com.middlewareteam.spring.web.context;

import java.io.IOException;

import javax.servlet.ServletConfig;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.web.context.support.XmlWebApplicationContext;

/**
* @author dhrubo
*
*/
public class PluggableXmlWebApplicationContext extends XmlWebApplicationContext {

public static final String PLUGIN_CONTEXT_CONFIG_NAME = "pluginContextConfigName";

private final Logger log = LoggerFactory.getLogger(PluggableXmlWebApplicationContext.class);

@Override
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader)
throws IOException {

super.loadBeanDefinitions(reader);

log.info("Loading plugin application contexts.");
log.info("Seeking plugin application context configuration name from servlet config ");

ServletConfig config = this.getServletConfig();
String resourceName = null;
if(config != null) {

resourceName = this.getServletConfig().getInitParameter(PLUGIN_CONTEXT_CONFIG_NAME);
log.info("Configured plugin resource name : {}",resourceName);
}

if(StringUtils.isEmpty(resourceName)) {
log.info("Seeking plugin application context configuration name from servlet context/web app context.");
resourceName = this.getServletContext().getInitParameter(PLUGIN_CONTEXT_CONFIG_NAME);
}

log.info("Final plugin resouce name : {}",resourceName);

ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
try {
Resource[] configResources = resourcePatternResolver
.getResources(resourceName);


if (configResources.length != 0) {
log.info("Plugins found");
for (Resource resource : configResources) {
log.info("Resource == " + resource);

}
reader.loadBeanDefinitions(configResources);
} else {
log.info("No Plugins found.");
}
} catch (IOException e) {
throw new RuntimeException("Error loading plugin definitions",e);
}

}

}



Now this is how you should configure this class in your Spring bean loading mechanism.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="dms" version="2.5">
<display-name>oollaasweb</display-name>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/config/log4j.xml</param-value>
</context-param>

<!-- security beans are here -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/config/spring-security.xml
</param-value>
</context-param>

<context-param>
<param-name>contextClass</param-name>
<param-value>
com.middlewareteam.spring.web.context.PluggableXmlWebApplicationContext
</param-value>
</context-param>

<context-param>
<param-name>pluginContextConfigName</param-name>
<param-value>
classpath*:pluginRootApplicationContext.xml
</param-value>
</context-param>


<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>


<!-- Root web application context -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- Spring security filters -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
<servlet-name>staticresourceservlet</servlet-name>
<servlet-class>com.middlewareteam.spring.web.servlet.ResourceServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>

<servlet>
<servlet-name>dmsservlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/spring-web.xml</param-value>
</init-param>
<init-param>
<param-name>contextClass</param-name>
<param-value>com.middlewareteam.spring.web.context.PluggableXmlWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>pluginContextConfigName</param-name>
<param-value>classpath*:pluginApplicationContext.xml</param-value>
</init-param>

<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>staticresourceservlet</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>dmsservlet</servlet-name>
<url-pattern>/index.html</url-pattern>
</servlet-mapping>


<servlet-mapping>
<servlet-name>dmsservlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>


<servlet-mapping>
<servlet-name>dmsservlet</servlet-name>
<url-pattern>*.json</url-pattern>
</servlet-mapping>


<welcome-file-list>

<welcome-file>index.html</welcome-file>

</welcome-file-list>

</web-app>

Now just put your spring config xml with the name pluginRootApplicationContext.xml in the root of the jar and you are ready to roll out pluggable modules. You can use multiple configuration file here too. Just import them in the pluginRootApplicationContext.xml. Also if you use Maven 2 put the pluginRootApplicationContext.xml in the src/main/resources folder. Things will be much easier to package and deploy if you are using Maven.


There are few limitations to this model though. Since Spring does not yet support hot deployment (application context is read only at run time you cannot add beans while your app is running, you can refresh though but it is risky) you have to re-start your applications for the plugins to get listed in the Spring container. As I wrote earlier this is in line with Eclipse plugin architecture. Another limitation is we do not yet have a versioning. What this means is what if you are using application layer plugins and you have just modified a handful of classes. In that case you like that your plugin system can identify and register the new version and retire the old version during an application restart. However you can always delete the old jar and replace it with the new one till we have the version management.

Comments

  1. Dhrubo, and that's what I am thinking about all this stuff:

    http://geekandpoke.typepad.com/geekandpoke/2010/08/how-do-you-know-your-framework-is-complex-enough.html

    :-)

    ReplyDelete
  2. Yeh frameworks are complex beasts. Open source are bigger beasts...!

    ReplyDelete
  3. Dhrubo, we have the same requirements that you comment above, but let me ask you something, if we make a Spring web application with minimal features (e.g. login page, main menu and logout) and all others features inside a JARs file, each JAR file contains views file, controllers, service and model, so when we need to plug in a new feature just create a new JAR file and put on classpath outside WAR file. With that scenario do you believe that approach will work for us ?

    ReplyDelete

Post a Comment

Popular posts from this blog

CKEDITOR 3.x - Simplest Ajax Submit Plugin

  I have assumed that you have downloaded and got started with CKEDITOR. Step 1 – The html file is shown below: <html> <head> <title>Writer</title> <meta content="text/html; charset=utf-8" http-equiv="content-type" /> <script type="text/javascript" src="ckeditor/ckeditor.js"></script> <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script> <style> .cke_contents { height: 400px !important; } </style> </head> <body> <form action="sample_posteddata.php" method="post"> <textarea id="editor" > </textarea> <script type="text/javascript"> //<![CDATA[ CKEDITOR.replace( 'editor', { fullPage : true, uiColor : '#9AB8F3', toolbar : 'MyToolbar' }); //]]> </script> </form> </body> </html> Note that the jquery js

Part 3 - Integrating Tiles, Thymeleaf and Spring MVC 3

In this post I will demonstrate how to integrate Apache Tiles with Thymeleaf. This is very simple. The first step is to include the tiles and thymeleaf-tiles extension dependencies. I will include them in the pom.xml. Note we wil lbe using Tiles 2.2.2 Listing 1 - parent/pom.xml --- thymeleaf-tiles and tiles dependencies <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <!-- Tiles --> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-core</artifactId> <version>${tiles.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.tiles</groupId> <artifactId>tiles-template</artifactId> <version>${tiles.version}</version> <scope>compile</s

How to Stand up a Spring Cloud Config Server?

Setup and Configure Spring Cloud Config Server Project Spring Cloud Config Server is just another Spring Boot application. It provides several infrastructure micro services to centralize access to configuration information backed by a version controlled (well at least in the case of default GIT storage) repository. Step 1 - Create a Spring Boot project in STS with the dependencies shown in Figure 2. Figure 1 - Creating Spring Boot project to setup Spring Cloud Config Server Figure 2 - Spring Cloud Config Server dependencies Click on 'Finish' to complete the creation of the Spring Boot project in STS. The build.gradle file is shown in listing below. There is only one dependency to the Spring Cloud Config Server. Also Spring Cloud release train 'Dalston.SR1'. Step 2 - Annotate the class containing main method The next step is to annotate the ConfigServerInfraApplication class with  @EnableConfigServer That's all is needed on the Java si