Spring2.0rc3的一个Bug

作者:孙鑫   日期:2006-9-6

在使用Spring自动代理配置事务时,要配置TransactionAttributeSourceAdvisor(自动代理只能用于Advisor),该类需要一个事务拦截器(TransactionInterceptor)的引用。TransactionAttributeSourceAdvisor类提供了两种方式注入TransactionInterceptor对象,一种是通过构造器注入,一种是通过setter注入。当采用构造器注入时,运行良好;当采用setter注入时,则会抛出下列异常:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionAttributeSourceAdvisor' defined in class path resource [beans.xml]: Instantiation of bean failed; nested exception is java.lang.NullPointerException

Caused by: java.lang.NullPointerException

       at org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor$TransactionAttributeSourcePointcut.getTransactionAttributeSource(TransactionAttributeSourceAdvisor.java:102)

       at org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor$TransactionAttributeSourcePointcut.hashCode(TransactionAttributeSourceAdvisor.java:121)

       at java.lang.Object.toString(Object.java:209)

       at java.lang.String.valueOf(String.java:2577)

 

错误原因:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory抽象类中,createBean()方法负责创建Bean。在该方法中有下列段代码:

bean = instanceWrapper.getWrappedInstance();

 

                     // Eagerly cache singletons to be able to resolve circular references

                     // even when triggered by lifecycle interfaces like BeanFactoryAware.

                     if (isAllowCircularReferences() && isSingletonCurrentlyInCreation(beanName)) {

                            if (logger.isDebugEnabled()) {

                                   logger.debug("Eagerly caching bean [" + bean + "] with name '" + beanName +

                                                 "' to allow for resolving potential circular references");

                            }

也就是在Bean创建成功后,并且配置的日志级别是DEBUG,那么打印缓存的bean信息。而问题也就出在这个日志记录代码中。

       TransactionAttributeSourceAdvisor类继承自AbstractPointcutAdvisor,在这个父类中,重写了toString()方法,在上述日志输出语句中,打印bean对象(类型为TransactionAttributeSourceAdvisor),就会调用其继承的toString()方法,该方法如下:

public String toString() {

              return "PointcutAdvisor: pointcut [" + getPointcut() + "], advice [" + getAdvice() + "]";

              //return "PointcutAdvisor: pointcut [], advice [" + getAdvice() + "]";

       }

在这个代码中又调用了getPointcut()方法,TransactionAttributeSourceAdvisor类的该方法如下:

public Pointcut getPointcut() {

              return this.pointcut;

       }

成员变量pointcut的类型定义如下:

private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut();

toString()方法中构造字符串时,如果有对象类型,就会调用其toString()方法,在这里,也就会调用TransactionAttributeSourcePointcuttoString()方法,该类是TransactionAttributeSourceAdvisor中的一个内部类,本身没有定义toString()方法,于是就会调用Object类中的toString()方法。Object类中的toString()方法如下:

java.lang.Object

public String toString() {

       return getClass().getName() + "@" + Integer.toHexString(hashCode());

    }

Object类的toString()方法打印类名和对象的HashCode,问题就出在hashCode()的调用上,内部类TransactionAttributeSourcePointcut虽然没有重写toString()方法,但是重写了hashCode()方法。该方法如下:

public int hashCode() {

                     TransactionAttributeSource tas = getTransactionAttributeSource();

                     return (tas != null ? tas.hashCode() : 0);

              }

在这个方法中,它去计算TransactionAttributeSource对象的hashCode,因此要调用getTransactionAttributeSource(),该方法是TransactionAttributeSourcePointcut中的方法,该方法如下:

private TransactionAttributeSource getTransactionAttributeSource() {

                     return transactionInterceptor.getTransactionAttributeSource();

              }

由于采用了Setter注入,而此时调用蓝色显示的代码时,Bean对象刚构造完成,属性还未设置,也就是说,TransactionAttributeSourceAdvisor类的拦截器还未注入,其成员变量transactionInterceptornull。因此抛出空指针异常。

解决办法有两个:1、采用构造器注入;2、设置日志级别高于DEBUG

但这不是根本的解决办法,在710的“Java企业级开发班”上课时,我曾经对学员说,要修正这个问题实际上非常简单,只需要TransactionAttributeSourcePointcut类的getTransactionAttributeSource()方法中对transactionInterceptor做一个是否为null的判断,如果为null,就返回null,如果不为null,就返回transactionInterceptor.getTransactionAttributeSource()

后记:今天在弄事务时又想到了这个问题,于是上Spring的网站上下了一个RC4版本,欣喜的发现,Spring的开发团队已经修正了这个错误,TransactionAttributeSourcePointcut类的getTransactionAttributeSource()方法改为如下:

private TransactionAttributeSource getTransactionAttributeSource() {

                     return (transactionInterceptor != null ? transactionInterceptor.getTransactionAttributeSource() : null);

              }

和我当初设想的一致。