spring3.0版本configuration注解对象的启动过程分析

2016/08/03 15:43:41 No Comments

spring 3.0版本的一个很重要的特性在于支持注解式配置.之前在xml配置中的所有信息均可以通过注解进行配置了.本篇即从实现的角度理解spring是通过怎么样的机制来实现的.为了避免重复发明轮子,尽量采用之前已有的技术来进行实现才是最好的处理方式.

使用Configuration对象

整个的支持首先即是通过configuration这个对象来完成的.

第一种方式是通过声明AnnotationConfigApplicationContext上下文,然后通过调用其register方式来完成.如下参考所示:

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AppConfig.class);
        context.register(App2Config.class);

第二种方式,则是通过在一个统一的xml中声明<context:annotation-config/>,然后再通过声明<context:component-scan base-package="com.iflym"/>,将自己的@configuration对象放到相应的包下面,让spring容器自行进行解析即可.因为@Configuration注解上有声明@Component注解,因此也可以认为这也是注解一个bean的方式.

这两种方式均是通过将configBean放到spring容器当中,我们可以认为就是手动地注册相应的bean到容器当中.只不过这里的bean是特殊的configBean.

ConfigurationClassPostProcessor处理器

在整个spring容器处理周期当中,存在一个特殊的处理周期,即invokeBeanFactoryPostProcessors.这里即在容器完成初始解析之后,再进行特定的处理,以添加更多的bean或者对容器内的bean进行修改.这里分别通过接口BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor来完成.后者表示会对容器进行修改,会添加新的bean定义信息.

针对configBean的处理即是通过类ConfigurationClassPostProcessor来完成,其实现了BeanDefinitionRegistryPostProcessor接口,同时实现了BeanFactoryPostProcessor接口.(因为definitionRegistry接口本身即是继承了beanFactoryPost接口). 

ConfigurationClassPostProcessor类是通过静态方法AnnotationConfigUtils.registerAnnotationConfigProcessors注册到spring容器当中.而此静态方法,又是通过<context:annotation-config/>或者是AnnotationConfigApplicationContext声明自动完成此操作.

解析configBean

进入到ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry实现当中.在一些简单的判断之后,会跳转到 processConfigBeanDefinitions(registry) 方法当中.

其简单的逻辑即是遍列整个容器bean列表,找出有@Configuration注解的bean,排序,解析此bean,扫描其上的各个注解,然后读取bean定义.重复这个过程,直到所有configBean均处理完毕.主要的代码如下所示

	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	//加载当前已知所有bean定义
		List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
		String[] candidateNames = registry.getBeanDefinitionNames();

		//过滤出要处理的configBean,通过ConfigurationClassUtils.checkConfigurationClassCandidate判定
		......
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		......


		//如注释所示,排个序,如果多个configBean的初始化顺序有要求
		// Sort by previously determined @Order value, if applicable
		......

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
		//使用外部表存储当前已解析的,避免循环解析或者是重复解析
		Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
		do {
			//解析configBean 对应ConfigurationClassParser.parse.
			//再对应于对应ConfigurationClassParser.parse(AnnotationMetadata metadata, String beanName)
			//最终处理方法doProcessConfigurationClass
			//此方法会处理以下信息 
			//类上的注解 PropertySource(属性源) ComponentScan(类扫描) Import(引入其它configBean) ImportResource(包含xml)
			//方法上的注解 @Bean(bean方法定义)
			parser.parse(candidates);

			//验证, configBean类本身不能为final bean方法必须为public,如果不是static,则不能为final
			//原因在于configBean需要被cglib重写,这些是cglib的要求
			parser.validate();

			//加载所有方法上有@Bean注解的对象,定义为bean对象
			//如果方法为static,则通过class+factoryMethod进行定义
			//如果不是,则通过factoryBean+method进行定义,这里的factoryBean即configBean,表示调用configBean的相应的方法获取而来
			//bean的name首先找@Bean注解中配置,实在没有就以方法名作为beanName
			this.reader.loadBeanDefinitions(configClasses);
		
			......
	}

configBean类改写

在configBean中,bean之间的引用可以直接通过方法调用来完成.如下的例子所示:

    @Bean
    public AnnoTa1 anno() {
        return new AnnoTa1();
    }

    @Bean
    public AnnoTa2 annoTa2() {
        AnnoTa2 annoTa2 = new AnnoTa2();
        annoTa2.setAnnoTa1(anno());
        return annoTa2;
    }

在上面的代码中,首先通过anno方法定义了Ta1对象,在annoTa2中又通过anno()方法再次调用了一次.如果是普通的方法调用,那么就会产生两个ta1对象.对于spring容器,必须保证两次或者多次调用都返回同一个bean定义.那么可以认为,第二次对anno方法的调用都将只返回第一次创建的bean对象.这里就必然涉及到方法代理处理,显然这是spring的长项.

具体的实现可以理解为,如果是第一次调用,将调用原生的方法直接创建出bean对象并放到容器当中,后续的调用则根据key直接返回此bean即可.这里的key即可以认为就是方法名.

类改写,仍通过ConfigurationClassPostProcessor来完成,即相应的接口实现 postProcessBeanFactory 来完成.其接口方法表示将在容器初始化时对一些容器内bean进行调整,如著名的aspectj代理.

改写是通过调用beanDefinition中的beanClass来完成,整个过程通过方法 enhanceConfigurationClasses 来完成,而相应的调用即是通过类 ConfigurationClassEnhancer 来进行增强.其背后通过cglib来处理.相应的处理过程,可以查看如何创建出enhancer对象.如下所示:

	private Enhancer newEnhancer(Class<?> superclass, ClassLoader classLoader) {
		Enhancer enhancer = new Enhancer();
		//设定父类为当前configClass
		enhancer.setSuperclass(superclass);
		//增加新的实现接口,此接口的目的在于创建configBean时注入相应的容器对象beanFactory
		//以方便下次获取bean时,能够找到容器,再直接在容器中拿即可
		enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
		enhancer.setUseFactory(false);
		enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
		//类修改策略,这里在生成类中加入$$beanFactory字段
		enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
		//对调用方法的拦截
		enhancer.setCallbackFilter(CALLBACK_FILTER);
		enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
		return enhancer;
	}

	//其中callback为
	private static final Callback[] CALLBACKS = new Callback[] {
			//拦截对@bean的调用
			new BeanMethodInterceptor(),
			//对注入beanFactory的拦截,即要调用相应的setBeanFactory,同时为相应的字段$$beanFactory设置值
			new BeanFactoryAwareMethodInterceptor(),
			NoOp.INSTANCE
	};

在以上的代码中,即可看出如何通过cglib来完成对方法拦截,从而实现之前所说的一次处理过程.

这里有一个小细节,如何判断方法是第一次调用呢.spring的判断方法为判断调用的方法是否就是当前factoryBean创建的方法.即SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod()方法通过threadLocal缓存了当前正在调用的factoryMethod,而这个方法即是通过外部创建factoryBean时设置的.

访问方法有2种思路,一种是由容器创建时,通过getBean时触发,这里factoryMethod肯定是调用method.另一种,则是通过在方法中调用,而方法中引用仍也是最终会通过getBean来转发,最终进入到与第一种相同的处理方式.具体逻辑可以查看BeanMethodInterceptor.intercept 的处理.

总结

从整个流程来看,为实现configBean注解式处理,spring只是将之前一直在使用的beanFactoryPostProcessor再扩展了一下,就直接支持这种处理.整个处理思路还是很明确的,不需要开辟2条逻辑,整个容器的启动路径仍然是与之前是一样的.

转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/201608030001.html

相关文章:

留下足迹