Невидимый класс конфигурации @Configuration в Spring

Spring
Невидимый класс конфигурации @Configuration в Spring

Введение

Здесь я должен вздохнуть о совершенстве и превосходстве кода Spring.От ошеломленного взгляда на исходный код до настоящего времени, после того, как я немного разобрался в исходном коде Spring, я обнаружил, что разработчики Spring становятся все более и более вдумчивыми!

Какова цель изучения исходного кода раньше? Поскольку мне особенно нравится предложение,有道无术,术尚可求也!有术无道,止于术!, Недостаточно понимать Spring только для того, чтобы использовать его. Как среда управления проектами, используемая подавляющим большинством разработчиков Java в Китае, Spring представляет собой экосистему. Что такое экосистема? такие как настоящееSpringBoot,SpringCloud, кто они такие? Это неотъемлемая часть экосистемы Spring! Они используют различные точки расширения, предусмотренные в экосистеме Spring, для пошаговой инкапсуляции и достижения текущей версии Spring.快速启动,自动配置В ожидании ослепительной функции! Как пользователи Spring, мы должны понимать реализацию Spring и различные точки расширения, чтобы мы могли по-настоящему углубиться в экосистему Spring! Идите глубже, а затем изучите такие компоненты экологии, как:SpringBootРамки стрима - это само собой разумеющееся!

Во-вторых, вступительный вопрос

Я считаю, что большинство разработчиков естественным образом используют Spring! Тогда следующий фрагмент кода должен быть знаком каждому!

/**
 * 全局配置类
 *
 * @author huangfu
 */
@Configuration
public class ExpandRunConfig {
	@Bean
	public TestService testService() {
		return new TestServiceImpl();
	}

	@Bean
	public UserService userService() {
		testService();
		return new UserServiceImpl();
    }
}

Хорошо видно, что здесь есть два класса, управляемых Spring.TestService,UserService,Но когдаuserService()цитируется сноваtestService()!Так вот вопрос, как вы считаетеTestServiceСколько раз он будет создан?

Я считаю, что есть много студентов, которые открывают рот и говорят一次, да, да, но почему? Я был глубоко неуверен в себе по поводу проблемы здесь! Какое-то время я даже сомневался в своей основе Java, очевидно, вызывая здесь другой метод, но почему я не создал его дважды?

Я спрашивал у многих коллег и друзей, и они только и знают, что так написать не проблема! Но я не знаю точной причины! Зачем? Давайте рассмотрим этот вопрос ниже!

3. Является ли класс конфигурации, который вы видите, действительно классом конфигурации?

Давайте возьмем этот класс конфигурации из контейнера компонентов и посмотрим, что изменилось!

public static void main(String[] args) {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ExpandRunConfig.class);
    ExpandRunConfig bean = ac.getBean(ExpandRunConfig.class);
    System.out.println(bean);

}

Давайте отладим и посмотрим, что у нас получилось!

被代理的Spring配置类

Конечно же, он больше не он, он был (испорченным) доверенным лицом, а используемое доверенное лицоcglib, то вы можете догадаться о проблеме здесь.При вызове другого метода Bean в методе Bean он должен делать это через прокси, тем самым выполняя функцию создания экземпляра только один раз для нескольких вызовов!

Вот, решил, получилось вот так! Итак, теперь есть два вопроса:

  1. Когда прокси добавляется в класс конфигурации?
  2. Как логика прокси выполняет функцию многократного возврата одного и того же экземпляра?

Давайте посмотрим на исходный код Spring с двумя вопросами и посмотрим, как это работает!

В-четвертых, значок агента

cglib代理配置类的流程图

Я выложил эту картинку.Если вы не поняли, она должна быть очень запутанной. Это не имеет значения. Я буду использовать исходный код, чтобы объяснить это позже, и после прочтения исходного кода, мы, вероятно, напишем рукописный один, чтобы помочь вам понять!

5. Подробный исходный код

Угадайте, читатели, читавшие мои предыдущие статьи, должны знать! Когда Spring создает экземпляр компонента, необходимая ему информация находится в beanDefinitionMapхранится в нем, то при разборе bd бина во время инициализации объект класса в конфигурации class bd должен быть заменен, чтобы он стал прокси-объектом при последующей инстанциации конфига, поэтому наша запись должна быть здесь:

