Кэш автоматической проводки Gemfire NoSuchBeanDefinitionException (Spring 5.0.2/Gemfirev9.2.7)

Мы переходим с Gemfire 8.2.7 на 9.2.1

В рамках запуска Gemfire мы используем SpringContextBootstrappingInitializer для инициализации компонентов Spring, которые @Autowire the Cache.

Тот же код при переходе на Gemfire 9.2.1 (вместе с другим стеком) дает сбой при запуске сервера с ошибкой ниже.

Gemfire 8.2.7 --> Gemfire 9.2.1
Spring-data-Gemfire 1.8.4 --> 2.0.2
Spring-Boot 1.4.7 --> 2.0.0.M7
Spring --> 5.0.2

Вызвано: org.springframework.beans.factory.NoSuchBeanDefinitionException: нет подходящего компонента типа «org.apache.geode.cache.Cache»: ожидается по крайней мере 1 компонент, который квалифицируется как кандидат на автоматическое подключение. Аннотации зависимостей: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

Любые указатели/изменения, необходимые для GemfireConfig? Ниже приведен наш JavaConfig.

@Bean
public CacheFactoryBean gemfireCache() {

    return new CacheFactoryBean();
}

Похоже, ComponentScan срабатывает до процессора конфигурации. Любая идея по контролю этого поведения? Это было проверено на работу в Spring-Boot 1.4.6 (Spring-4.3.8) и решается с помощью параметра @Depends, но просто хотел понять, есть ли какие-либо фундаментальные изменения в порядке инициализации bean-компонентов с более новой версией Spring. .

@Configuration
@EnableAutoConfiguration(exclude = { HibernateJpaAutoConfiguration.class, BatchAutoConfiguration.class })
@Import(value = { GemfireServerConfig.class, JpaConfiguration.class, JpaConfigurableProperties.class })
@ComponentScan(basePackages = "com.test.gemfire", excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class) )

person Jebuselwyn Martin    schedule 02.01.2018    source источник


Ответы (1)


Для начала позвольте мне дать вам несколько советов, так как есть 3 проблемы с вашей формулировкой проблемы выше...

1) Во-первых, вы не дали понять, почему и как вы используете o.s.d.g.support.SpringContextBootstrappingInitializer Документы здесь.

Я могу только предположить, что это потому, что вы запускаете свои серверы GemFire ​​с помощью Gfsh, используя следующую команду...

gfsh> start server --name=MyServer --cache-xml-file=/path/to/cache.xml ...

Где ваш cache.xml определяется аналогично это. В конце концов, это и было первоначальным намерением использовать SpringContextBootstrappingInitializer.

Если это так, почему бы не использовать Gfsh, start serverкоманда, вместо этого --spring-xml-location опция. Например:

gfsh> start server --name=MyServer --spring-xml-location=/by/default/a/classpath/to/applicationContext.xml --classpath=/path/to/spring-data-gemfire-2.0.2.RELEASE.jar:...

Таким образом, вам больше не нужно предоставлять cache.xml только для объявления SpringContextBootstrappingInitializer для начальной загрузки контейнера Spring внутри процесса JVM GemFire. Вы можете просто использовать параметр --spring-xml-location и поместить SDG в путь к классам сервера при запуске сервера.

2) Во-вторых, неясно, в какой тип компонента/бина приложения вы вводите ссылку GemFire ​​Cache (например, регион или другой класс компонентов приложения, например, DAO и т. д.). Предоставление фрагмента кода, показывающего, как вы внедрили ссылку Cache, то есть точку внедрения с использованием аннотации @Autowired, было бы полезно. Например:

@Service
class MyService {

  @Autowired
  private Cache gemfireCache;

  ...
}

3) № 2 был бы более очевидным, если бы вы включили полную трассировку стека, а не только сообщение NoSuchBeanDefinitionException.

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

  1. Очевидно, вы используете "сканирование компонентов пути к классам" (с аннотацией @ComponentScan) и выполняете автоматическое связывание "по типу"; что может быть ключевым на самом деле; Я вернусь к этому позже ниже.

  2. Вы используете аннотацию Spring @Autowired для поля класса компонента (внедрение поля) или свойства (внедрение сеттера), возможно, даже в конструкторе.

  3. Тип этого поля/свойства (или параметра конструктора) определенно org.apache.geode.cache.Cache.

Идем дальше...

Как правило, Spring в первую очередь следует порядку зависимостей. То есть, если A зависит от B, то B должен быть создан до и уничтожен после A. Как правило, Spring будет и может соблюдать это без инцидентов.

Помимо создания bean-компонентов "порядок зависимости" и удовлетворения зависимостей между bean-компонентами (в том числе с аннотацией @DependsOn), порядок создания bean-компонентов довольно слабо определен.

