Spring2.0rc3的一个Bug
作者:孙鑫 日期:
在使用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()方法,在这里,也就会调用TransactionAttributeSourcePointcut的toString()方法,该类是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类的拦截器还未注入,其成员变量transactionInterceptor为null。因此抛出空指针异常。
解决办法有两个:1、采用构造器注入;2、设置日志级别高于DEBUG。
但这不是根本的解决办法,在
后记:今天在弄事务时又想到了这个问题,于是上Spring的网站上下了一个RC4版本,欣喜的发现,Spring的开发团队已经修正了这个错误,TransactionAttributeSourcePointcut类的getTransactionAttributeSource()方法改为如下:
private TransactionAttributeSource getTransactionAttributeSource() {
return (transactionInterceptor != null ? transactionInterceptor.getTransactionAttributeSource() : null);
}
和我当初设想的一致。