Статьи

Обмен конфигурации Spring Bean во время выполнения

Большинство разработчиков Java в наши дни имеют дело с Spring на регулярной основе, и многие из нас уже знакомы с его возможностями и ограничениями.

Недавно я столкнулся с проблемой, которой раньше не сталкивался: появилась возможность перемонтировать внутренние компоненты бина на основе конфигурации, представленной во время выполнения. Это полезно для простых изменений конфигурации или, возможно, замены чего-то вроде класса « Стратегия» или « Фабрика », а не для перестройки сложной части контекста приложения.

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

Масштабы проблемы

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

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

Код

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static <T> Map<String, T> extractBeans(Class<T> beanType,
   List<String> contextXmls, ApplicationContext parentContext) throws Exception {
 
   List<String> paths = new ArrayList<String>();
   try {
      for (String xml : contextXmls) {
         File file = File.createTempFile("spring", "xml");
         // ... write the file using a utility method
         FileUtils.writeStringToFile(file, xml, "UTF-8");
         paths.add(file.getAbsolutePath());
      }
 
      String[] pathArray = paths.toArray(new String[0]);
      return buildContextAndGetBeans(beanType, pathArray, parentContext);
 
   } finally {
      // ... clean up temp files immediately if desired
   }
}
 
private static <T> Map<String, T> buildContextAndGetBeans(Class<T> beanType,
               String[] paths, ApplicationContext parentContext) throws Exception {
 
   FileSystemXmlApplicationContext context =
      new FileSystemXmlApplicationContext(paths, false, parentContext) {
         @Override  // suppress refresh events bubbling to parent context
         public void publishEvent(ApplicationEvent event) { }
      };
 
   try {
      // avoid classloader errors in some environments
      context.setClassLoader(beanType.getClassLoader());
      context.refresh(); // parse and load context
      Map<String, T> beanMap = context.getBeansOfType(beanType);
 
      return beanMap;
   } finally {
      try {
         context.close();
      } catch (Exception e) {
         // ... log this
      }
   }
}

Если вы посмотрите на buildContextAndGetBeans () , то увидите, что он выполняет большую часть работы, создавая контекст Spring с помощью предоставленных файлов определений XML-бинов. Затем он возвращает карту созданных bean-компонентов запрашиваемого типа.

Примечание. Так как временный контекст Spring уничтожен, убедитесь, что у ваших bean-компонентов нет методов жизненного цикла, которые приводят их в недопустимое состояние при остановке или уничтожении.

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

Вот пример определения XML, которое можно использовать для переподключения нашего FraudService .

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
   <bean id="fraudStrategy" class="com.example.SomeFraudStategory">
      <!-- example of a bean defined in the parent application context that we can reference -->
      <property name="fraudRuleFactory" ref="fraudRuleFactory"/>
   </bean>
</beans>

А вот код, который вы могли бы использовать, чтобы перекомпоновать ваш bean-компонент со ссылкой на определенный fraudStrategy , предполагая, что он есть в служебном классе под названием SpringUtils :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FraudService implements ApplicationContextAware {
 
   private ApplicationContext context;
   // volatile for thread safety (in Java 1.5 and up only)
   private volatile FraudStrategy fraudStrategy;
 
   @Override // get a handle on the the parent context
   public void setApplicationContext(ApplicationContext context) {
      this.context = context;
   }
 
   public void swapFraudStategy(String xmlDefinition) throws Exception {
      List<Sting> definitions = Arrays.asList(xmlDefinition);
      Map<String, FraudStrategy> beans =
         SpringUtils.extractBeans(FraudStrategy.class, definitions, context);
      if (beans.size() != 1) {
         throw new RuntimeException("Invalid number of beans: " + beans .size());
      }
      this.fraudStrategy = beans.values().iterator().next();
   }
 
}

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

Ссылка: обмен конфигурацией Spring Bean во время выполнения от наших партнеров JCG в блоге Carfey Software .

Статьи по Теме :