不加@Configuration导致的问题
我们先来看看如果不在配置类上添加@Configuration注解会有什么问题,代码示例如下:
@ComponentScan("com.dmz.source.code") //@Configuration public class Config{ @Bean public A a(){ return new A(dmzService()); } @Bean public DmzService dmzService(){ return new DmzService(); } } public class A { public A(DmzService dmzService){ System.out.println("create A by dmzService"); } } @Component public class DmzService { public DmzService(){ System.out.println("create dmzService"); } } public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class); } }
不添加@Configuration注解运行结果:
create dmzService create A by dmzService create dmzService
添加@Configuration注解运行结果:
create dmzService create A by dmzService
在上面的例子中,我们会发现没有添加@Configuraion注解时dmzService被创建了两次, 这是因为第一次创建是被Spring容器所创建的,Spring调用这个dmzService()创建了一个Bean被放入了单例池中(没有添加其它配置默认是单例的),第二次创建是Spring容器在创建a时调用了a(),而a()又调用了dmzService()方法。
这样的话,就出现问题了。
第一,对于dmzService而言,它被创建了两次,单例被打破了
第二,对于a而言,它所依赖的dmzService不是Spring所管理的,而是直接调用的一个普通的java method创建的普通对象。这个对象不被Spring所管理意味着,首先它的域(Scope)定义失效了,其次它没有经过一个完整的生命周期,那么我们所定义所有的Bean的后置处理器都没有作用到它身上,其中就包括了完成AOP的后置处理器,所以AOP也失效了。
首先,Spring就在注释中指出了,通常来说,BeanMethod一般都申明在一个被@Configuration注解标注的类中,在这种情况下,BeanMethod可能直接引用了在同一个类中申明的beanMethod,就像本文给出的例子那样,a()直接引用了dmzService(),我们重点再看看划红线的部分,通过调用另外一个beanMethod进入的Bean的引用会被保证是遵从域定义以及AOP语义的,就像getBean所做的那样。这是怎么实现的呢?在最后被红线标注的地方也有说明,是通过在运行时期为没有被@Configuration注解标注的配置类生成一个CGLIB的子类。
源码分析
Spring是在什么时候创建的代理呢?到目前为止我们应该没有落掉Spring整个启动流程的任何关键代码,那么我们不妨带着这个问题继续往下看。目前来说我们已经阅读到了Spring执行流程图中的3-5步,也就是org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法。其执行逻辑如下:
在之前的分析中我们已经知道了,这个方法的主要作用就是执行BeanFactoryPostProcessor中的方法,首先执行的是BeanDefinitionRegistryPostProcessor(继承了BeanFactoryPostProcessor)的postProcessBeanDefinitionRegistry方法,然后执行postProcessBeanFactory方法。那么目前为止容器中有哪些BeanFactoryPostProcessor呢?Spring内置的BeanFactoryPostProcessor并且在当前这个时机就已经被注册到容器中的只有一个,就是ConfigurationClassPostProcessor
,在之前的文章中我们已经分析过了它的postProcessBeanDefinitionRegistry方法,这个方法主要是为了完成配置类的解析以及对组件的扫描,紧接着我们就来看看它的postProcessBeanFactory方法做了什么。其源码如下:
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { int factoryId = System.identityHashCode(beanFactory); // 防止重复处理 if (this.factoriesPostProcessed.contains(factoryId)) { throw new IllegalStateException( "postProcessBeanFactory already called on this post-processor against " + beanFactory); } this.factoriesPostProcessed.add(factoryId); // 在执行postProcessBeanDefinitionRegistry方法的时就已经将这个id添加到registriesPostProcessed集合中了 if (!this.registriesPostProcessed.contains(factoryId)) { processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory); } // 看起来这个方法就是完成了代理 enhanceConfigurationClasses(beanFactory); // 添加了一个后置处理器 beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory)); }
enhanceConfigurationClasses源码分析
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { // map中放置的是所有需要被代理的类 Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>(); for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) { // 省略异常跟日志代码.... // 这个代码的含义就是如果是一个被@Configuration注解标注的类,那么将其放入到configBeanDefs这个集合中 configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef); } } if (configBeanDefs.isEmpty()) { // nothing to enhance -> return immediately return; } // 对配置类进行代理的核心类 ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer(); for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) { AbstractBeanDefinition beanDef = entry.getValue(); // 对于配置类永远使用cglib代理 beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); try { // cglib代理是基于类实现的,所以在这之前要明确代理的类是什么 Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader); if (configClass != null) { // 通过ConfigurationClassEnhancer获取到一个经过代理的class Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader); // 省略日志.... // 将原有的配置类的bd中的beanClass属性替换成代理后的class beanDef.setBeanClass(enhancedClass); } } } catch (Throwable ex) { throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex); } } }
这段代码非常简单,其目的就是生成一个enhancedClass(经过了cglib增强的class),然后用其替换目标配置类对应的BeanDefinition中的beanClass属性。那么我们接下来需要分析的就是enhancedClass是如何生成的?其核心的代码在ConfigurationClassEnhancer中,所以我们要分析下ConfigurationClassEnhancer的源码,在分析它的源码前,我们需要对cglib有一定的了解。
1、cglib原理分析
1.1、使用示例
public class Target{ public void f(){ System.out.println("Target f()"); } public void g(){ System.out.println("Target g()"); } } public class Interceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("I am intercept begin"); //Note: 此处一定要使用proxy的invokeSuper方法来调用目标类的方法 proxy.invokeSuper(obj, args); System.out.println("I am intercept end"); return null; } } public class Test { public static void main(String[] args) { // 设置这个属性,将代理类的字节码文件生成到F盘的code目录下 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\code"); //实例化一个增强器,也就是cglib中的一个class generator Enhancer eh = new Enhancer(); //设置目标类 eh.setSuperclass(Target.class); // 设置拦截对象 eh.setCallback(new Interceptor()); // 生成代理类并返回一个实例 Target t = (Target) eh.create(); t.f(); t.g(); } }
运行结果为:
I am intercept begin Target f() I am intercept end I am intercept begin Target g() I am intercept end
可以看到,通过上面这种方式,我们已经对Target中的方法完成了增强(可以在方法执行前后插入我们自己的定制的逻辑)
1.2、原理分析
查看F盘的code目录,会发现多了以下几个文件
其中第二个文件就是我们的代理类字节码,将其直接用IDEA打开
// 省略多余的方法,我们就关注g方法 public class Target$$EnhancerByCGLIB$$788444a0 extends Target implements Factory { final void CGLIB$g$0() { super.g(); } // 经过代理过的g方法 public final void g() { // 查看是否有拦截器存在 MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0; if (tmp4_1 == null) { CGLIB$BIND_CALLBACKS(this); tmp4_1 = this.CGLIB$CALLBACK_0; } // 如果有拦截器的存在的话,直接调用拦截器的方法 if (this.CGLIB$CALLBACK_0 != null) { tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy); } // 如果没有拦截器,说明不需要代理,直接调用父类方法,也就是目标类的方法 else{ super.g(); } } }
从上面的代码中我们可以看出 1. 代理类继承了目标类 2. 目标类中的方法在代理类中对应了两个方法,就以上面例子中的目标类中的g()方法为例,其对应的代理类中的两个方法为 - CGLIB$g$0() - g()
它们之间的关系如下所示
实际被增强的方法是代理对象中的g方法,当它被调用时,在执行的过程中会调用CGLIB$g$0()方法,而这个方法又会调用代理类的父类,也就是目标类中的g()。
从这里就能看出,跟JDK动态代理不同的是,cglib代理采用的是继承的方式生成的代理对象。
在上面的例子中,我们实现了对cglib中方法的拦截,但是就目前而言我们没有办法选择性的拦截目标类中的某一个方法,假设现在我们只想拦截Target中的g方法而不拦截f方法有什么方法呢?我们看下面这个例子
public class Main { public static void main(String[] args) { System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\code"); //实例化一个增强器,也就是cglib中的一个class generator Enhancer eh = new Enhancer(); //设置目标类 eh.setSuperclass(Target.class); // 设置拦截对象 eh.setCallbacks(new Callback[]{new Interceptor(), NoOp.INSTANCE}); eh.setCallbackFilter(new CallbackFilter() { @Override public int accept(Method method) { if(method.getName().equals("g")) // 这里返回的是上面定义的callback数组的下标,0就是我们的Interceptor对象,1是内置的NoOp对象,代表不做任何操作 return 0; else return 1; } }); // 生成代理类并返回一个实例 Target t = (Target) eh.create(); t.f(); t.g(); } }
运行结果:
Target f() I am intercept begin Target g() I am intercept end
此时f方法已经不会被代理了
/