Sunday, January 16, 2011

Spring thread safe injection

This is a problem that hits all teams working with Spring and in general Servlet programming.

The developer tests locally and even in an integration server with some users hitting the application. Everything looks great but when traffic gets a little bit serious all kind of problems arise.

To find out if the program is thread safe you can use smart load tests with Jakarta JMeter but it would be time consuming. It would be ideal if a Unit test could simply make the build fail in case a potential thread safe problem is detected.

One of the advantages of using Spring injected singleton beans is the application heap footprint, so light, so fast ...

But the developer must be aware of the fact that every @Service, @Repository and any injected resource must be thread safe. That is the developer responsibility. A simple instance (class) member could result in a total application disaster.

So here is a JUnit test you can use to find out thread safe problems in your Spring code. Just modify it to take into account the exceptions to the rule: If an instance field is needed it should be either thread-safe or it must be declared final and static.
package com.nestorurquiza;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Set;

import net.sourceforge.stripes.util.ResolverUtil;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-config.xml"})
public class ThreadSafeTest {
    
    private static final Logger log = LoggerFactory.getLogger(ThreadSafeTest.class);
    
    @Test
    public void testInstanceVariables() {
        ResolverUtil<Object> resolver = new ResolverUtil<Object>();
        resolver.findAnnotated(Repository.class, "com.nestorurquiza.dao");
        Set<Class<? extends Object>> classes = resolver.getClasses();
        resolver.findAnnotated(Service.class, "com.nestorurquiza.service");
        classes.retainAll(resolver.getClasses());
        resolver.findAnnotated(Controller.class, "com.nestorurquiza.web");
        classes.retainAll(resolver.getClasses());
        List<String> errors;
        for (Class<? extends Object> controller : classes) {
            Field[] fields = controller.getDeclaredFields();
            for(Field field : fields) {
                Annotation[] annotations = field.getAnnotations();
                boolean isThreadSafe = false;
                for(Annotation annotation : annotations) {
                    String annotationName = annotation.annotationType().getName();
                    if(annotationName.equals("org.springframework.beans.factory.annotation.Autowired") || annotationName.equals("javax.persistence.PersistenceContext")) {
                        isThreadSafe = true;
                    }
                }
                int mod = field.getModifiers();
                boolean noError = isThreadSafe || Modifier.isFinal(mod) && Modifier.isStatic(mod);
                String errorDescription = controller.getName() + " is not thread-safe because of field " + field.getName();
                //TODO: Activate the assertion once all problems are corrected to ensure this problem does not hit the team again
                if(!noError) {
                    log.error(errorDescription);
                }
                //assertTrue(errorDescription, noError);
            }
        }
    }
}

No comments:

Followers