• 谈谈Spring对于@Configuration的Cglib代理


    1、现象

    众所周知,Spring中配置类是用来代替配置文件的,在老一些的日子里面我们使用XML配置,而如今大多使用JavaBean的方式配置。

    一个简单的配置类如下:

    @Configuration
    @ComponentScan("com.dh")
    public class AppConfig {
    	@Bean
    	public Entity1 entity1(){
    		return new Entity1();
    	}
    	@Bean
    	public Entity2 entity2(){
    		return new Entity2();
    	}
    }
    

    这是一个简单的配置类,没啥特殊的.

    那么现在开始我们的示例代码:

    Entity1.java:

    public class Entity1 {
    	public Entity1() {
    		System.out.println("Entity1 is initing....");
    	}
    }
    

    Entity2.java:

    public class Entity2 {
    	public Entity2() {
    		System.out.println("Entity2 is initing....");
    	}
    }
    

    AppConfig.java:

    @Configuration
    @ComponentScan("com.dh")
    public class AppConfig {
    	@Bean
    	public Entity1 entity1(){
    		return new Entity1();
    	}
    	@Bean
    	public Entity2 entity2(){
    		entity1();
    		return new Entity2();
    	}
    }
    

    这个时候运行项目后打印结果为:

    Entity1 is initing....
    Entity2 is initing....
    

    当我把AppConfig.java中@Configuration注解删除掉,那么打印结果为下:

    Entity1 is initing....
    Entity1 is initing....
    Entity2 is initing....
    

    你可能会想,我们没删除Configuration注解的时候为什么"Entity1 is initing...."不是打印两次而只打印一次,为什么去掉Configuration注解后就恢复了我们认知当中的情况?
    这个就是设置到Spring中配置类的full和lite,进而涉及到Spring中使用的Cglib代理.

    2、解析

    这里直接对其原因进行解析,下面会进行源码解析.

    首先在我们Spring当中配置类有两种类型:

    * 1、full(带有@Configuration注解的,全配置类)
    * 2、lite(不带有@Configuration注解,但带有其他配置注解,例如Import、ComponentScan,部分配置类)
    

    如果配置类是lite的,那么Spring则不会去代理其配置类,只会单纯的实现这些配置类该有的功能.

    如果配置类是full的,那么则会代理配置类,在配置类中调用其方法去获取Bean的时候,首先去BeanFactory中getBean(返回值类型.class),如果容器中存在则不会去实际调用真实的方法,直接就return了

    代理配置类的伪代码:

    // Cglib学过的都知道这个
    Enhancer enhancer = new Enhancer();
    /* 代理类继承于被代理类(也就是我们的配置类) */
    enhancer.setSuperclass(需要被代理的配置类.class);
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    /* 方法拦截,在执行方法前会回调其这里给的方法 */
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            // method.getReturnType():获取配置类当前调用的方法的配置返回值类型Class,这里如果容器中存在,则直接不需要调用配置类的实际方法了,直接return
            if(Objects.nonNull(工厂对象.getBean(method.getReturnType()))){
                return 工厂对象.getBean(method.getReturnType());
            }
            // 容器中不存在的时候才会去调用配置类的方法
            return methodProxy.invokeSuper(o,objects)
        }
    });
    代理类对象 对象 = enhancer.create();
    

    3、源码

    这里我们关心几点:

    1、配置类是在哪里解析的?
    2、配置类在哪里判断其是否为全配置类的?full/lite
    3、在哪里对配置类进行代理的
    

    启动类:

    public static void main(String[]args){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(AppConfig.class);
        applicationContext.refresh();
    }
    

    首先我们要知道Spring容器做实际功能的代码是在refresh中的,而本次聊的这个在refresh代码中的"invokeBeanFactoryPostProcessors(beanFactory);"这一行代码中.

    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    	// 进入这行代码中	
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    		......
    	}
    

    注意在这里面涉及到Spring的BeanFactoryPostProcessor,BeanFactoryPostProcessor是Spring在还未初始化Bean的时候给我们提供的回调,当然Spring自己也写了BeanFactoryPostProcessor回调来实现配置类解析,这个类叫做ConfigurationClassPostProcessor,但是我们看了这个类以后发现,ConfigurationClassPostProcessor这个类直接实现接口为:
    BeanDefinitionRegistryPostProcessor,这个BeanDefinitionRegistryPostProcessor继承于BeanFactoryPostProcessor,那么就说明我们的ConfigurationClassPostProcessor
    同时做了两个接口的接口实现,在其实现接口中postProcessBeanDefinitionRegistry用来做配置类功能解析,例如扫描包,引入类、配置等,postProcessBeanFactory用来做代理.

    那么我们回到上面的代码流程中,进入其invokeBeanFactoryPostProcessors方法.

    找到其第一个invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);代码,这里是进行配置类配置解析的.进入这行代码

    private static void invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
        for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessBeanDefinitionRegistry(registry);
        }
    }
    

    此时postProcessors只有一条数据[ConfigurationClassPostProcessor],进入ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法.

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + registry);
        }
        this.registriesPostProcessed.add(registryId);
    
        processConfigBeanDefinitions(registry);
    }
    

    进入其processConfigBeanDefinitions方法:

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        String[] candidateNames = registry.getBeanDefinitionNames();
        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                .....很多代码
            }
            // 判断是不是配置类
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {	// line1
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
        .......很多代码
    }
    

    注意这里Spring将容器中所有的BeanDefinition的名称拿出来,然后遍历,这里注意能拿到出我们的AppConfig,因为我们前面register进去了,然后第一个if就是判断是否已经解析过了,如果是则不操作,否则
    就判断当前的这个类是否为配置类,那么我们进入ConfigurationClassUtils.checkConfigurationClassCandidate里面去:

    public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
        String className = beanDef.getBeanClassName();
        if (className == null || beanDef.getFactoryMethodName() != null) {
            return false;
        }
    
        AnnotationMetadata metadata;/* 此处需要拿到该BeanDefinition的metadata信息,但由于注解BeanDefinition、XML的BeanDefinition获取metadata的方式不一样,所以需要分别判断其类型后再获取*/
        if (beanDef instanceof AnnotatedBeanDefinition &&
                className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
            // Can reuse the pre-parsed metadata from the given BeanDefinition...
            metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
        }
        else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
            // Check already loaded Class if present...
            // since we possibly can't even load the class file for this Class.
            Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
            metadata = new StandardAnnotationMetadata(beanClass, true);
        }
        else {
            try {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
                metadata = metadataReader.getAnnotationMetadata();
            }
            catch (IOException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not find class file for introspecting configuration annotations: " + className, ex);
                }
                return false;
            }
        }
        if (isFullConfigurationCandidate(metadata)) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);/*设置标志位*/
        }
        else if (isLiteConfigurationCandidate(metadata)) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);/*设置标志位*/
        }
        else {
            return false;
        }
        return true;
    }
    

    这个方法来总结一下:

    • metadata是当前类的元信息,包括注解等
    • 根据这个当前BeanDefinition不同的类型,使用不同的方法去获取其元信息metadata
    • 在if (isFullConfigurationCandidate(metadata))这行代码中会去判断当前元信息中是否包含@Configuration注解,如果有则认为其是一个完全的配置类,则设置标注位值为full
    • 在else if中判断是否为接口,如果为接口,则返回false,那么整个代码就return false了,如果元信息中包含以下注解:Component、ComponentScan、Import、ImportResource、方法中包含@Bean,那么则认为其是一个配置类,但不完全是,return一个true,设置标志位为lite
      我们来验证看看,进入isFullConfigurationCandidate方法:
    public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
        return metadata.isAnnotated(Configuration.class.getName());
    }
    

    这就不过多解释了,我们再来看isLiteConfigurationCandidate:

    public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
        // Do not consider an interface or an annotation...
        if (metadata.isInterface()) {
            return false;
        }
    
        // Any of the typical annotations found?
        for (String indicator : candidateIndicators) {
            if (metadata.isAnnotated(indicator)) {
                return true;
            }
        }
    
        // Finally, let's look for @Bean methods...
        try {
            return metadata.hasAnnotatedMethods(Bean.class.getName());
        }
        catch (Throwable ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
            }
            return false;
        }
    }
    

    重要的是for遍历判断的地方,candidateIndicators是一个集合,里面有四个元素,就是上面我们说的,然后在try中判断类中的方法里面是否包含@Bean,如果包含了也认为是一个配置类, 只是不完全是哈.
    至此,我们解释了第1和第2点.

    下面我们来看在哪里做的代理:

    我们回到invokeBeanFactoryPostProcessors方法去,在我们前面说的Spring是去处理BeanDefinitionRegistryPostProcessor回调,我们翻到这个invokeBeanFactoryPostProcessors方法的最底下去

    我们进入到这个方法里面去:

    private static void invokeBeanFactoryPostProcessors(
            Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {
        for (BeanFactoryPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessBeanFactory(beanFactory);
        }
    }
    

    注意这里的回调的方法和我们上面的回调是不一样的,虽然是同样的类,但是实现了两个接口,我们这里进入ConfigurationClassPostProcessor中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);
        if (!this.registriesPostProcessed.contains(factoryId)) {
            // BeanDefinitionRegistryPostProcessor hook apparently not supported...
            // Simply call processConfigurationClasses lazily at this point then.
            processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
        }
    
        enhanceConfigurationClasses(beanFactory);
        beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
    }
    

    进入其enhanceConfigurationClasses方法.

    public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
        Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {    // 代码行1---->line1
                if (!(beanDef instanceof AbstractBeanDefinition)) {
                    throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
                            beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
                }
                else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
                    logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
                            "' since its singleton instance has been created too early. The typical cause " +
                            "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
                            "return type: Consider declaring such methods as 'static'.");
                }
                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();
            // If a @Configuration class gets proxied, always proxy the target class
            beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
            try {
                // Set enhanced subclass of the user-specified bean class
                Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
                if (configClass != null) {
                    Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);   // 代码行2---->line2
                    if (configClass != enhancedClass) {
                        if (logger.isDebugEnabled()) {
                            logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
                                    "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
                        }
                        beanDef.setBeanClass(enhancedClass);
                    }
                }
            }
            catch (Throwable ex) {
                throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
            }
        }
    }
    

    注意看我们的代码行1---->line1:

    这个代码里面做了这件事情

    public static boolean isFullConfigurationClass(BeanDefinition beanDef) {
        return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
    }
    // CONFIGURATION_CLASS_FULL的值=full
    

    判断其是否为full的,如果是full的那么则会存储到configBeanDefs这个map集合中.

    然后在代码行2----->line2:Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);这串代码就是获取当前类(配置类)的代理类,我们进入enhancer.enhance方法中

    public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
        if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
            if (logger.isDebugEnabled()) {
                logger.debug(String.format("Ignoring request to enhance %s as it has " +
                        "already been enhanced. This usually indicates that more than one " +
                        "ConfigurationClassPostProcessor has been registered (e.g. via " +
                        "<context:annotation-config>). This is harmless, but you may " +
                        "want check your configuration and remove one CCPP if possible",
                        configClass.getName()));
            }
            return configClass;
        }
        Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));        // line1
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s",
                    configClass.getName(), enhancedClass.getName()));
        }
        return enhancedClass;
    }
    

    再进入这里的line1处的newEnhancer方法中,点进去:

    private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
        Enhancer enhancer = new Enhancer();
        /* Cglib代理中,代理类继承于被代理类 */
        enhancer.setSuperclass(configSuperClass);       // line1
        /* 设置接口,注意此接口中可以回调拿到BeanFactory */
        enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});       // line2
        enhancer.setUseFactory(false);
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);       // line3
        enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));   // line4
        enhancer.setCallbackFilter(CALLBACK_FILTER);        // line5
        enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());      // 拦截器类型
        return enhancer;
    }
    

    line1处设置目标类为代理类,因为cglib是基于继承的.

    line2处时设置一些实现接口,注意我们想一下我们在解析的地方已经说了调用方法之前getBean判断是否工厂中存在,那么肯定就需要有工厂,
    此时我们需要先知道在Spring当中有各种XXXXAware接口回调,Spring会回调各种对象过来,例如当我们实现了BeanFactoryAware的时候,
    那么就会回调setBeanFactory方法传递给BeanFactory工厂,然后我我们看看EnhancedConfiguration这个接口,其继承了BeanFactoryAware:

    public interface EnhancedConfiguration extends BeanFactoryAware {
    }
    

    那么就说明我们的代理类可以拿到Bean工厂,那么这就说明我们前面解析的从工厂中getBean后判断是否存在这个是成立的.

    line3处设置Bean名字生成策略

    line4设置字节码生成器,这里Spring使用的是BeanFactoryAwareGeneratorStrategy,其继承与DefaultGeneratorStrategy类,我们看其BeanFactoryAwareGeneratorStrategy的transform方法

    protected ClassGenerator transform(ClassGenerator cg) throws Exception {
        ClassEmitterTransformer transformer = new ClassEmitterTransformer() {
            @Override
            public void end_class() {
                declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
                super.end_class();
            }
        };
        return new TransformingClassGenerator(cg, transformer);
    }
    

    注意看其end_class方法,其设置了一个字段,名称为$$beanFactory,类型为BeanFactory,这个字段用来存储BeanFactory.

    line5设置拦截器类型.

    至此,我们解释了其第3点:在哪里对配置类进行代理的

    此解析只做导读,切勿当作唯一依据

  • 相关阅读:
    互联网流媒体直播点播平台报ioutil.WriteFile错误导致文件只读如何处理?
    互联网直播点播平台go语言搭建重定向和反向代理的区别及使用
    互联网视频直播点播平台EasyDSS如何集成流媒体平台调取登录及上传接口?
    互联网直播点播平台如何联合RTMP推流摄像头构建智慧消防方案?
    RTMP推流网关如何实现摄像头微信幼儿园直播?
    国标GB28181流媒体服务器gorm框架内表明重复添加前缀排查
    国标GB28181流媒体平台EasyGBS新版界面视频流更新时间显示错误问题解决
    视频流媒体播放器EasyPlayer-RTSP-Android 如何随意切换播放视频流?
    国标GB28181流媒体服务器分辨率会导致视频无法播放吗?
    国标GB28181流媒体平台集成后播放多个视频部分视频无法播放问题
  • 原文地址:https://www.cnblogs.com/daihang2366/p/15125874.html
Copyright © 2020-2023  润新知