invokerBeanFactory入口方法

Итак, где здесь улучшенный код?

/**
	 * 准备配置类以在运行时为Bean请求提供服务
	 * 通过用CGLIB增强的子类替换它们。
	 */
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    ..................忽略对应的逻辑................
    //字节码增强配置类  貌似用的cglib
    enhanceConfigurationClasses(beanFactory);
    ..................忽略对应的逻辑................
}

Усовершенствованная логика вызова классов конфигурацииenhanceConfigurationClasses

/**
 * 对BeanFactory进行后处理以搜索配置类BeanDefinitions; 然后,任何候选人都将通过{@link ConfigurationClassEnhancer}.
 * 候选状态由BeanDefinition属性元数据确定。
 * @see ConfigurationClassEnhancer
 */
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    // 最终需要做增强的Bean定义们
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
        //什么是Full类,简单来说就是加了 @Configuration 的配置类
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
           .....忽略日志打印......
            //// 如果是Full模式,才会放进来
            configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
        }
    }
    if (configBeanDefs.isEmpty()) {
        // 没有什么可增强的->立即返回
        return;
    }
    //配置类增强器
    // ConfigurationClassEnhancer就是对配置类做增强操作的核心类
    //初始化会初始化两个chlib拦截类  BeanFactoryAwareMethodInterceptor 和  BeanMethodInterceptor
    //这个是重点  这个类里面的方法会产生最终的代理类
    //这个方法里面有个
    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
    //对每个Full模式的配置类,一个个做enhance()增强处理
    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
        AbstractBeanDefinition beanDef = entry.getValue();
        // 如果@Configuration类被代理,请始终代理目标类
        beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
        try {
            // 设置用户指定的bean类的增强子类
            //CGLIB是给父类生成子类对象的方式实现代理,所以这里指定“父类”类型
            Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
            if (configClass != null) {
                //做增强处理,返回enhancedClass就是一个增强过的子类
                //这个是重点,这个会构建一个cglib的增强器,最终返回被代理完成的类对象!
                Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
                //不相等,证明代理成功,那就把实际类型设置进去
                if (configClass != enhancedClass) {
                    ..... 忽略日志打印 ....
                    //这样后面实例化配置类的实例时,实际实例化的就是增强子类喽
                    //这里就是替换 config类的beanClass对象的!
                    beanDef.setBeanClass(enhancedClass);
                }
            }
        }
        catch (Throwable ex) {
            。。。。。忽略异常处理。。。。。。。
        }
    }
}

Этот класс очень важен и всего делает несколько вещей:

  1. Класс конфигурации фильтра, только добавить@ConfigurationКласс конфигурации будет повышен!
  2. использоватьenhancer.enhanceСоздайте энхансер, который возвращает расширенный объект прокси-класса!
  3. Замените оригинальный beanClass класса конфигурации прокси-классом!

Итак, что нас больше всего беспокоит, так это то, как этого добиться, мы должны увидетьenhancer.enhanceЛогика внутри~

public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
		// 如果已经实现了该接口,证明已经被代理过了,直接返回
		if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
			。。。。忽略日志打印。。。。
			return configClass;
		}
		//没被代理过。就先调用newEnhancer()方法创建一个增强器Enhancer
		//然后在使用这个增强器,生成代理类字节码Class对象
		//创建一个新的CGLIB Enhancer实例,并且做好相应配置
        //createClass是设置一组回调(也就是cglib的方法拦截器)
		Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
		if (logger.isTraceEnabled()) {
			。。。。忽略日志打印。。。。
		}
		return enhancedClass;
	}

Это излишество, реальная вещь для создания прокси-энхансераnewEnhancerметод, мы, кажется, близки к ответу, который мы ищем!

