My Blog

This blog is to be considered a staging area for articles. Some of them might become published on other sites (such as GitHub or the official Vaadin blog), some might not. cool

«Back
Experimenting with Vaadin, Spring and Serialization

I think everyone who has tried to get Vaadin and Spring to play nicely together has sooner or later run into either problems with the session scope or with session serialization. Although there is a good add-on that fills the "gap" between Vaadin and Spring, I wanted to see for myself how much work it would take to get Spring, Vaadin and session serialization to work without AspectJ.

Disclaimer: This is only an experiment, not a best practice for using Vaadin with Spring. Have a look at the code, try it out, then draw your own conclusions. It may be useful for your project, or it might not.

The architecture of my approach is the following (similar to the one used by the Spring Stuff add-on by Archie Cobbs):

The standard Vaadin application servlet is used to create instances of the Vaadin application and the standard Spring context listener is used to initialize the root web application context. The Vaadin application instance itself is not managed by Spring, but it can configure itself with the root application context, making it possible to inject beans from the root application context into the Vaadin application. This is done in the following way:

@Override
public void init() {
    configureApplication();
    initApplication();
}

The configureApplication() method performs the configuration in the following way:

parentContext = (ConfigurableWebApplicationContext) ContextLoader.getCurrentWebApplicationContext();
BeanConfigurerSupport configurerSupport = new BeanConfigurerSupport();
configurerSupport.setBeanFactory(parentContext.getBeanFactory());
configurerSupport.afterPropertiesSet();
configurerSupport.configureBean(this);
configurerSupport.destroy();

The parentContext variable is a transient field of my Vaadin application class (ContextAwareApplication in this example).

Next, a Vaadin application specific context is created. There are different ways of doing this, but I prefer to use the annotation based context to avoid the creation of XML files. In this example, I scan the package of the Vaadin application class, looking for annotated Spring beans:

vaadinApplicationContext = new AnnotationConfigApplicationContext();
vaadinApplicationContext.setParent(parentContext);
final String packageName = getClass().getPackage().getName();
vaadinApplicationContext.scan(packageName);

The vaadinApplicationContext variable is a transient field of my Vaadin application class, meaning it has to be recreated after the application has been deserialized. For beans that are not serializable, this is not a problem, but serializable beans (e.g. Vaadin components) need some special treatment.

I've solved the problem in the following way: Every singleton bean definition for a bean that is serializable is dynamically converted into a bean factory. The bean factory stores the bean in a serializable map that is owned by the Vaadin application instance. When the application context asks the bean factory for a bean, it first checks the map. If a bean is found, it is returned, otherwise, a new instance is created, stored in the map and returned. What this means in practice is that after a deserialization, the transient beans will be recreated and the serializable ones will be reconfigured into the application context. The implementation looks like this:

vaadinApplicationContext.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerResolvableDependency(Application.class, ContextAwareApplication.this);
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            final BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
            if (definition.isSingleton() && definition.getBeanClassName().startsWith(packageName) && isSerializable(definition)) {
                final String factoryName = beanName + "Factory";
                beanFactory.registerSingleton(factoryName, new SerializableSingletonFactory(beanName, definition));
                definition.setFactoryBeanName(factoryName);
                definition.setFactoryMethodName("get");
            }
        }
    }
});

Also note that the Vaadin application instance itself is registered as a resolvable dependency. This makes it possible to inject the application instance into beans in the application specific context.

The SerializableSingletonFactory class is defined as an inner class of my Vaadin application class:

private Map<String, Serializable> singletons = new HashMap<String, Serializable>();

public class SerializableSingletonFactory {
    private final BeanDefinition definition;
    private final String beanName;

    public SerializableSingletonFactory(String beanName, BeanDefinition definition) {
        this.definition = definition;
        this.beanName = beanName;
    }

