Spring Interceptor автоматически внедряет FeignClient, что приводит к циклической зависимости 2.0

Spring

1. Восстановление места ошибки

описание проблемы

Когда я писал перехватчик, многие классы были внедрены через конструктор, а зависимость FeignClient была объявлена ​​через конструктор в перехватчике.После запуска проекта анализ зависимостей Spring показал, что эти классы генерируют циклические зависимости

сообщение об ошибке


Анализ аномалий

thirdDemoэто стартовый класс

TakeResourcesClientЭто класс с аннотацией @Component, который вызывается через @Autowired.ThirdFeignClient

@Component
public class TakeResourcesClient {
    @Autowired
    private ThirdFeignClient thirdFeignClient;

    @Autowired
    private ThirdProperties thirdProperties;
    
    ……
}

Это может объяснить зависимость циклических зависимостей 1 и зависимость 2. SpringBoot автоматически загружает @Component при запуске и анализирует его зависимости.ThirdFeignClient

@FeignClient(path = PathConstant.CONTEXT_PATH + PathConstant.URL, name = PathConstant.NAME_APPLICATION)
public interface ThirdFeignClient {
 
}

ЭтоThirdFeignClient, это клиент Feign с аннотацией @FeignClient

Дальше спускаемся вниз, зависимость 3 не объяснима, вот результат

Вопрос 1:ThirdFeignClientЗачем полагаться наWebMvcAutoConfiguration$EnableWebMvcConfiguration?

Продолжить вниз, проанализировать зависимости 4

ThirdInterceptorConfigКласс конфигурации перехватчика, унаследованныйWebMvcConfigurationSupport, конструктор вводитThirdFeignClientзависимость

@Component
public class ThirdInterceptorConfig  extends WebMvcConfigurationSupport  {

    private final List<AuthHandle> authHandles;

    private final ThirdProperties thirdProperties;

    private final ThirdFeignClient thirdFeignClient;

    @Autowired
    public ThirdInterceptorConfig(List<AuthHandle> authHandles, ThirdProperties thirdProperties, ThirdFeignClient thirdFeignClient) {
        this.authHandles = authHandles;
        this.thirdProperties = thirdProperties;
        this.thirdFeignClient = thirdFeignClient;
    }
    
    
     @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new ThirdInterceptor(authHandles, thirdProperties, thirdFeignClient))
     ……       
    }

Но здесь будут ошибки, зависимость 2TakeResourcesClient --> ThirdFeignClient(Вызов ThirdFeignClient через @Autowired)

Зависимость 4 внедряется через конструкторThirdFeignClient, также должно бытьThirdInterceptorConfig --> ThirdFeignClien

Наконец, посмотрите на конфигурацию перехватчика, которая также внедряется через конструктор.ThirdFeignClient,фактическиThirdInterceptorConfigвводитьThirdFeignClient, цель состоит в том, чтобы создатьThirdInterceptorобъект, вводитьThirdFeignClient

перехватчик

public class ThirdInterceptor extends HandlerInterceptorAdapter {
    private final List<AuthHandle> authHandles;
    private final ThirdProperties thirdProperties;
    private ThirdFeignClient thirdFeignClient;

    public ThirdInterceptor(List<AuthHandle> authHandles, ThirdProperties thirdProperties, ThirdFeignClient thirdFeignClient) {
        this.authHandles = authHandles;
        this.thirdProperties = thirdProperties;
        this.thirdFeignClient = thirdFeignClient;
    }
    
    ……

Продолжая вниз, зависимость 5 и зависимость 6 не могут быть объяснены, поэтому возникают следующие проблемы.

Вопрос 2:mvcResourceUrlProviderчто это? ПочемуThirdInterceptorConfigполагатьсяmvcResourceUrlProvider?

Вопрос 3: ПочемуmvcResourceUrlProviderзависит отThirdFeignClient?

2. Анализ ошибок

2.1 Гипотеза

Результат анализа зависимостей может не быть реальной зависимостью, но при выполнении анализа зависимостей возникает какое-то исключение.mvcResourceUrlProvidermvcResourceUrlProviderиFeignClientЗагрузка связана с порядком загрузки перехватчика, поэтому для отладки первой сцены исключения броска см. иmvcResourceUrlProviderЭто имеет значение.

2.2Debug

Анализ аномалий

Первая сцена аномалии выглядит следующим образом


Смысл анализа этого кода должен быть:org.springframework.beans.factory.support.DefaultSingletonBeanRegistryизgetSingleton()функция создаетсяmvcResourceUrlProviderпрежде чем звонитьbeforeSingletonCreation()функция для проверкиmvcResourceUrlProviderсуществуетthis.singletonsCurrentlyInCreationСуществует ли он уже, если да, сгенерируйте исключение

продолжать следитьmvcResourceUrlProviderгде он изначально загружается


Проследите стек вызовов и найдитеorg.springframework.context.event.AbstractApplicationEventMulticasterизretrieveApplicationListeners()функция,mvcResourceUrlProviderПоявляется здесь впервые, т.listenerBeansэлемент , иlistenerBeansда

listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);

