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