问题
项目远程调用使用Spring遇到了问题:无法初始化ApplicationContext(其实是找不到业务类,也就是类加载器的问题,不要被外在异常迷惑)
跟踪代码发现有两个问题:
1)ClassPathXmlApplicationContext 不方便传入ClassLoader参数,默认取到了线程上下文的ClassLoader
2)AspectJExpressionPointcut 类实现了BeanFactoryAware接口,但相关的setBeanFactory()方法没有被调用
解决
问题1还比较容易解决:
a) 使用ClassPathXmlApplicationContext(String[] configLocations, boolean refresh)构造,
第二个参数传入false,禁止自动刷新
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(...,false);
b) 设置类加载器:ctx.setClassLoader(自定义的classloader);
c) 手动刷新:ctx.refresh();
问题2明显是Spring的Bug
外在表现:问题1解决后,AOP拦截类报错。
作者的原意自然是希望实现了Aware接口的类,相关的set回调被call,不幸的是大神忘了call了。
解决思路:
1) 设置线程上下文类加载器,这个方案最简单,其实也能同时解决问题1。
Thread.currentThread().setContextClassLoader(customCl);
AOP出问题根本原因也是类加载器不对:
AspectJExpressionPointcut 的 相关代码如下:
private void checkReadyToMatch() {
if (getExpression() == null) {
throw new IllegalStateException("Must set property 'expression' before attempting to match");
}
if (this.pointcutExpression == null) {
//由于beanFactory是null(没设置),所以取的是 ClassUtils.getDefaultClassLoader()
// 又是取了线程上下文的classloader
this.pointcutClassLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?
((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() :
ClassUtils.getDefaultClassLoader());
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
}
}
但毕竟,依赖线程上下文是不好的设计。
2) 按调用栈一路修改代码:
ReflectiveAspectJAdvisorFactory getAdvisor方法是AspectJExpressionPointcut 的构造处
@Override
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
int declarationOrderInAspect, String aspectName) {
validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
AspectJExpressionPointcut expressionPointcut = getPointcut(
candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
if (expressionPointcut == null) {
return null;
}
// TODO 对expressionPointcut手动设置beanFactory
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
如何得到beanFactory呢?
也有多种选择:
1)上层传入
BeanFactoryAspectJAdvisorsBuilder的构造方法如下
/**
* Create a new BeanFactoryAspectJAdvisorsBuilder for the given BeanFactory.
* @param beanFactory the ListableBeanFactory to scan
*/
public BeanFactoryAspectJAdvisorsBuilder(ListableBeanFactory beanFactory) {
this(beanFactory, new ReflectiveAspectJAdvisorFactory());
}
可以增加ReflectiveAspectJAdvisorFactory有参构造方法来接收beanFactory参数
2)debug 代码发现上面的getAdvisor方法的 aspectInstanceFactory参数实际是LazySingletonAspectInstanceFactoryDecorator类型
,这是个装饰模式,装饰了一个BeanFactoryAspectInstanceFactory,看名字就跟BeanFactory有关系,
不错,它第一个字段就是:private final BeanFactory beanFactory; 但没公开,又要反射了。
总结
程序总是与bug相爱相杀,伟大的程序也不例外。没碰到bug? 换个打开方式试试.