/**
	 * 创建一个新的CGLIB {@link Enhancer}实例。
	 */
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
    Enhancer enhancer = new Enhancer();
    // 目标类型:会以这个作为父类型来生成字节码子类
    enhancer.setSuperclass(configSuperClass);
    //代理类实现EnhancedConfiguration接口,这个接口继承了BeanFactoryAware接口
    //这一步很有必要,使得配置类强制实现 EnhancedConfiguration即BeanFactoryAware 这样就可以轻松的获取到beanFactory
    enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
    // 设置生成的代理类不实现org.springframework.cglib.proxy.Factory接口
    enhancer.setUseFactory(false);
    //设置代理类名称的生成策略:Spring定义的一个生成策略 你名称中会有“BySpringCGLIB”字样
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
    //设置拦截器/过滤器  过滤器里面有一组回调类,也就是真正的方法拦截实例
    enhancer.setCallbackFilter(CALLBACK_FILTER);
    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
    return enhancer;
}

Если вы знакомы с cglib, вы должны быть хорошо знакомы с этими строками кода, в основном выполняющими эти действия!

  1. Установите класс, который необходимо проксировать
  2. Установите интерфейс, который должен реализовать сгенерированный прокси-класс, здесь реализованы настройкиEnhancedConfiguration, Обратите внимание, что это очень сложная операция.Это важная логика, которая может гарантировать, что окончательный класс может быть возвращен из beanFactory.Почему? потому чтоEnhancedConfigurationдаBeanFactoryAwareПодкласс , Spring перезвонит ему и настроит для него beanFactory.Если вы этого не понимаете, вы можете сначала записать это, а затем вернуться и внимательно попробовать после прочтения!
  3. Установите фильтр.На самом деле в фильтре есть набор методов обратного вызова.Этот метод обратного вызова является реальной логикой, выполняемой после перехвата конечного метода.Мы проанализируем этот набор экземпляров обратного вызова в фильтре позже!
  4. Вернемся к окончательному бустеру!

Как я только что сказал, нам нужно сосредоточиться на этом наборе методов перехвата.Мы входим в перехватчик и находим соответствующий экземпляр обратного вызова!

CALLBACK_FILTER: Константа соответствует фильтру, давайте посмотрим, как это реализовано:

private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);

тогда в это времяCALLBACKSЭто метод обратного вызова, который я ищу, щелкните по нему, и вы увидите:

// 要使用的回调。请注意,这些回调必须是无状态的。
private static final Callback[] CALLBACKS = new Callback[] {
    //这个是真正能够Bean方法多次调用返回的是一个bean实例的实际拦截方法,这个拦截器就是完全能够说明,为什么多次调用只返回
    //一个实例的问题
    new BeanMethodInterceptor(),
    //拦截 BeanFactoryAware 为里面的 setBeanFactory 赋值
    //刚刚也说了,增强类会最终实现 BeanFactoryAware 接口,这里就是拦截他的回调方法 setBeanFactory方法,获取bean工厂!
    new BeanFactoryAwareMethodInterceptor(),
    //这个说实话  真魔幻  我自己实现cglib的时候一直在报错  报一个自己抛出的异常,异常原因是没有处理object里面的eques等
    //方法,这个就是为了处理那些没有被拦截的方法的实例  这个些方法直接放行
    //这个实例里面没有实现任何的东西,空的,代表着不处理!
    NoOp.INSTANCE
};

Что конкретно делает каждый перехватчик в конкретном комментарии очень понятно, начнем со второго! Почему бы не начать с первого? Первый более хлопотный, давайте пойдем от мелкого к более глубокому, шаг за шагом!

BeanFactoryAwareMethodInterceptor

/**
	 * 拦截对任何{@link BeanFactoryAware#setBeanFactory(BeanFactory)}的调用 {@code @Configuration}类实例,用于记录{@link BeanFactory}。
	 * @see EnhancedConfiguration
	 */
private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {

    @Override
    @Nullable
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        //找到本类(代理类)里名为`$$beanFactory`的字段
        Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
        //若没找到直接报错。若找到了此字段,就给此字段赋值
        Assert.state(field != null, "Unable to find generated BeanFactory field");
        field.set(obj, args[0]);

        // 实际的(非CGLIB)超类是否实现BeanFactoryAware?
        // 如果是这样,请调用其setBeanFactory()方法。如果没有,请退出。
        //如果用户类(也就是你自己定义的类)自己实现了该接口,那么别担心,也会给你赋值上
        if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
            return proxy.invokeSuper(obj, args);
        }
        return null;
    }

    /**
	 * 执行到setBeanFactory(xxx)方法时匹配成功
	 * @param candidateMethod 当前执行的方法
	 * @return
	 */
    @Override
    public boolean isMatch(Method candidateMethod) {
        //判断方法是不是 `setBeanFactory` 方法 
        return isSetBeanFactory(candidateMethod);
    }
    
    .........忽略不必要逻辑.........
}

