Spring AOP ClassLoader Bug

问题

   项目远程调用使用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? 换个打开方式试试.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值