инициализированное назначение,listenerBeansВсего имеется 22 объекта , которые представляют собой инициализированные по умолчанию экземпляры SpringBoot.

После поиска этого класса это действительно конфигурация по умолчанию, которая представляет собой Bean, определенный во время процесса запуска веб-приложения Springboot. Ссылаться наblog.CSDN.net/Andy_Zhang 2…

Продолжайте спрашивать: почемуthis.singletonsCurrentlyInCreationуже существует вmvcResourceUrlProvider, должны быть другие места для загрузки, сначала ищите глобальноmvcResourceUrlProvider,существуетorg.springframework.web.servlet.config.annotation.WebMvcConfigurationSupportсередина


Есть только одно место для прямого звонка, которое также находится вorg.springframework.web.servlet.config.annotation.WebMvcConfigurationSupportсередина


здесь должно бытьWebMvcConfigurationSupporПосле добавления перехватчика вызовите его через аннотацию @BeanmvcResourceUrlProviderзарегистрирован как перехватчик по умолчанию, иmvcResourceUrlProviderОн предварительно загружен как конфигурация по умолчанию.

(mvcResourceUrlProviderпоставкаResourceUrlProviderпример,ResourceUrlProviderЭто основной компонент преобразования для получения внешних URL-адресов и его внутреннихMap<String, ResourceHttpRequestHandler> handlerMapИспользуется для разбора цепочки. )


Пока проблема, которую предстоит решить,

Почемуthis.singletonsCurrentlyInCreationуже существует вmvcResourceUrlProvider?

существуетbeforeSingletonCreation()Точка останова обнаружила, что эта функция будет выполняться дважды, при первом ее выполнении,this.singletonsCurrentlyInCreationНет вmvcResourceUrlProvider, исключение не будет вызвано, исключение будет вызвано только во второй раз

Анализ процесса вызова первого исполнения функции this.singletonsCurrentlyInCreation()

При первом исполненииthis.singletonsCurrentlyInCreationНет вmvcResourceUrlProvider, затем поставьтеmvcResourceUrlProviderДобавьте его, чтобы исключение срабатывало при выполнении второго выполнения.


Я не знаю, почему сейчасbeforeSingletonCreation()Функция будет выполнена дважды, в зависимости от функции и связанного с ней именования, ее не следует загружать дважды. Наблюдая за стеком вызовов, выясняется, что он связан с выпуском события обновления.Взгляните на стек вызовов.refresh()функция,


родыorg.springframework.context.support.AbstractApplicationContext, это должно быть шагом на этапе создания контекста.

refresh()Сразу после стека вызововcreateContext(),родыorg.springframework.cloud.context.named.NamedContextFactory, эта функция выполняетсяcontext.refresh(),ТакcontextЗачем он создается, через стек вызовов иcontextсвойства, полагая, что это должно бытьFeignContext,следующее


Теперь выдвинем гипотезу:При разборе автоматической конфигурации Spring анализирует зависимости, сканирует зависимости, связанные с Feign, и считает необходимым создать FeignContext, который выполняется в процессе создания.context.refresh()

В соответствии с информацией, связанной с beanName, проследите стек до функций, связанных с feign, и найдите зависимости, связанные с Feign, следующим образом.


Это видно по имени функции и связанным переменным, которые взяты изFeignClientFactoryBeanПолучено с этой фабрики бобовThirdFeignClientпример, ссылкаАнализ принципа spring-cloud-openfeign, подтверждает, что FeignClientFactoryBean создает фабрику для фиктивных клиентов.

Проследите стек вызовов и продолжите анализ того, какая автоматическая конфигурация связана с зависимостями Feign.


Здесь проверяется зависимость 2, и первая половина приведенной выше гипотезы Spring загружает класс автоконфигурацииTakeResourcesClient, найти это зависит отThirdFeignClient.

Продолжайте обращать внимание здесьdoGetObjectFromFactoryBean(), посмотрите на процесс создания FeignClient


Feign.Builder builder = feign(context);

Выполнение этого кода вызовет другие функции, создаст FeignContext, расположенный вorg.springframework.cloud.context.named.NamedContextFactory

Как показано ниже, он выполняется, когда здесь создается FeignContext.context.refresh(), и предыдущийrefresh()Функция выполняет совпадение, иrefresh()после,будет выполняться впервыеbeforeSingletonCreation(),ПучокmvcResourceUrlProviderдобавить вthis.singletonsCurrentlyInCreationСредний, не исключение


Второе выполнение анализа процесса вызова функции this.singletonsCurrentlyInCreation()

При первом анализе, при отладке во второй раз сначала обратите внимание на то, какие зависимости вызываютсяFeignContextсоздан, и почемуFeignContextнужно создать снова