Не знаю, обратили ли вы внимание, в финальном сгенерированном классе есть класс конфигурации прокси$$beanFactoryАтрибут, этот атрибут присваивается здесь! Потом выложи картинку, посмотри на последний атрибут!

被代理的Spring配置类

Основная роль этого перехватчика:

  1. перехватыватьsetBeanFactoryметод, для$$beanFactoryНазначение!

Ну вот этот перехватчик введен, и функцию все запомнили.Тогда давайте разберем следующий перехватчик.Это ключевой момент!

BeanMethodInterceptor

/**
 * 增强{@link Bean @Bean}方法以检查提供的BeanFactory中的 这个bean对象的存在。
 * @throws Throwable 作为所有在调用时可能引发的异常的统筹 代理方法的超级实现,即实际的{@code @Bean}方法
 * 当该方法经过匹配成功后 会进入到这个拦截方法  这个是解决bean方法只被创建一次的重要逻辑
 */
@Override
@Nullable
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
                        MethodProxy cglibMethodProxy) throws Throwable {
    //通过反射,获取到Bean工厂。也就是 $$beanFactory 这个属性的值
    //也就是上一个拦截器被注入的值
    ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
    //拿到Bean的名称
    String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

    // 确定此bean是否为作用域代理
    //方法头上是否标注有@Scoped注解
    if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
        String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
        if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
            beanName = scopedBeanName;
        }
    }
    。。。。。。忽略与本题无关的代码。。。。。。。。。。
        
    // 检查给定的方法是否与当前调用的容器相对应工厂方法。
    // 比较方法名称和参数列表来确定是否是同一个方法
    // 怎么理解这句话,参照下面详解吧
    //在整个方法里面,我认为这个判断是核心,为什么说他是核心,因为只有这个判断返回的是false的时候他才会真正的走增强的逻辑
    //什么时候会是false呢?
    //首先  spring会获取到当前使用的方法   其次会获取当前调用的方法,当两个方法不一致的时候会返回false
    //什么情况下胡不一致呢?
    //当在bean方法里面调用了另一个方法,此时当前方法和调用方法不一致,导致返回课false然后去执行的增强逻辑
    if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
        // 这是个小细节:若你@Bean返回的是BeanFactoryPostProcessor类型
        // 请你使用static静态方法,否则会打印这句日志的~~~~
        // 因为如果是非静态方法,部分后置处理失效处理不到你,可能对你程序有影像
        // 当然也可能没影响,所以官方也只是建议而已~~~
        if (logger.isInfoEnabled() &&
            BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
            ...... 忽略日志打印......
        }
        // 这表示:当前方法,就是这个被拦截的方法,那就没啥好说的
        // 相当于在代理代理类里执行了super(xxx);
        // 但是,但是,但是,此时的this依旧是代理类
        //这个事实上上调用的是本身的方法  最终会再次被调用到下面的 resolveBeanReference 方法
        //这里的设计很奇妙  为什么这么说呢?
        //了解这个方法首先要对cglib有一个基础的认识 为什么这么说呗?
        //首先要明白 cglib是基于子类集成的方式去增强的目标方法的
        //所以在不进行增强的时候就可以以很轻松的调用父类的原始方法去执行实现
        //当前调用的方法和调用的方法是一个方法的时候  就直接调用cglib父类  也就是原始类的创建方法直接创建
        //当不一样的时候  会进入到下面的方法  直接由beanFactory返回  精妙!!
        return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
    }
    //方法里调用的实例化方法会交给这里来执行
    //这一步的执行是真正的执行方式,当发现该方法需要代理的时候不调用父类的原始方法
    //而是调用我需要代理的逻辑去返回一个对象,从而完成对对象的代理
    return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}

