Spring源码阅读——记录一次非典型异常排查

最近在做微服务技术方案调研,因为eureka的闭源,注册中心准备启用consul,同时consul还可以把配置中心的活干了。搭了一个简单的springboot2.0+consul2.1的demo,模拟了一下配置中心的任务,没啥问题,准备跟现有项目合并,结果合并后springboot无法正常启动,报了一个很诡异的错误:

[WARN] [main] 2019-01-03 12:10:14,408 org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext : log 87 - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanInitializationException: Failed to process @EventListener annotation on bean with name 'consulAutoServiceRegistration'; nested exception is java.lang.IllegalStateException: Need to invoke method 'bind' declared on target class 'AbstractAutoServiceRegistration', but not found in any interface(s) of the exposed proxy type. Either pull the method up to an interface or switch to CGLIB proxies by enforcing proxy-target-class mode in your configuration. 

这个错误看起来没什么大不了的,甚至于spring只是将其列为了一个warning,还很贴心的给了一大堆错误提示,简单理解一下就是说consulserviceautoconfig这个类在初始化过程中报错了,因为没有找到类的接口定义,但是又使用了jdk的动态代理,导致无法进一步执行bind方法注入,还给出了推荐强制使用cglib代理的解决方案。但其实这个问题没那么简单。首先项目中我们已经配置了aop的代理模式为cglib,其次如果一个类没有实现接口,spring难道不应该在初始化bean的时候就自动去采用cglib代理么?

问题排查的话,先去看一眼报错的类ConsulAutoServiceRegistrationAutoConfiguration:

    @Bean
	@ConditionalOnMissingBean
	public ConsulAutoServiceRegistration consulAutoServiceRegistration(
			ConsulServiceRegistry registry,
			AutoServiceRegistrationProperties autoServiceRegistrationProperties,
			ConsulDiscoveryProperties properties,
			ConsulAutoRegistration consulRegistration) {
		return new ConsulAutoServiceRegistration(registry,
				autoServiceRegistrationProperties, properties, consulRegistration);
	}

方法注入的bean名称默认为方法名,这点方便后面找ConsulAutoServiceRegistration的实例bean,继续看一眼ConsulAutoServiceRegistration类本身没有bind方法,实现在其父类AbstractAutoServiceRegistration:

    @EventListener(WebServerInitializedEvent.class)
	public void bind(WebServerInitializedEvent event) {
		ApplicationContext context = event.getApplicationContext();
		if (context instanceof ConfigurableWebServerApplicationContext) {
			if ("management".equals(
					((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
				return;
			}
		}
		this.port.compareAndSet(0, event.getWebServer().getPort());
		this.start();
	}

初步判定,在进行EventListener注解绑定的时候出现了问题。下一步该干嘛呢?当然不是百度。简单的配置问题百度一下固然可以,但是这个问题明显有点非典型,先不说能不能百度到,即便是百度到答案,不明白为啥会出这样的问题,以后再遇到也一样懵逼。所以解决spring的问题,最好的办法就是,翻源码。

排查之前我们首先要整理一下现有的线索,我们目前已知的线索如下:

  1. 项目确认已经配置了aop:proxy-target-class属性为true,强制使用cglib代理,观察启动日志也可以见到cglib相关代理类的初始化信息,可以验证cglib模块工作正常。
  2. ConsulAutoServiceRegistration的初始化发生了问题,而且问题并不是发生在初始化阶段,而是在实例初始化完成后,进行下一步的实例内部注解的解析封装时出现了问题。

线索2的得出是基于之前对bean初始化过程的一定了解。spring的bean初始化大体上分成加载bean定义,初始化bean实例,执行BeanProcessor处理器。这里涉及一个概念就是BeanProcessor,为了方便称之为bean处理器。bean处理器用来对bean做额外操作,例如修改bean的相关属性。Spring提供了一个BeanPostProcessor接口,用来给开发人员使用。很多人理解BeanProcessor一定会在Bean初始化完成后执行,其实不然。这里主要看如何定义Bean初始化完成这个动作。DefaultListableBeanFactory是一个常用的工厂类,我们看一下其中创建bean的逻辑:

@Override
	public void preInstantiateSingletons() throws BeansException {
		if (logger.isDebugEnabled()) {
			logger.debug("Pre-instantiating singletons in " + this);
		}

		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						final FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
						if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
							isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
											((SmartFactoryBean<?>) factory)::isEagerInit,
									getAccessControlContext());
						}
						else {
							isEagerInit = (factory instanceof SmartFactoryBean &&
									((SmartFactoryBean<?>) factory).isEagerInit());
						}
						if (isEagerInit) {
							getBean(beanName);
						}
					}
				}
				else {
					getBean(beanName);
				}
			}
		}

		// Trigger post-initialization callback for all applicable beans...
		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
						smartSingleton.afterSingletonsInstantiated();
						return null;
					}, getAccessControlContext());
				}
				else {
					smartSingleton.afterSingletonsInstantiated();
				}
			}
		}
	}

方法整体上由两个大循环构成,其中第一个循环负责初始化bean,第二个循环用来进行bean初始化后的回调工作。以本次出问题的ConsulAutoServiceRegistration类为例,第一个循环中会创建ConsulAutoServiceRegistration的bean实例,并且同时调用该bean所属的Context中包含的所有BeanProcessor,对bean进行必要性的处理,其中如果涉及aop的相关逻辑,在此时生成的bean对象,本身就已经是一个代理对象:

	@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
        //getBeanPostProcessor会返回该context下所包含的所有类型为BeanProcessor的bean,包括用户
        //自定义的BeanPostProcessor接口
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
            //result代表返回的bean实例,current是由各个processor生成的结果,这些结果根据各个 
            //processor的内部逻辑,有可能是普通对象也有可能是代理对象
			result = current;
		}
		return result;
	}

所以BeanPostProcessor接口的执行时机,并不是我们普通意义上的Bean实例化完成后,而是在bean实例被创建以后,尚未完全实例化的过程中执行。那么什么叫Bean的完全实例化?还是以DefaultListableBeanFactory为例,在该工厂中默认注册了一个DefaultSingletonBeanRegistry这样一个策略类,负责对单例bean的获取销毁等工作。当中有这样一个方法:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值