Тот же способ отслеживания стека вызовов для поиска зависимостей



Как показано на двух рисунках выше, мы можем получитьThirdFeignClient --> thirdInterceptorConfig --> WebMvcAutoConfiguration$EnableWebMvcConfigurationТакие зависимости, то же самое пойдет на созданиеFeignContextШаг


второе исполнениеbeforeSingletonCreation(),ПучокmvcResourceUrlProviderдобавить вthis.singletonsCurrentlyInCreationсередина, вызвать исключение, то есть первую сцену исключения.

анализировать:WebMvcAutoConfiguration$EnableWebMvcConfigurationДолжен быть класс конфигурации перехватчика, т.е.ThirdInterceptorConfig, конструктор явно объявляетThirdFeignClientЗависимость, приводящая ко второму творениюFeignContext

Так почему FeignContext нужно создавать снова?

FeignContextдля изолированной конфигурации, унаследованнойorg.springframework.cloud.context.named.NamedContextFactory, вышеcreateContext,createContextСоздается независимо для каждого пространства именApplicationContext, установите родителем контекст, переданный извне, чтобы можно было совместно использовать компоненты во внешнем контексте.

Следите за созданиемFeignContextПеред оценкой пространства имен каждое выполнениеgetContext()когда,Командное пространство — третья платформа, а количество this.contexts в существующем пространстве имен равно 0, что напрямую приводит к двойному созданию FeignContext., каждый раз, когда вы входитеcreateContext()этап, который должен быть после первого выполненияFeignContextна самом деле не существует в этом контексте.

3. Анализ

На следующем рисунке, согласно приведенному выше анализу, схема шагов выполнения для запуска ненормальной блок-схемы.


Здесь два шага эквивалентны одновременному выполнению, иThirdFeignClientВсе они применяются другими автоматически подключаемыми классами через объявление отображения конструктора, что, как мне кажется, приводит к двум загрузкам:ThirdFeignClientклиент Файна,Не вводите явно через конструктор, позвольте контейнеру Spring управлять его генерацией,Его можно вызывать в других местах, и его не нужно инициализировать отображением объявления, чтобы вызвать созданиеFeignContext.

принять меры, чтобы позвонитьThirdFeignClientКласс вызывается аннотацией @Autowired

Ответьте на вопрос 1:

второе исполнениеbeforeSingletonCreation()когда это должно бытьWebMvcAutoConfiguration$EnableWebMvcConfigurationполагаться ThirdFeignClient

Чтобы ответить на вопрос 2:

ThirdInterceptorConfigпоказать зависимостьThirdFeignClient, в результате чего создаетсяFeignContext,context.refresh()загружен сноваmvcResourceUrlProvider

Ответьте на вопрос 3:

mvcResourceUrlProviderне зависеть отThirdFeignClient, загружается дваждыFeignContextИнициированное исключение

4. Осознайте

Модифицированный код выглядит следующим образом

public class ThirdInterceptor extends HandlerInterceptorAdapter {
    private static final Logger logger = LoggerFactory.getLogger(ThirdInterceptor.class);

    private final List<AuthHandle> authHandles;
    private final ThirdProperties thirdProperties;
    @Autowired
    private ThirdFeignClient thirdFeignClient;

    public ThirdInterceptor(List<AuthHandle> authHandles, ThirdProperties thirdProperties) {
        this.authHandles = authHandles;
        this.thirdProperties = thirdProperties;
    }
}
@Component
public class TakeResourcesClient {
    @Autowired
    private ThirdFeignClient thirdFeignClient;

    @Autowired
    private ThirdProperties thirdProperties;
}
@Configuration
public class ThirdInterceptorConfig extends WebMvcConfigurationSupport {

    private final List<AuthHandle> authHandles;

    private final ThirdProperties thirdProperties;

    @Autowired
    public ThirdInterceptorConfig(List<AuthHandle> authHandles, ThirdProperties thirdProperties) {
        this.authHandles = authHandles;
        this.thirdProperties = thirdProperties;
    }

    @Bean
    public ThirdInterceptor getThirdInterceptor() {
        return new ThirdInterceptor(authHandles, thirdProperties);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getThirdInterceptor())
    ……

}


После модификации проект запускается нормально, что вполне осуществимо.


И соблюдайте порядок загрузки, в первую загрузкуtakeResourcesClientКогда экземпляр загружается, он уже загруженthirdFeignClientэкземпляр, загрузкаthirdInterceptorConfig ,воплощать в жизнь

ConstructorResolver.setCurrentInjectionPoint(descriptor)

получитьpreviousInjectionPointпредыдущая точка впрыска, внутриthirdFeignClient, больше не будет создаватьсяFeignContext.


5. Заключение

Клиент Feign Spring анализирует зависимости, не внедряет через конструктор, а вызывает его через аннотацию @Autowired при вызове.

Справочная документация

технический блог.Paipaidai.com/2018/05/28/…