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