• Spring阶段性学习:基础、配置解析、回调


    1、BeanDefinition

    是什么?

    我们都知道Spring会将我们的类new出来以后存放到它自己的容器当中去,然后Spring还需要对我们的类进行其他很多功能的处理,那么Spring的流程是先将需要new的类的
    信息都保存下来,然后统一的去new然后存放到容器当中.BeanDefinition就是存放类型下的.

    概览

    BeanDefinition是一个接口,其他有很多的实现类.我们先看看该接口的代码:

    public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    
    	/**
    	 * 单例的字符串值:singleton
    	 */
    	String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
    
    	/**
    	 * 原型的字符串值:prototype
    	 */
    	String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
    
    
    	int ROLE_APPLICATION = 0;
    
    	int ROLE_SUPPORT = 1;
    
    	int ROLE_INFRASTRUCTURE = 2;
    
    	void setParentName(@Nullable String parentName);			// 设置父级BeanDefinition的name
    
    	@Nullable
    	String getParentName();		
    
    	void setBeanClassName(@Nullable String beanClassName);		// 设置Class
    
    	@Nullable
    	String getBeanClassName();
    
    	void setScope(@Nullable String scope);						// 设置Scope,如果存在的话
    
    	@Nullable
    	String getScope();
    
    	void setLazyInit(boolean lazyInit);							// 设置是否懒加载
    
     	boolean isLazyInit();
    
    	void setDependsOn(@Nullable String... dependsOn);			// 设置DependsOn
    
    	@Nullable
    	String[] getDependsOn();
    
    	void setAutowireCandidate(boolean autowireCandidate);		// 设置调价注入
    
    	boolean isAutowireCandidate();
    
    	void setPrimary(boolean primary);							// 设置Primary
    
    	boolean isPrimary();
    
    	void setFactoryBeanName(@Nullable String factoryBeanName);
    
    	@Nullable
    	String getFactoryBeanName();
    
    	void setFactoryMethodName(@Nullable String factoryMethodName);
    
    	@Nullable
    	String getFactoryMethodName();
    
    	ConstructorArgumentValues getConstructorArgumentValues();
    
    	default boolean hasConstructorArgumentValues() {
    		return !getConstructorArgumentValues().isEmpty();
    	}
    
    	MutablePropertyValues getPropertyValues();
    
    	default boolean hasPropertyValues() {
    		return !getPropertyValues().isEmpty();
    	}
    	
        // 以下是属性
    	boolean isSingleton();
    
    	boolean isPrototype();
    
    	boolean isAbstract();
    
    	int getRole();
    
    	@Nullable
    	String getDescription();
    
    	@Nullable
    	String getResourceDescription();
    
    	@Nullable
    	BeanDefinition getOriginatingBeanDefinition();
    
    }
    

    以上是BenaDefiniiton(简称bd)这个接口的代码,这里面大多的方法都是见名知意的,这个接口定义了我们类在Spring当中最基本的信息,例如Primary、懒加载等等。

    那么既然是接口,就会衍生出很多实现类,不同的实现类用于不同的场景,例如我们Spring内置的BeanDefinition使用RootBeanDefinition,扫描包得到的类使用ScannedGenericBeanDefinition,基本的使用GenericBeanDefinition。在Spring5.x时,BeanDefinition的实现类与子接口有如下:

    1、实现类:AbstractBeanDefinition
    
    2、子接口:AnnotatedBeanDefinition
    
    3、实现类:AnnotatedGenericBeanDefinition
    
    4、实现类:ChildBeanDefinition
    
    5、实现类:ConfigurationClassBeanDefinition
    
    6、实现类:GenericBeanDefinition
    
    7、实现类:RootBeanDefinition
    
    8、实现类:ScannedGenericBeanDefinition
    

    这些都是应用于不同的场景。

    例如当我们往Spring的bd容器中注册类的时候,它内部就是将我们注册的类的信息封装到AnnotatedGenericBeanDefinition里面去,详见org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean的代码,其中就是将类的信息封装到bd中的代码是:

    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
    

    PS:在我们学习Spring当中,BeanDefinition是非常重要的,一定要理解知道这个BeanDefinition的作用。

    2、SpringApplicationContext创建初始化过程

    现有以下示例代码:

    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    

    话不多说我们先来个图:

    一图胜千言。

    在我们SpringBoot环境中仍然是使用的此AnnotationConfigApplicationContext。

    3、XXXAware回调接口

    在我们Spring当中有着各种各样的Aware接口,例如:

    1、EnvironmentAware:拿到ConfigurableEnvironment
    
    2、EmbeddedValueResolverAware:拿到StringValueResolver
    
    3、ResourceLoaderAware:拿到ConfigurableApplicationContext
    
    4、ApplicationEventPublisherAware:拿到ConfigurableApplicationContext
    
    5、MessageSourceAware:拿到ConfigurableApplicationContext
    
    6、ApplicationContextAware:拿到ConfigurableApplicationContext
    

    例如我们写一个类,假设我们这个类会被加入到Spring当中,那么我们实现的接口方法中就会获得各种的对象,其中我们最熟悉的就是ApplicationContext。

    那么在Spring当中实现回调此功能的类为:ApplicationContextAwareProcessor。

    在了解ApplicationContextAwareProcessor之前我们需要先知道PostProcessor,PostProcessor是Spring给我们提供的回调接口,当我们每个类实例化完后,就会调用我们实现此接口的类的方法,并将实例化后的类传入进来,我们来看一下PostProcessor接口的代码:

    public interface BeanPostProcessor {
    
    	@Nullable
    	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}
    
    	@Nullable
    	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}
    
    }
    

    这个接口里面的bean参数就是实例化后的对象。

    看完这个后我们再回来看ApplicationContextAwareProcessor这个类,它实现于PostProcessor接口,在其postProcessBeforeInitialization方法中做了具体的功能实现,这里我截取一下此类的核心代码:

    private void invokeAwareInterfaces(Object bean) {
    		if (bean instanceof Aware) {
    			if (bean instanceof EnvironmentAware) {
    				((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
    			}
    			if (bean instanceof EmbeddedValueResolverAware) {
    				((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
    			}
    			if (bean instanceof ResourceLoaderAware) {
    				((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
    			}
    			if (bean instanceof ApplicationEventPublisherAware) {
    				((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
    			}
    			if (bean instanceof MessageSourceAware) {
    				((MessageSourceAware) bean).setMessageSource(this.applicationContext);
    			}
    			if (bean instanceof ApplicationContextAware) {
    				((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
    			}
    		}
    	}
    

    这个代码应该都能看懂吧。

    4、BeanPostProcessor

    BeanPostProcessor是Spring提供对外的回调接口,我们在第标题3中已经大概说了这个接口,我们这里再次描述:

    BeanPostProcessor是Spring提供的可扩展接口,我们实现此方法以后可以在拿到Spring容器当中实例化的每一个对象,使用此扩展接口的方式很简单,写一个类实现此接口,并使其能加入到Spring当中,加入@Component注解。@Import都可以。
    
    这个接口可以实现很多功能,我们更换其实例化的对象,或者更改其对象内容,或者做代理。例如:日志、事物等。我们这里给一个例子,模拟一个事务注解的功能。
    

    首先来一个代表开启事物的注解:

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyTransactional {
    }
    

    再来一个类使用此注解:

    @Component
    public class Entity1 {
    
    	@MyTransactional
    	public void updateBatch(){
    		System.out.println("coding 1.....");
    		if (1==1) {
    			throw new RuntimeException("xxxxx");
    		}
    		System.out.println("coding 2.....");
    	}
    
    	public void updateBatch2(){
    		System.out.println("updateBatch2........");
    	}
    }
    
    

    再来一个BeanPostProcessor:

    @Component
    public class MyTransactionalBeanPostProcessor implements BeanPostProcessor {
    	@Override
    	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		Method[] methods = bean.getClass().getMethods();
    		boolean isNeedProxy = false;
    		if (methods != null && methods.length > 0){
    			for (Method method : methods) {
    				MyTransactional annotation = method.getAnnotation(MyTransactional.class);
    				if (annotation != null){
    					isNeedProxy = true;
    					break;
    				}
    			}
    		}
    		if (!isNeedProxy){
    			return bean;
    		}
    		Enhancer enhancer = new Enhancer();
    		enhancer.setSuperclass(bean.getClass());
    		enhancer.setCallback(new MethodInterceptor() {
    			@Override
    			public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    				MyTransactional annotation = method.getAnnotation(MyTransactional.class);
    				if (annotation == null){
    					return methodProxy.invokeSuper(o,objects);
    				}
    				try {
    					/* 打开事务 */
    					System.out.println("事务开始了!!!!!");
    					return methodProxy.invokeSuper(o,objects);
    				}catch (Throwable e){
    					/* 回滚事务 */
    					System.out.println("回滚事务!!!!!");
    					throw e.getCause();
    				} finally {
    					/* 提交事务 */
    					System.out.println("提交事务!!!!!");
    				}
    			}
    		});
    		return enhancer.create();
    	}
    }
    

    然后我们从ApplicationContext中去获取此Entity1,执行其updateBatch,结果如下:

    事务开始了!!!!!
    coding 1.....
    回滚事务!!!!!
    提交事务!!!!!
    Exception in thread "main" java.lang.RuntimeException: xxxxx
    	at com.dh.testEntity.Entity1.updateBatch(Entity1.java:21)
    	at com.dh.testEntity.Entity1$$EnhancerByCGLIB$$75f7b0e9.CGLIB$updateBatch$0(<generated>)
    	at com.dh.testEntity.Entity1$$EnhancerByCGLIB$$75f7b0e9$$FastClassByCGLIB$$52ecdc45.invoke(<generated>)
    	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
    	at com.dh.process.MyTransactionalBeanPostProcessor$1.intercept(MyTransactionalBeanPostProcessor.java:64)
    	at com.dh.testEntity.Entity1$$EnhancerByCGLIB$$75f7b0e9.updateBatch(<generated>)
    	at com.dh.main.Main1.test1(Main1.java:42)
    	at com.dh.main.Main1.main(Main1.java:46)
    

    相信到这来了以后大概就知道了整体的流程,我们可以使用cglib在BeanPostProcessor当中拿到带有我们自定义注解的Object,然后进行代理拦截方法操作。

    注意:

    1、我们的BeanPostProcessor是在bean实例化之后,放入Spring的bean容器之前被调用执行
    
    2、该扩展接口只能对现有bean进行增强,但并不能增加bean
    

    5、BeanFactoryPostProcessor

    在我们刚才的BeanPostProcessor中,只能对现有bean进行更改,但如果有这种需求,我们需要动态的往Spring当中去注入Bean或者修改未实例化Bean的信息,那么就可以使用这个接口BeanFactoryPostProcessor。先来看看这个接口的样子:

    public interface BeanFactoryPostProcessor {
    	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
    }
    

    这里面给了我们BeanFactory,给了beanFactory那么就好办了,可以直接加入我们生成的,或者扫描到的类进去。

    在这里我们可以拿到所有未实例化的BeanDefinition,并且可以修改其属性内容。使用方法如下示例:

    @Component
    public class TestMyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    	@Override
    	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    		BeanDefinition pojo1 = beanFactory.getBeanDefinition("pojo1");
    		pojo1.setPrimary(true);
            beanFactory.registerSingleton("xxssxx",XXX.class);
    	}
    }
    

    注意:这个回调接口有两种方式可以给Spring,第一个就是加入@Component注解并让Spring扫描到,第二个就是ApplicationContext.addBeanFactoryPostProcessor(xxxx)。但是他们执行的位置都是一样的,并不会存在先后顺序。

    调用的位置:

    在PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors方法当中,我们手动给的BeanFactoryPostProcessor会被存入到regularPostProcessors集合当中,然后会等待解析完配置、扫描包以后,将扫描出来的BeanFactoryPostProcessor存入到registryProcessors当中,然后这这两行代码统一去调用。

    invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
    invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
    

    6、BeanDefinitionRegistryPostProcessor

    首先我们要知道:

    1、我们的BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子接口
    
    2、此接口里面可以拿到Bean的注册器,可以直接注册BeanDefinition
    

    先来看看接口:

    @Component
    public class TestMyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
       @Override
       public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
          BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(XXXX.class);
    	  registry.registerBeanDefinition("xxxxxx",beanDefinitionBuilder.getBeanDefinition());
       }
    }
    

    注意在这里我们直接拿到了注册器,可以自己直接注册BD进去。

    我们加入BeanDefinitionRegistryPostProcessor的方式有两种,第一种是@Component,第二种是ApplicationContext.addBeanFactoryPostProcessor(xxxx)。

    但他们执行的位置不一样。

    如果是@Component的话,那么就是在Spring解析操作完以后去调用此接口,此时我们能拿到所有的BeanDefinition。
    
    如果手动调用方法加入到Spring的,那么则会在Spring进行解析操作以前去调用此接口,此时我们只能拿到Spring内置的BeanDefinition
    

    源码查看:

    @Component的操作时调用的地方:
    

    它是在Spring解析配置完成以后再从容器当中拿到类型为此接口的类,并进行相应的调用执行。

    如果是手动加入到容器里面去的话:
    

    这里已进入此方法就拿到参数的集合,参数的集合就是我们调用方法存入的集合,这里还没有进行配置类解析,那么自然也就没有我们其他的BeanDefinition,所以用这种方法加入回调接口的话,你除了Spring内置的以外,用户自定义的都是拿不到的。

    这个回调接口在Spring当中应用非常重要,例如我们解析配置类的代码就是实现了此接口,然后在此进行解析操作的,此类叫做:ConfigurationClassPostProcessor。

    7、解析Configuration配置类

    在Spring当中,解析配置的类叫做ConfigurationClassPostProcessor,该类实现于BeanDefinitionRegistryPostProcessor接口,对于配置的解析全部都是存在于其postProcessBeanDefinitionRegistry方法当中的。

    如何调用解析类?

    我们首先先知道其如何去调用这个ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法:

    首先进入到ApplicationContext的refresh方法的invokeBeanFactoryPostProcessors(beanFactory);中:

    进入红线部分的方法中:

    注意看标红的代码处,此时Spring还未解析配置,那么当前beanFactory当中的BeanDefinition肯定是没有的,就算是有,那也只是Spring内置的。

    这里的beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);方法的意思就是拿到bean工厂中类型为BeanDefinitionRegistryPostProcessor的Bean的beanName。

    断点后发现,此处postProcessorNames数组的长度为1,里面的这个值就是我们的ConfigurationClassPostProcessor。

    注意,我们的ConfigurationClassPostProcessor在Spring当中的beanName就是这个值,验证如下:

    ,然后继续看:

    注意这里的registryProcessors.addAll(currentRegistryProcessors),registryProcessors也可能存在我们手动加入Spring的回调,所以需要合并

    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);这行代码就是真正的去调用,进入这行代码后:

    代码里面如何实现功能的?

    概览

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        // 获取当前容器中所有BeanDefinition的名称,然后进行遍历,因为要看这里面有否有配置类,如果有配置类,那么就会解析这些配置类,如果没有,那么就直接跳过了。
        String[] candidateNames = registry.getBeanDefinitionNames();
        // 遍历这些类进行解析
        for (String beanName : candidateNames) {
            // 拿到BeanDefinition
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            // 明面上的意思:判断该类是否已经加载过了,如果加载过了,那么则无需再加载
            // 实际上:判断这个了是否存在lite或者full的配置类标识,如果存在这个标识中任意一个,说明此类已经被解析过,并且是配置类,那么就不用再去解析了
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {	// 判断该BeanDefinition是否存在full或lite的标识
                // 打个log,说这个beandefinition已经加载过了。
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            }
            /* 
            	如果没加载过,那么判断此类是否是配置类,如果是则加入到configCandidates中后面会对这些配置类进行解析,
            	此方法里面代码逻辑:
            		1、判断这个类的类型,因为注解类和其他类获取类信息的方式是不一样的
            		2、判断配置类的类型
            			1):该类如果包含@Configuration注解,那么设置标志位为full
            			2):否则判断是否为接口,如果是,则返回false,否则判断其是否包含@Component、@ComponentScan、@Import、@ImportResource、@Bean,如果包含此中任意一个,则认为其是一个半配置类,设置标志位为lite,如果都不包含,则返回false
            			3):如果既不是全配置类,也不是半个配置类,那么则返回false
            			4):如果类上存在@Order注解,那么则设置其order标志位的值
            			5):返回true
             */
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {	/*  */
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
    
        // 前面的遍历中,如果一个配置类都没有,那就没必要继续下去了,直接return了
        if (configCandidates.isEmpty()) {
            return;
        }
    
        // 记得在ConfigurationClassUtils.checkConfigurationClassCandidate这个if里面注释说了如果存在@Order就会设置其标志位的值吗?
       	// 这里就是把@Order设置标志位的值拿出来,遍历一次
        configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });
    
        // 这里不要太过于关心,就是拿到BeanName生成器,如果我们给了就拿我们的,没有则回去拿Spring自己内置的生成器
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                if (generator != null) {
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }
        }
    	// 环境的变量而已
        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }
    
        // ConfigurationClassParser这个类我们见名知意,就是做【配置类解析】的
        ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    	// 这个集合里面就是我们当前现有的所有配置类
        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        // 记录已经解析了的配置类
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        do {
            /* 解析这些类 */
            parser.parse(candidates);
            // 校验
            parser.validate();
    		// 解析拿到的类
            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);
    
            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
            }
            /* 注意看这个方法,是将我们解析的来在此处进行解析 */
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);
    
            candidates.clear();
           ...........
        }
        while (!candidates.isEmpty());
    
       ...........
    }
    

    这一个方法里面概涵了解析配置类的操作。代码中干的事情我都以注释的形式在上面写上,一些无用的代码此处未贴上来。

    parse解析

    进入这个方法后,实际上到了此处代码:

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            return;
        }
        /*处理imported的情况,就是当前类被其他类Import的情况*/
        ConfigurationClass existingClass = this.configurationClasses.get(configClass);
        if (existingClass != null) {
            if (configClass.isImported()) {
                if (existingClass.isImported()) {
                    existingClass.mergeImportedBy(configClass);
                }
                // Otherwise ignore new imported config class; existing non-imported class overrides it.
                return;
            }
            else {
                // Explicit bean definition found, probably replacing an import.
                // Let's remove the old one and go with the new one.
                this.configurationClasses.remove(configClass);
                this.knownSuperclasses.values().removeIf(configClass::equals);
            }
        }
        /* 仅仅是转换为SourceClass */
        // Recursively process the configuration class and its superclass hierarchy.
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);
    
        this.configurationClasses.put(configClass, configClass);
    }
    

    此方法的实际逻辑:

    * 此方法解析配置类时:
    *  doProcessConfigurationClass中解析当前类,
    *  1、首先解析PropertySources,
    *  2、然后解析ComponentScan,解析完后放置到存放BD的集合中,然后遍历ComponentScan中的类如果是配置类,那么去调用其parse方法进行解析这个配置类.
    *     ASM将class文件转换为类,中间也有可能有一些我们配置的过滤的包路径
    *     还包括了一些@Lazy的设置啊等等
    *  3、然后解析Import中的类
    *     1、判断当前类中Import的类如果为空,则停止Import的解析
    *     2、遍历Import中引入的类,
    *        2.1、如果实现了ImportSelector,那么就拿到其返回值,并将这些类解析为SourceClass,然后再调用当前的processImports方法解析这些SourceClass,因为有可能这些类里面也包含了@Import
    *        2.2、如果实现了ImportBeanDefinitionRegistrar,那么则拿到其返回的实例对象,加入到importBeanDefinitionRegistrars集合在中去,然后在org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions(java.util.Set)中实例化进行调用方法
    *        2.3、如果以上的不成立,那么说明是一个普通类,但是注意如果是普通类的话,那么说明它可能也是一个配置类,那么就需要重新给这个类解析一圈,就重新调用org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass(org.springframework.context.annotation.ConfigurationClass)
    *           方法进行解析,注意此时的参数,就是我们的普通类
    *  #、最后将当前类存放至configurationClasses集合中去,这里第一次进来的时候是AppConfig,然后在处理Import的时候,这个方法还会被调用一遍,被调用时此时的ConfigurationClass为Pojo2
    

    此doProcessConfigurationClass方法中具体做了哪些事情?先贴代码:

    @Nullable
    	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
    			throws IOException {
    
    		/* 处理内部类的情况 */
    		processMemberClasses(configClass, sourceClass);
    
    		/* 处理@PropertySource的情况,并解析 */
    		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
    				sourceClass.getMetadata(), PropertySources.class,
    				org.springframework.context.annotation.PropertySource.class)) {
    			if (this.environment instanceof ConfigurableEnvironment) {
    				processPropertySource(propertySource);
    			}
    			else {
    				logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
    						"]. Reason: Environment must implement ConfigurableEnvironment");
    			}
    		}
    
    		/* 处理@ComponentScan和s的情况 */
    		Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
    				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    		if (!componentScans.isEmpty() &&
    				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    			for (AnnotationAttributes componentScan : componentScans) {
    				Set<BeanDefinitionHolder> scannedBeanDefinitions =
    						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
    				// Check the set of scanned definitions for any further config classes and parse recursively if needed
    				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
    					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
    					if (bdCand == null) {
    						bdCand = holder.getBeanDefinition();
    					}
    					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
    						parse(bdCand.getBeanClassName(), holder.getBeanName());
    					}
    				}
    			}
    		}
    
    		// 处理@Import的情况
    		processImports(configClass, sourceClass, getImports(sourceClass), true);
    
    		/* 处理@ImportResource和s的情况 */
    		AnnotationAttributes importResource =
    				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    		if (importResource != null) {
    			String[] resources = importResource.getStringArray("locations");
    			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
    			for (String resource : resources) {
    				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
    				configClass.addImportedResource(resolvedResource, readerClass);
    			}
    		}
    
    		// 处理@Bean方法的情况
    		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    		for (MethodMetadata methodMetadata : beanMethods) {
    			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    		}
    
    		..............
    
    		// No superclass -> processing is complete
    		return null;
    	}
    

    每部分代码具体干的事情我在代码里注释的方式进行表达,不重要的....忽略了。

    我们这里挑几个重要的来说:

    @ComponentScan

    // Process any @ComponentScan annotations
    /* 处理ComponentScan和s的情况 */
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
        !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        for (AnnotationAttributes componentScan : componentScans) {
            // The config class is annotated with @ComponentScan -> perform the scan immediately
            /* 解析我们配置类上的ComponentScan和s注解信息,然后拿到扫描到的类信息,
    				 * 注意,此时这些BeanDefinition已经被放入到存放BeanDefinition的容器当中,此处再次for循环的原因是为了判断这些类是否包含配置类,如果是的话,那么还得递归的去调用再次解析
    				 * */
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            // Check the set of scanned definitions for any further config classes and parse recursively if needed
            for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    bdCand = holder.getBeanDefinition();
                }
                // 重点重点重点重点重点重点重点重点重点重点
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    parse(bdCand.getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }
    

    这段代码就是解析扫描包这个注解的代码,首先拿到这个类上所有的@ComponentScan和@ComponentScans。

    然后进行遍历解析,然后调用this.componentScanParser.parse方法解析我们填入的扫描包的字符串路径,返回结果为扫描到的所有BeanDefinitionHolder,这个BeanDefinitionHolder其实就是BeanName+BeanDefinition的合并版,为的是方便传参。这个parse方法我们先暂停一下,看下面一点。

    看下面遍历scannedBeanDefinitions集合的for循环,重点第二个if,先是校验是否为配置类(这个东西在上面我们说过并详细解释过),如果不是那么则不操作,如果是则再次对这个类进行解析,递归调用当前我们这个方法,当前方法执行完成后,则会将此类put到configurationClasses当中,不仅仅是当前递归的时候会put,我们首次调用解析的时候仍然会put。

    我们现在回头来看上面的parse方法:Set scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
        /*  创建一个扫描器,
        	注意在我们AnnotationConfigApplicationContext的无参构造器中创建了一个这个Scanner,但是这里扫描包仍然自己创建了一个Scanner
        	这也验证了我们第2部分中对于AnnotationConfigApplicationContext的无参构造器中创建了的这个Scanner是没有使用的验证。
        */
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
                                                                                    componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
        /* Bean的名称生成器 */
        Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
        boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
        scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
                                     BeanUtils.instantiateClass(generatorClass));
        /* Web当中的,暂不知 */
        ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
        if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
            scanner.setScopedProxyMode(scopedProxyMode);
        }
        else {
            Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
            scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
        }
    
        scanner.setResourcePattern(componentScan.getString("resourcePattern"));
        /* 过滤器,在此处会加入进去,后序解析的时候回进行相应的过滤操作 */
        for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
            for (TypeFilter typeFilter : typeFiltersFor(filter)) {
                scanner.addIncludeFilter(typeFilter);
            }
        }
        /* 剔除的过滤 */
        for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
            for (TypeFilter typeFilter : typeFiltersFor(filter)) {
                scanner.addExcludeFilter(typeFilter);
            }
        }
        /*
    		* 判断是否需要懒加载,如果需要懒加载,那么则设置scanner中默认懒加载为true
    		* 注意这里获取是否lazy是我们的配置类的注解,如果配置类上@Lazy加上了的话,那么这个配置类配置中扫描的其他类如果没设置@Lazy的话那么则会依据当前这个配置类的Lazy来设置其Lazy值,
    		* 具体详见:
    		* 	org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan中
    		* 			postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
    		* 			AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
    		* 			这两行代码
    		* */
        boolean lazyInit = componentScan.getBoolean("lazyInit");
        if (lazyInit) {
            scanner.getBeanDefinitionDefaults().setLazyInit(true);
        }
    
        Set<String> basePackages = new LinkedHashSet<>();
        /* 拿到所有扫描包的路径 */
        String[] basePackagesArray = componentScan.getStringArray("basePackages");
        for (String pkg : basePackagesArray) {
            String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
                                                                   ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            Collections.addAll(basePackages, tokenized);
        }
        /* 拿到扫描类 */
        for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(declaringClass));
        }
        /* 过滤排除操作,不用细究,只需要知道这里将不需要的路径给记下来了 */
        scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
            @Override
            protected boolean matchClassName(String className) {
                return declaringClass.equals(className);
            }
        });
        /* 这里才是正儿八经的去扫描包路径下面的类的方法 */
        return scanner.doScan(StringUtils.toStringArray(basePackages));
    }
    

    解释都写在注释上面了,现在继续进入doScan方法:

    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        /* 将多个包路径循环扫描 */
        for (String basePackage : basePackages) {
            /* 查找当前包下所有的类,并转换为BeanDefinition,这里扫描类文件并将其转换为class的操作使用的ASM一个开源的文件转class的项目,这里不细究 */
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            /* 将扫描到的类循环遍历 */
            for (BeanDefinition candidate : candidates) {
                /* 拿到Scope作用域 */
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                /* 生成BeanName */
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                /* 是否为AbstractBeanDefinition,这里扫描到的Bean类型都为ScannedGenericBeanDefinition,此类间接继承于AbstractBeanDefinition,所以此处一定是true */
                if (candidate instanceof AbstractBeanDefinition) {
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                /* ScannedGenericBeanDefinition同样实现了AnnotatedBeanDefinition,所以这里也肯定是true */
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                /* 检查一下这个Bean是否允许被放入Spring中来,如果是,则直接放进去 */
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                        AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }
    

    这里代码的解释都写在了注释里面,我们需要重点来说一下:

    if (candidate instanceof AbstractBeanDefinition) {
        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
    }
    

    if (candidate instanceof AnnotatedBeanDefinition) {
        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
    }
    

    注意我们当选的BeanDefinition的类型为:ScannedGenericBeanDefinition,这个可以在findCandidateComponents的scanCandidateComponents方法中找到:

    那么既然当前的BeanDefinition的类型为ScannedGenericBeanDefinition,那么这两个if都是会进入的。

    这两个if里面的实际调用的方法干的事情就是设置一些BeanDefinition的默认属性,例如是否为azy,如果没有设置则设置为其最顶级的配置类的lazy设置,此处在前面的代码处也有体现,例如AppConfig这个类配置了@CompentScan和@Lazy,那么根据这个配置扫描出来的类如果没设置@Lazy的话,那么则会按照AppConfig的配置进行设置。

    那么扫描包说完了,我们再来说一下@Import

    @Import

    在我们的doProcessConfigurationClass中的processImports(configClass, sourceClass, getImports(sourceClass), true);这一行代码中:

    processImports(configClass, sourceClass, getImports(sourceClass), true);
    

    当我们的配置类上拥有@Import注解,那么会进入这个类进行解析。

    首先在看源码之前我们要知道Import进来的类分为三种:

    ​ 1、普通的类

    ​ 2、实现了ImportSelector接口的类

    ​ 3、实现了ImportBeanDefinitionRegistrar接口的类

    如果是普通的类,那么就放入Spring了,如果是ImportSelector或者ImportBeanDefinitionRegistrar的话,那么则会去调用其回调方法。

    我们看看ImportSelector和ImportBeanDefinitionRegistrar的接口代码:

    public interface ImportSelector {
        // 这个返回的String[]的值示例:return new String[]{"com.dh.App1","com.dh.App2"}。那么Spring就会把你数组中的ckassName实例化到Spring当中去
        String[] selectImports(AnnotationMetadata importingClassMetadata);
    }
    
    public interface ImportBeanDefinitionRegistrar {
        void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
    }
    

    ImportSelector和ImportBeanDefinitionRegistrar,给的参数为AnnotationMetadata,从这个参数里面可以拿到该@Import注解的类上其他的注解信息以及类信息。

    除此之外,ImportBeanDefinitionRegistrar中还给了一个BeanDefinitionRegistry注册器,可以直接用这个对象把BeanDefinition注册到Spring当中,可以查看一下@MapperScan注解里面,这个是Mybatis的扫描包注解,我们进入此注解的代码中可以看到:

    它Import了一个MapperScannerRegistrar,进入这个类:

    其实现了这两个接口,那么你看mybatis源码就知道它如何集成的了,而且入口在哪,这个类会扫描注解上的包路径,然后进行处理后将创建mapper接口的实现类后装入BeanDefinition,并使用注册器放入spring中,那么我们在使用的时候就直接在Mapper变量上@Autowired,然后根据类型就可以自动注入进来。

    下面在代码中对Import解析进行详细的描述

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                                Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
        if (importCandidates.isEmpty()) {
            return;
        }
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            this.importStack.push(configClass);
            try {
                /* @Import注解中加入的类,对这些类进行遍历 */
                for (SourceClass candidate : importCandidates) {
                    /* 首先第一个判断:
                       判断当前Import的类是否是ImportSelector的实现类,如果是则去调用其processImports方法拿到其返回的className数组进行操作
                    */
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        // 拿到当前类
                        Class<?> candidateClass = candidate.loadClass();
                        /* 实例化我们的配置的类, */
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        ParserStrategyUtils.invokeAwareMethods(
                            selector, this.environment, this.resourceLoader, this.registry);
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectors.add(
                                new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }
                        else {
                            /* 调用我们实现ImportSelector的selectImports方法,拿到我们需要实例化的类名数组,然后进行实例化 */
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            /* 将ImportSelector返回的值进行处理,递归调用本身 */
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                            BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                        ParserStrategyUtils.invokeAwareMethods(
                            registrar, this.environment, this.resourceLoader, this.registry);
                        /* 这个和Selector不同,它并不是直接实例化后调用方法拿到返回值进行处理了,而是先存入importBeanDefinitionRegistrars变量中后面统一处理 */
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport(
                            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        /* 到这里的时候,说明当前的这个类是一个普通的类,那么就会将当前的类存入configurationClasses变量中,注意此时并没有存入Spring的bd容器里面去 */
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                    "Failed to process import candidates for configuration class [" +
                    configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
    }
    

    上述中循环里面解析分为三种情况:

    ​ 第一种:解析实现接口ImportSelector的

    ​ 第二种:解析实现接口ImportBeanDefinitionRegistrar

    ​ 第三种:以上不成立,就当作一个普通类来解析的

    第一种解析实现ImportSelector接口的:

    // 拿到当前类
    Class<?> candidateClass = candidate.loadClass();
    /* 实例化我们的配置的类, */
    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
    ParserStrategyUtils.invokeAwareMethods(
        selector, this.environment, this.resourceLoader, this.registry);
    if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
        this.deferredImportSelectors.add(
            new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
    }
    else {
        /* 调用我们实现ImportSelector的selectImports方法,拿到我们需要实例化的类名数组,然后进行实例化 */
        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
        /* 将ImportSelector返回的值进行处理,递归调用本身 */
        processImports(configClass, currentSourceClass, importSourceClasses, false);
    }
    

    首先实例化这个类,然后调用方法获取到ClassName数组,然后调用asSourceClasses方法将这些className转换为SourceClass集合,然后再将这些作为参数调用当前的这个方法,因为Import的这些类也可能是配置类。

    第二种:解析实现接口ImportBeanDefinitionRegistrar的:

    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
        // Candidate class is an ImportBeanDefinitionRegistrar ->
        // delegate to it to register additional bean definitions
        Class<?> candidateClass = candidate.loadClass();
        ImportBeanDefinitionRegistrar registrar =
            BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
        ParserStrategyUtils.invokeAwareMethods(
            registrar, this.environment, this.resourceLoader, this.registry);
        /* 这个和Selector不同,它并不是直接实例化后调用方法拿到返回值进行处理了,而是先存入importBeanDefinitionRegistrars变量中后面统一处理 */
        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
    }
    

    如果类型为ImportBeanDefinitionRegistrar,那么则会将其存入configClass里面,等待后面进行操作。

    第三种:解析普通类

    else {
        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
        // process it as an @Configuration class
        this.importStack.registerImport(
            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
        /* 到这里的时候,说明当前的这个类是一个普通的类,那么就会将当前的类存入configurationClasses变量中,注意此时并没有存入Spring的bd容器里面去 */
        processConfigurationClass(candidate.asConfigClass(configClass));
    }
    

    此处因为不知道其普通类中是否包含配置信息,所以使其重新回到processConfigurationClass方法中重新走一遍上述的所有流程,而这些流程走完以后,就会将当前这个类put到configurationClasses当中去

    额外解释:

    ​ 我们的ImportSelector和ImportBeanDefinitionRegistrar都可以往Spring中放入Bean,为什么有两个呢?

    ​ 1、首先ImportSelector返回的className最后生成的BeanDefinition的类型都为:StandardAnnotationMetadata,并且除了获取ClassName以外,创建到放入Spring的过程都由Spring处理,我们无法控制。

    ​ 2、而我们的ImportBeanDefinitionRegistrar它由于回调中存在Registry,所以我们所有操作包括创建和放入Spring,都由我们来操作,所以BeanDefinition的类型这些的都是自己去控制的。

    这两个回调接口在使用场景上各有不同,具体什么时候使用哪个的话,仁者见仁智者见智吧。

    这里我们不再去探究@Import下面的@ImportResource等其他的具体实现。

    parse方法以后的解析

    注意在我们的parse方法中,存在两个问题,processConfigurationClass中的类未放入Spring,ImportBeanDefinitionRegistrar的回调未当时进行回调调用。

    那么这就是是在parse后面的this.reader.loadBeanDefinitions(configClasses);代码中进行操作:

    这里面的代码:

    再次进入loadBeanDefinitionsForConfigurationClass方法:

    这里的代码很静很清楚了,我们都用红框标注除了拍,分别是处理@Import进来的普通类,需要放入Spring的、解析@bean方法的、还有对ImportBeanDefinitionRegistrar进行处理回调的,这里面的方法代码都比较少了,我们这里就不再单独的进去查看代码了。

    注意这里有一个点没有说,那就是我们的XML解析,因为现在大部分都没有使用XML配置了,所以此处就忽略掉。

    对配置类的Cglib代理

    具体到另外一篇博客中查看:https://www.cnblogs.com/daihang2366/p/15125874.html

    内容太多了,记住起来太累了。

    交流QQ:2366567504

    如果文中有误的地方,请提出,我会及时改正。

  • 相关阅读:
    2020-2021-1 20201329 《信息安全专业导论》第十一周学习总结
    python gui
    2020-2021-1 20201329 《信息安全专业导论》第十周学习总结
    2020-2021-1 20201329 《信息安全专业导论》第九周学习总结
    四则运算
    熟悉编程语言
    链表
    网站设计
    使用nmap扫描队友
    熟悉编程语言
  • 原文地址:https://www.cnblogs.com/daihang2366/p/15172622.html
Copyright © 2020-2023  润新知