Есть несколько факторов, которые могут повлиять на это, такие как «порядок регистрации» (то есть порядок, в котором объявляются определения bean-компонентов, что особенно верно для bean-компонентов, определенных в XML), «порядок импорта» (при использовании аннотации @Import для @Configuration классов ), отражение Java (включает @Bean определений, объявленных в @Configuration классах) и т. д. Организация конфигурации определенно важна, и к ней нельзя относиться легкомысленно.

Это одна из причин, по которой я не большой сторонник "сканирования компонентов пути к классам. Хотя это может быть удобно, всегда лучше, IMO, быть более "явным" в вашей конфигурации и организации вашей конфигурации по причинам, изложенным здесь в дополнение к другим неочевидным ограничениям. В худшем случае вам определенно следует ограничить область сканирования.

По иронии судьбы, вы исключили/отфильтровали 1 вещь, которая действительно могла бы помочь вашим организационным проблемам... компоненты типа @Configuration:

... excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)

ПРИМЕЧАНИЕ. Учитывая исключение, уверены ли вы, что не исключили класс 1 @Configuration, содержащий ваше определение CacheFactoryBean? Я полагаю, что нет, поскольку вы говорите, что это сработало после включения аннотации @DependsOn.

Очевидно, что существует определенная зависимость между некоторым компонентом вашего приложения (??) и bean-компонентом типа o.a.g.cache.Cache (использующим @Autowired), однако Spring не может ее разрешить.

Я думаю, что Spring не может разрешить зависимость Cache, потому что 1) компонент кэша GemFire ​​еще не создан и 2) Spring не может найти подходящее определение компонента желаемого типа (т.е. o.a.g.cache.Cache) в вашей конфигурации, которое разрешило бы зависимость и заставить сначала создать GemFire ​​Cache, или 3) GemFire ​​Cache был создан первым, но Spring не может разрешить тип как o.a.g.cache.Cache.

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

Есть несколько способов решить эту проблему.

Если проблема в более позднем, 3), то простое объявление вашей зависимости как типа o.a.g.cache.GemFireCache должно решить проблему. Так, например:

@Repository
class MyDataAccessObject {

  @Autowired
  private GemFireCache gemfireCache;

  ...
}

Причина этого в том, что класс o.s.d.g.CacheFactoryBean getObjectType() возвращает тип класса, в общем расширяющий o.a.g.cache.GemFireCache. Это было задумано, поскольку o.s.d.g.client.ClientCacheFactoryBean расширяет o.s.d.g.CacheFactoryBean, хотя я, вероятно, не сделал бы этого таким образом, если бы создал эти классы. Однако это согласуется с тем фактом, что фактический тип кэша в GemFire ​​— o.a.g.internal.cache.GemFireCacheImpl, который косвенно реализует как интерфейс o.a.g.cache.Cache, так и интерфейс o.a.g.cache.client.ClientCache.

Если ваша проблема относится к первому (1) + 2, что немного сложнее), то я бы посоветовал вам использовать более разумную организацию вашей конфигурации, разделенную по проблемам. Например, вы можете инкапсулировать свою конфигурацию GemFire ​​с помощью:

@Configuration
class GemFireConfiguration {
  // define GemFire components (e.g. CacheFactoryBean) here
}

Затем компоненты вашего приложения, некоторые из которых зависят от компонентов GemFire, могут быть определены с помощью:

@Configuration
@Import(GemFireConfiguration.class)
class ApplicationConfiguration {
  // define application beans, including beans dependent on GemFire components
}

Импортируя GemFireConfiguration, вы обеспечиваете создание компонентов/бинов GemFire ​​(создание экземпляров, настройку и инициализацию) в первую очередь.

Вы даже можете использовать более целенаправленное, ограниченное "сканирование компонентов пути к классам" на уровне класса ApplicationConfiguration в тех случаях, когда у вас есть большое количество компонентов приложения (службы, DAO и т. д.).

Затем вы можете заставить свой основной класс приложения Spring Boot управлять всем этим:

@Configuration
@Import(ApplicationConfiguration.class)
class MySpringBootApplication {

  public static void main(String[] args) {
    SpringApplication.run(MySpringBootApplication.class, args);
  }
}

Дело в том, что вы можете быть настолько детализированы, насколько захотите. Мне нравится инкапсулировать конфигурацию по задачам и четко организовывать конфигурацию (используя импорт), чтобы отразить порядок, в котором я хочу, чтобы мои компоненты были созданы (сконструированы, настроены и инициализированы).

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

Наконец, вы всегда можете положиться на аннотацию @DependsOn, как вы и сделали, чтобы гарантировать, что Spring создаст компонент раньше компонента, который его ожидает.

Основываясь на том факте, что аннотация @DependsOn решила вашу проблему, я бы сказал, что это организационная проблема, подпадающая под категорию 1)/2), которую я изложил выше.

Я собираюсь углубиться в это немного глубже и отвечу на свой ответ в комментариях тем, что я найду.

Надеюсь это поможет!

-Джон

person John Blum    schedule 12.01.2018