    public Object get() {
        Object bean = singletons.get(beanName);
        if (bean == null) {
            try {
                Class<?> beanClass = parentContext.getClassLoader().loadClass(definition.getBeanClassName());
                bean = beanClass.newInstance();
            } catch (Exception e) {
                throw new IllegalStateException("Could not create instance of class " + definition.getBeanClassName(), e);
            }
            singletons.put(beanName, (Serializable) bean);
        }
        return bean;
    }
}

Finally, I just have to refresh the application context to actually create the beans:

vaadinApplicationContext.refresh();

In its entirety, the configureApplication() method looks like this:

protected void configureApplication() {
    parentContext = (ConfigurableWebApplicationContext) ContextLoader.getCurrentWebApplicationContext();

    final BeanConfigurerSupport configurerSupport = new BeanConfigurerSupport();
    configurerSupport.setBeanFactory(parentContext.getBeanFactory());
    configurerSupport.afterPropertiesSet();
    configurerSupport.configureBean(this);
    configurerSupport.destroy();

    vaadinApplicationContext = new AnnotationConfigApplicationContext();
    vaadinApplicationContext.setParent(parentContext);

    final String packageName = getClass().getPackage().getName();

    vaadinApplicationContext.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            beanFactory.registerResolvableDependency(Application.class, ContextAwareApplication.this);
            for (String beanName : beanFactory.getBeanDefinitionNames()) {
                final BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
                if (definition.isSingleton() && definition.getBeanClassName().startsWith(packageName) && isSerializable(definition)) {
                    final String factoryName = beanName + "Factory";
                    beanFactory.registerSingleton(factoryName, new SerializableSingletonFactory(beanName, definition));
                    definition.setFactoryBeanName(factoryName);
                    definition.setFactoryMethodName("get");
                }
            }
        }
    });
    vaadinApplicationContext.scan(packageName);
    vaadinApplicationContext.refresh();
}

The initApplication() method is much smaller, it just asks the application context for a bean extending the Window class and uses it as the application's main window:

protected void initApplication() {
    setMainWindow(getVaadinApplicationSpecificContext().getBean(Window.class));
}

Now there is just some life-cycle management left to do. First, the application-specific context must be closed when the Vaadin application is closed:

@Override
public void close() {
    super.close();
    if (vaadinApplicationContext != null) {
        vaadinApplicationContext.close();
    }
}

Second, the context must be recreated after deserialization:

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    configureApplication();
}

Now the application class itself is ready. Before I give you the entire code, I just want to say a few things about the main window class. The interesting parts of the code are shown here:

@Component
public class MainWindow extends Window {
    @Autowired
    private Application application;
    
    @Resource
    private transient ApplicationContext context;
    
    @Resource
    private transient ApplicationEventPublisher eventPublisher;
    
    @EJB
    private transient MyEJB ejb;

    public MainWindow() {
        super("Spring Demo");
    }

    @PostConstruct
    protected void initComponents() {
        removeAllComponents();
        // configure layout, add components, etc.
    }
}

Now I'd like you to pay attention to the following:

  • The class has been annotated with @Component, meaning Spring will automatically detect it during package scanning. There is no need to define it in XML.
  • Beans can be injected using @Autowired, @Resource and @EJB. Spring also supports @Inject.
  • References to beans that are not serializable must be marked as transient.
  • The initComponents() method is called both when the window is created for the first time and after it has been deserialized. Thus, remember to remove any old components before adding them again.

The source code of the entire demo application can be downloaded here. It is a Maven-project designed to be deployed on a JEE6 Web Profile application server (such as GlassFish 3.1). You may freely do whatever you like with it. If you have any comments or find problems with the code (such as memory leaks or incorrect injections), please feel free to leave them below. You can also use Twitter or e-mail to give feedback.

Previous Next
Comments

Contact me

You can contact me using e-mail, Skype or telephone. If you choose any of the two latter options, remember that my timezone is GMT+0200 (EET). I normally have my phone switched off outside working hours.

You can also find me on LinkedIn.

Please note, that I cannot provide free support on a one-to-one basis. Please ask your questions on public channels (Vaadin forum or Skype chat) or buy professional support services.