Spring2.0rc3的一個Bug
更新時間: 2007-05-16 13:37:22來源: 粵嵌教育瀏覽量:882
在使用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。
但這不是根本的解決辦法,在7月10日的“Java企業級開發班”上課時,我曾經對學員說,要修正這個問題實際上非常簡單,只需要TransactionAttributeSourcePointcut類的getTransactionAttributeSource()方法中對transactionInterceptor做一個是否為null的判斷,如果為null,就返回null,如果不為null,就返回transactionInterceptor.getTransactionAttributeSource()。
后記:今天在弄事務時又想到了這個問題,于是上Spring的網站上下了一個RC4版本,欣喜的發現,Spring的開發團隊已經修正了這個錯誤,TransactionAttributeSourcePointcut類的getTransactionAttributeSource()方法改為如下:
private TransactionAttributeSource getTransactionAttributeSource() {
return (transactionInterceptor != null ? transactionInterceptor.getTransactionAttributeSource() : null);
}
和我當初設想的一致。