На первый взгляд, много ли, ничего страшного, разберем по крупицам:

  1. Во-первых, позвольте мне увидеть это суждениеif (isCurrentlyInvokedFactoryMethod(beanMethod))Это решение очень важно! он изThreadLocalВыньте фабричный метод, названный на этот раз.Фабричный метод упоминался много раз.Что такое фабричный метод? Это метод, соответствующий написанному вами @Bean, мы называем его фабричным методом, мы используем приведенный выше开篇一问Вот пример этого кода!
    • при созданииUserServiceImplКогда текущий объект метода сохраняется первым, чтоUserServiceImplобъект метода, то есть помещенный вThreadLocalЗайти внутрь!
    • Затем найдите, что это прокси-объект, введите прокси-логику, в прокси-логике перейдите к этой логике суждения и найдите метод этого перехвата иThreadLocalМетоды внутри такие же, затем отпустите и начните вызывать настоящиеuserService()метод, когда этот метод выполняется, метод вызывается внутриtestService();метод!
    • ОбнаружитьtestService()Это еще один прокси-объект, поэтому я снова просматриваю логику прокси, а затем перехожу к этому решению и делаю вывод, что текущий метод перехватаtestServiceМетод в ThreadLocaluserService, На этот раз суд не прошел, поэтому он ушел в другую ветку!
    • Другая ветвь больше не будет выполнять этот метод, а перейдет непосредственно к beanFactory, чтобы получить компонент и вернуться напрямую!
  2. return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);Это чтобы выпустить напрямую, когда перехваченный метод является фабричным методом и выполнить логику родительского класса, почему это родительский класс! Cglib реализован на основе наследования, а его родительским классом является исходный метод без прокси, что эквивалентно вызовуsuper.userService()Иди вызов оригинальной логики!
  3. resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);Это также логика кода, которую мы рассмотрим некоторое время.Это когда суждение не установлено, то есть когда обнаруживается, что в фабричном методе вызывается другой фабричный метод, он войдет сюда! Итак, давайте посмотрим на логику здесь!

Логика метода resolveBeanReference

private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
                                    ConfigurableBeanFactory beanFactory, String beanName) {
		。。。。。。。。。忽略不必要代码。。。。。。。。。
        //通过getBean从容器中拿到这个实例
        //这个beanFactory是哪里来的,就是第一个拦截器里面注入的`$$beanFactory`
        Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
                               beanFactory.getBean(beanName));

        。。。。。。。。。忽略不必要代码。。。。。。。。。
        return beanInstance;
    }
 
}

Основная логика здесь состоит в том, чтобы получить объект компонента, соответствующий этому методу, из beanFactory и вернуть его напрямую! Вместо вызова соответствующего метода для создания! Вот почему при многократном вызове возвращаемый экземпляр всегда только один!

6. Резюме

Весь процесс довольно сложный, читатели могут следить за статьей для отладки исходного кода, я верю, что после глубокого размышления вы обязательно что-то приобретете!

Весь процесс делится на две части:

  1. Класс расширенной конфигурации
    • Добавлено обнаружение@ConfigurationАннотированный класс конфигурации!
    • Создавайте прокси-объекты (BeanMethodInterceptor, BeanFactoryAwareMethodInterceptor) в качестве методов обратного вызова для усилителей!
    • Верните объект прокси-класса!
    • Установите в beanClass класса конфигурации!
  2. создать компонент
    • Обнаружено, что при создании bean-компонента он привязывается к классу конфигурации (то есть методу добавления @Bean)!
    • Вызовите метод расширенного класса конфигурации и запишите его!
    • Определите, согласуется ли перехваченный метод с записанным методом
      • Если это непротиворечиво, перейдите к исходной логике создания!
      • Непоследовательно, просто возьмите его с бобовой фабрики!
    • вернуть созданный бин

сбить!


Знаний и познаний у меня мало.Если есть ошибка в понимании статьи, милости просим в приват и поправьте меня! Добро пожаловать, чтобы обратить внимание на публичный аккаунт автора, вместе добиваться прогресса и учиться вместе!