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 Гипотеза
Результат анализа зависимостей может не быть реальной зависимостью, но при выполнении анализа зависимостей возникает какое-то исключение.mvcResourceUrlProvider
,иmvcResourceUrlProvider
и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/…