NetBeansPlatformWithSpring

Spring in NetBeans Platform application

Contributed By; Filip Jirsák


[[{TableOfContentsTitle=TableOfContents} | {TableOfContents title='Table of Contents'}]]

Introduction


This text describes usage of Spring and Hibernate in simple NetBeans Platform application. (It was tested with NB Platform version 6.1) That means usage of Spring's services and Hibernate's ORM from your application modules. It doesn't mean Platform modules became Spring managed. Instead (in this tutorial) Spring is one of the platform application modules and offers its services to other modules.

Service Locator


We need some connecting point which will connect Spring's world (with its dependency injection etc.) with NetBeans world (for eg; Module ClassLoader's). NetBeans components (forms etc.) cannot be (as far as I know) managed by Spring.

Spring managed component must its whole lifecycle live inside Spring context (especially it must be created by Spring), but NetBeans form is instantiated by Netbeans itself. So we can't use dependency injection for pull service implementation into form.

Instead we will use ServiceLocator to obtain service implementation from Spring. Probably there is way how to use NetBeans Lookup for this, but in its tutorial we will use simple singleton class.
This singleton bring Spring context to other modules. So in other modules you can write;

ServiceLocator.getInstance().getBean("addressService");

This code ask Spring context for bean named "addressService".

Configure Spring, Create ServiceLocator

Before you can use Spring this way, you have to configure it. In this example it is done during creation of ServiceLocator instance;

public class ServiceLocator {

    private final GenericApplicationContext ctx;
    private static final ServiceLocator instance = new ServiceLocator();

    public static ServiceLocator getInstance() {
        return instance;
    }

    //tunnel between our ServiceLocator and Spring - obtain bean from Spring by name
    public Object getBean(String beanName) {
        return this.ctx.getBean(beanName);
    }

    //basic initialization of Spring context
    private ServiceLocator() {
        this.ctx = new GenericApplicationContext();
        XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
        xmlReader.loadBeanDefinitions(new ClassPathResource("applicationConfig.xml", ServiceLocator.class));
        ctx.refresh();
    }

}

In case, we wish use Netbeans Lookup instead of Singleton, this initialization should be written in the Module Installer.

Works Only, If In Single Module

But this works only in case all code is in single module. But we probably wish to have more modules - some for Spring, some for API, some for implementation. So we must deal with classloaders. Spring will be in one module (library wrapper) and our code will be in different one (or in any more modules).

NetBeans Optimizing ClassLoader


NetBeans 6.1 use new optimizing classloader. This classloader use prefix nbjcl (instead of jar) as protocol name for locating jar files.
At time of writing this, Spring doesn't know this prefix. So we must give to it extended path resolver, which identify nbjcl as right prefix of jar file path.

        @Override
        protected ResourcePatternResolver getResourcePatternResolver() {
            return new PathMatchingResourcePatternResolverEx(this);
        }


        @Override
        protected boolean isJarResource(Resource resource) throws IOException {
            return super.isJarResource(resource) || "nbjcl".equals(resource.getURL().getProtocol());
        }

Full sample code;

public class ServiceLocator {

    private final GenericApplicationContext ctx;
    private static final ServiceLocator instance = new ServiceLocator();

    public static ServiceLocator getInstance() {
        return instance;
    }

    public Object getBean(String beanName) {
        return this.ctx.getBean(beanName);
    }

    private ServiceLocator() {
        this.ctx = new GenericApplicationContext() {

            //use extended path resolver
            @Override
            protected ResourcePatternResolver getResourcePatternResolver() {
                return new PathMatchingResourcePatternResolverEx(this);
            }
        };

        XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
        xmlReader.loadBeanDefinitions(new ClassPathResource("applicationConfig.xml", ServiceLocator.class));
        ctx.refresh();
    }

    private static class PathMatchingResourcePatternResolverEx extends PathMatchingResourcePatternResolver {

        public PathMatchingResourcePatternResolverEx(ResourceLoader resourceLoader) {
            super(resourceLoader);
        }

        public PathMatchingResourcePatternResolverEx(ClassLoader classLoader) {
            super(classLoader);
        }

        public PathMatchingResourcePatternResolverEx() {
            super();
        }

        //is jar file if parent resolver tell it is jar or if it has protocol "nbjcl"
        @Override
        protected boolean isJarResource(Resource resource) throws IOException {
            return super.isJarResource(resource) || "nbjcl".equals(resource.getURL().getProtocol());
        }
    }
}

Module ClassLoader's and Class Visibility


Spring have to feel about all classes, which may be Spring managed. There are two solutions - either Spring module must depend on all modules containing Spring managed classes or Spring must use context classloader, which see all classes currently loaded into application.

  • In the first case we would have hard dependency between Spring and all modules containing service layer code - so we can't change constellation of modules after build.
  • In this tutorial we will use second way - Spring sees all modules. So we have to set its context classloader;
this.ctx.setClassLoader(Thread.currentThread().getContextClassLoader());

Full sample code;

public class ServiceLocator {

    private final GenericApplicationContext ctx;
    private static final ServiceLocator instance = new ServiceLocator();

    public static ServiceLocator getInstance() {
        return instance;
    }

    public Object getBean(String beanName) {
        return this.ctx.getBean(beanName);
    }

    private ServiceLocator() {
        this.ctx = new GenericApplicationContext() {

            @Override
            protected ResourcePatternResolver getResourcePatternResolver() {
                return new PathMatchingResourcePatternResolverEx(this);
            }
        };

        //set Spring's classloader to context classloader
        this.ctx.setClassLoader(Thread.currentThread().getContextClassLoader());

        XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
        xmlReader.loadBeanDefinitions(new ClassPathResource("applicationConfig.xml", ServiceLocator.class));
        ctx.refresh();
    }

    private static class PathMatchingResourcePatternResolverEx extends PathMatchingResourcePatternResolver {

        public PathMatchingResourcePatternResolverEx(ResourceLoader resourceLoader) {
            super(resourceLoader);
        }

        public PathMatchingResourcePatternResolverEx(ClassLoader classLoader) {
            super(classLoader);
        }

        public PathMatchingResourcePatternResolverEx() {
            super();
        }

        @Override
        protected boolean isJarResource(Resource resource) throws IOException {
            return super.isJarResource(resource) || "nbjcl".equals(resource.getURL().getProtocol());
        }
    }
}

That's not all. We must arrange that all service modules will be loaded before Spring context is initialized - otherwise Spring will see all currently loaded modules on its classloader, but some modules needn't be loaded at that time. We can use module tokens for this. We set some Required Token for our Service Loader module and all other modules providing services will provide this token. So we know all modules with this token will be loaded before we initialize Spring context.

Attention

This works in NetBeans Platform 6.1, but this behavior is not required by NetBeans module contract. In future version it is possible that module system loads only one module providing required token and other modules will be loaded lazily. That it is needed to use module activation/deactivation listener to update Spring's context.

Attached Example


There is sample application in attached file http://wiki.netbeans.org/attach/NetBeansPlatformWithSpring/SampleSuite_NetBeansPlatformWithSpring.zip. This application uses Spring and Hibernate to list addresses from in-memory HSQLDB database. It should "just work" - download it, unzip it, open SampleSuite in NetBeans and run it.

Enhancement Proposal


Use module activation/deactivation listener to update Spring context dynamically; we can use Spring's context hierarchy for it -- every module can be one Spring context.

Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo