1. 程式人生 > 其它 >談談Spring對於@Configuration的Cglib代理

談談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點:在哪裡對配置類進行代理的

此解析只做導讀,切勿當作唯一依據