programing tip

런타임에 Spring Bean 정의를 바꿀 수 있습니까?

itbloger 2020. 11. 9. 07:53
반응형

런타임에 Spring Bean 정의를 바꿀 수 있습니까?


다음 시나리오를 고려하십시오. 내가 속성을 구성해야 빈과 Spring 애플리케이션 컨텍스트가 생각 DataSourceMailSender. 변경 가능한 애플리케이션 구성은 별도의 빈에 의해 관리됩니다 configuration.

관리자는 이제 이메일 주소 또는 데이터베이스 URL과 같은 구성 값을 변경할 수 있으며 런타임에 구성된 Bean을 다시 초기화하고 싶습니다.

위에서 구성 가능한 빈의 속성 (예 : FactoryBean생성자 또는 생성자 주입)을 단순히 수정할 수는 없지만 빈 자체를 다시 만들어야 한다고 가정 합니다.

이것을 달성하는 방법에 대한 생각이 있습니까? 전체 구성 작업을 구성하는 방법에 대한 조언도 받게되어 기쁩니다. 고정 된 것은 없습니다. :-)

편집하다

좀 명확히하기 위해 구성을 업데이트하는 방법이나 정적 구성 값을 삽입하는 방법을 묻는 것이 아닙니다. 예를 들어 보겠습니다.

<beans>
    <util:map id="configuration">
        <!-- initial configuration -->
    </util:map>

    <bean id="constructorInjectedBean" class="Foo">
        <constructor-arg value="#{configuration['foobar']}" />
    </bean>

    <bean id="configurationService" class="ConfigurationService">
        <property name="configuration" ref="configuration" />
    </bean>
</beans>

그래서 constructorInjectedBean생성자 주입을 사용 하는 빈이 있습니다. 빈의 구성이 매우 비싸서 프로토 타입 범위 또는 팩토리 프록시를 사용하는 것은 선택 사항이 아니라고 생각해보십시오 DataSource.

내가하고 싶은 것은 구성이 업데이트 될 때마다 ( configurationService빈을 통해 constructorInjectedBean응용 프로그램 컨텍스트 및 종속 빈에 다시 생성되고 다시 주입되는 것입니다.

우리는 그것이 constructorInjectedBean인터페이스를 사용하고 있다고 안전하게 가정 할 수 있으므로 프록시 마법은 실제로 선택 사항입니다.

질문이 좀 더 명확 해 졌으면합니다.


나는 홀더 빈이 holdee에게 위임하는 '홀더 빈'접근법 (본질적으로 데코레이터)을 생각할 수 있으며, 다른 빈에 의존성으로 주입되는 홀더 빈이다. 누구도 보유자에 대한 참조를 가지고 있지 않습니다. 이제 홀더 빈의 구성이 변경되면이 새 구성으로 holdee를 다시 만들고 위임을 시작합니다.


다음은 과거에 내가 한 방법입니다. 즉석에서 변경할 수있는 구성에 의존하는 서비스를 실행하여 수명주기 인터페이스를 구현합니다. IRefreshable :

public interface IRefreshable {
  // Refresh the service having it apply its new values.
  public void refresh(String filter);

  // The service must decide if it wants a cache refresh based on the refresh message filter.
  public boolean requiresRefresh(String filter);
}

구성이 변경된 JMS 토픽에 대한 구성 브로드 캐스트를 수정할 수있는 컨트롤러 (또는 서비스) (구성 개체의 이름 제공). 그런 다음 메시지 구동 Bean은 IRefreshable을 구현하는 모든 Bean에서 IRefreshable 인터페이스 계약을 호출합니다.

Spring의 좋은 점은 새로 고침해야하는 애플리케이션 컨텍스트에서 서비스를 자동으로 감지하여 명시 적으로 구성 할 필요가 없다는 것입니다.

public class MyCacheSynchService implements InitializingBean, ApplicationContextAware {
 public void afterPropertiesSet() throws Exception {
  Map<String, ?> refreshableServices = m_appCtx.getBeansOfType(IRefreshable.class);
  for (Map.Entry<String, ?> entry : refreshableServices.entrySet() ) {
   Object beanRef = entry.getValue();
   if (beanRef instanceof IRefreshable) {
    m_refreshableServices.add((IRefreshable)beanRef);
   }
  }
 }
}

이 접근 방식은 많은 앱 서버 중 하나가 구성을 변경할 수있는 클러스터 된 애플리케이션에서 특히 잘 작동하며,이를 모두 인식해야합니다. 변경을 트리거하는 메커니즘으로 JMX를 사용하려는 경우 JMX Bean은 속성이 변경 될 때 JMS 주제로 브로드 캐스트 할 수 있습니다.


JMX를 봐야 합니다. Spring은 이에 대한 지원도 제공합니다.


스크립트 빈을 다루기 위해 추가로 업데이트 된 답변

스프링 2.5.x +가 지원하는 또 다른 접근 방식은 스크립트 된 빈의 접근 방식입니다. 스크립트에 다양한 언어를 사용할 수 있습니다. BeanShell은 Java와 동일한 구문을 가지고 있다는 점을 감안할 때 가장 직관적 일 수 있지만 일부 외부 종속성이 필요합니다. 그러나 예제는 Groovy에 있습니다.

Spring 문서 의 섹션 24.3.1.2는 이를 구성하는 방법을 다루지 만, 여기에 내가 편집 한 접근 방식을 설명하는 몇 가지 중요한 발췌문이 있습니다.

<beans>

    <!-- This bean is now 'refreshable' due to the presence of the 'refresh-check-delay' attribute -->
    <lang:groovy id="messenger"
          refresh-check-delay="5000" <!-- switches refreshing on with 5 seconds between checks -->
          script-source="classpath:Messenger.groovy">
        <lang:property name="message" value="defaultMessage" />
    </lang:groovy>

    <bean id="service" class="org.example.DefaultService">
        <property name="messenger" ref="messenger" />
    </bean>

</beans>

Groovy 스크립트는 다음과 같습니다.

package org.example

class GroovyMessenger implements Messenger {

    private String message = "anotherProperty";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message
    }
}

시스템 관리자는 변경을 원할 때 스크립트의 내용을 적절하게 편집 할 수 있습니다. 스크립트는 배포 된 응용 프로그램의 일부가 아니며 알려진 파일 위치 (또는 시작하는 동안 표준 PropertyPlaceholderConfigurer를 통해 구성된 위치)를 참조 할 수 있습니다.

이 예제에서는 Groovy 클래스를 사용하지만 클래스가 간단한 속성 파일을 읽는 코드를 실행하도록 할 수 있습니다. 이렇게하면 스크립트를 직접 편집하지 않고 터치 만하면 타임 스탬프를 변경할 수 있습니다. 그런 다음이 작업은 다시로드를 트리거하고, 차례로 (업데이트 된) 속성 파일에서 속성 새로 고침을 트리거하고, 마지막으로 Spring 컨텍스트 내의 값을 업데이트하고 이동합니다.

문서는이 기술이 생성자-주입에 대해 작동하지 않는다고 지적하지만 아마도 당신은 그것을 해결할 수 있습니다.

동적 속성 변경을 다루기 위해 업데이트 된 답변

전체 소스 코드제공 하는 이 기사에서 인용 한 한 가지 접근 방식은 다음과 같습니다.

* a factory bean that detects file system changes
* an observer pattern for Properties, so that file system changes can be propagated
* a property placeholder configurer that remembers where which placeholders were used, and updates singleton beans’ properties
* a timer that triggers the regular check for changed files

관찰자 패턴은 인터페이스와 클래스 ReloadableProperties, ReloadablePropertiesListener, PropertiesReloadedEvent 및 ReloadablePropertiesBase에 의해 구현됩니다. 그들 중 어느 것도 특히 흥미롭지 않으며 정상적인 청취자 처리입니다. DelegatingProperties 클래스는 속성이 업데이트 될 때 현재 속성을 투명하게 교환하는 역할을합니다. 전체 속성 맵을 한 번에 업데이트하므로 응용 프로그램이 불일치 중간 상태를 방지 할 수 있습니다 (나중에 자세히 설명).

이제 ReloadablePropertiesFactoryBean을 작성하여 ReloadableProperties 인스턴스를 생성 할 수 있습니다 (PropertiesFactoryBean처럼 Properties 인스턴스 대신). 그렇게하라는 메시지가 표시되면 RPFB는 파일 수정 시간을 확인하고 필요한 경우 ReloadableProperties를 업데이트합니다. 이것은 관찰자 패턴 기계를 트리거합니다.

우리의 경우 유일한 리스너는 ReloadingPropertyPlaceholderConfigurer입니다. 플레이스 홀더의 모든 사용을 추적한다는 점을 제외하면 표준 스프링 PropertyPlaceholderConfigurer와 똑같이 작동합니다. 이제 속성이 다시로드되면 수정 된 각 속성의 모든 사용이 발견되고 해당 싱글 톤 Bean의 속성이 다시 할당됩니다.

정적 속성 변경 사항을 다루는 아래의 원래 답변 :

Spring 컨텍스트에 외부 속성을 주입하려는 것처럼 들립니다. PropertyPlaceholderConfigurer이 목적을 위해 설계되었습니다 :

  <!-- Property configuration (if required) -->
  <bean id="serverProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <list>
        <!-- Identical properties in later files overwrite earlier ones in this list -->
        <value>file:/some/admin/location/application.properties</value>
      </list>
    </property>
  </bean>

그런 다음 Ant 구문 자리 표시자를 사용하여 외부 속성을 참조합니다 (Spring 2.5.5 이상에서 원하는 경우 중첩 될 수 있음).

  <bean id="example" class="org.example.DataSource">
    <property name="password" value="${password}"/>
  </bean>

그런 다음 application.properties 파일이 관리자 및 애플리케이션을 실행하는 사용자 만 액세스 할 수 있는지 확인합니다.

application.properties 예 :

password = Aardvark


또는 이 비슷한 질문 의 접근 방식을 사용할 수 있으므로 내 솔루션사용할 수 있습니다 .

접근 방식은 속성 파일을 통해 구성된 빈을 갖는 것이며 해결책은

  • 속성이 변경된 경우 전체 applicationContext를 새로 고치거나 (예약 된 작업을 자동으로 사용하거나 JMX를 사용하여 수동으로)
  • 전용 속성 공급자 개체를 사용하여 모든 속성에 액세스합니다. 이 속성 공급자는 수정을 위해 속성 파일을 계속 확인합니다. 프로토 타입 기반 속성 조회가 불가능한 Bean의 경우 업데이트 된 속성 파일을 찾을 때 속성 공급자가 실행할 사용자 지정 이벤트 를 등록합니다. 라이프 사이클이 복잡한 Bean은 해당 이벤트를 수신하고 스스로 새로 고쳐야합니다.

이것은 내가 시도한 것이 아니라 포인터를 제공하려고합니다.

애플리케이션 컨텍스트가 AbstractRefreshableApplicationContext (예 : XmlWebApplicationContext, ClassPathXmlApplicationContext)의 하위 클래스라고 가정합니다. AbstractRefreshableApplicationContext.getBeanFactory ()는 ConfigurableListableBeanFactory의 인스턴스를 제공합니다. BeanDefinitionRegistry의 인스턴스인지 확인하십시오. 그렇다면 'registerBeanDefinition'메소드를 호출 할 수 있습니다. 이 접근 방식은 Spring 구현과 밀접하게 결합됩니다.

AbstractRefreshableApplicationContext 및 DefaultListableBeanFactory의 코드를 확인하십시오 ( 'AbstractRefreshableApplicationContext getBeanFactory ()'를 호출 할 때 얻을 수있는 구현입니다).


ApplicationContext에 "재구성 가능"이라는 사용자 지정 범위를 만들 수 있습니다. 이 범위에있는 모든 Bean의 인스턴스를 작성하고 캐시합니다. 구성 변경시 캐시를 지우고 새 구성으로 처음 액세스 할 때 빈을 다시 생성합니다. 이 작업을 수행하려면 재구성 가능한 Bean의 모든 인스턴스를 AOP 범위 프록시로 래핑하고 Spring-EL로 구성 값에 config액세스해야합니다 #{ config['key'] }. ApplicationContext에 호출 된 맵을 넣고 .


옵션 1 :

  1. 또는에 configurable콩을 주입하십시오 . 항상 이러한 Bean 내에서 구성 Bean에서 구성 가능한 값을 가져 오십시오.DataSourceMailSender
  2. configurable내부에서 스레드를 실행하여 외부에서 구성 가능한 속성 (파일 등)을 주기적으로 읽습니다. 이렇게 configurable하면 관리자가 속성을 변경 한 후 빈이 자동으로 새로 고쳐 지므로 DataSource업데이트 된 값을 자동으로 가져옵니다.

옵션 2 (나쁜 것 같지만 아닐 수도 있습니다-사용 사례에 따라 다름) :

  1. 항상 범위를 사용하여 DataSource/ MailSender- 유형의 Bean에 대해 새 Bean을 작성하십시오 prototype. 빈의 초기화에서 속성을 새로 읽으십시오.

옵션 3 : JMX 사용에 대한 @ mR_fr0g 제안은 나쁜 생각이 아닐 수도 있습니다. 당신이 할 수있는 일은 :

  1. 구성 빈을 MBean으로 노출하십시오 ( http://static.springsource.org/spring/docs/2.5.x/reference/jmx.html 읽기 )
  2. 관리자에게 MBean의 구성 특성을 변경하도록 요청하십시오 (또는 해당 소스에서 특성 업데이트를 트리거하기 위해 Bean에 인터페이스를 제공하십시오).
  3. 이 MBean (작성해야 할 새로운 Java 코드)은 Bean (변경된 속성을 변경 / 삽입하려는 항목)의 참조를 유지해야합니다. 이것은 간단해야합니다 (빈 이름 / 클래스의 setter 주입 또는 런타임 가져 오기를 통해)
    1. MBean의 속성이 변경 (또는 트리거)되면 각 Bean에서 적절한 setter를 호출해야합니다. 이렇게하면 레거시 코드가 변경되지 않고 런타임 속성 변경 사항을 계속 관리 할 수 ​​있습니다.

HTH!


You may want to have a look at the Spring Inspector a plug-gable component that provides programmatic access to any Spring based application at run-time. You can use Javascript to change configurations or manage the application behaviour at run-time.


Here is the nice idea of writing your own PlaceholderConfigurer that tracks the usage of properties and changes them whenever a configuration change occurs. This has two disadvantages, though:

  1. It does not work with constructor injection of property values.
  2. You can get race conditions if the reconfigured bean receives a changed configuration while it is processing some stuff.

My solution was to copy the original object. Fist i created an interface

/**
 * Allows updating data to some object.
 * Its an alternative to {@link Cloneable} when you cannot 
 * replace the original pointer. Ex.: Beans 
 * @param <T> Type of Object
 */
public interface Updateable<T>
{
    /**
     * Import data from another object
     * @param originalObject Object with the original data
     */
    public void copyObject(T originalObject);
}

For easing the implementation of the function fist create a constructor with all fields, so the IDE could help me a bit. Then you can make a copy constructor that uses the same function Updateable#copyObject(T originalObject). You can also profit of the code of the constructor created by the IDE to create the function to implement:

public class SettingsDTO implements Cloneable, Updateable<SettingsDTO>
{
    private static final Logger LOG = LoggerFactory.getLogger(SettingsDTO.class);

    @Size(min = 3, max = 30)  
    private String id;

    @Size(min = 3, max = 30)
    @NotNull 
    private String name;

    @Size(min = 3, max = 100)
    @NotNull 
    private String description;

    @Max(100)
    @Min(5) 
    @NotNull
    private Integer pageSize;

    @NotNull 
    private String dateFormat; 

    public SettingsDTO()
    { 
    }   

    public SettingsDTO(String id, String name, String description, Integer pageSize, String dateFormat)
    {
        this.id = id;
        this.name = name;
        this.description = description;
        this.pageSize = pageSize;
        this.dateFormat = dateFormat;
    }

    public SettingsDTO(SettingsDTO original)
    {
        copyObject(original);
    }

    @Override
    public void copyObject(SettingsDTO originalObject)
    {
        this.id = originalObject.id;
        this.name = originalObject.name;
        this.description = originalObject.description;
        this.pageSize = originalObject.pageSize;
        this.dateFormat = originalObject.dateFormat;
    } 
}

I used it in a Controller for updating the current settings for the app:

        if (bindingResult.hasErrors())
        {
            model.addAttribute("settingsData", newSettingsData);
            model.addAttribute(Templates.MSG_ERROR, "The entered data has errors");
        }
        else
        {
            synchronized (settingsData)
            {
                currentSettingData.copyObject(newSettingsData);
                redirectAttributes.addFlashAttribute(Templates.MSG_SUCCESS, "The system configuration has been updated successfully");
                return String.format("redirect:/%s", getDao().getPath());
            }
        }

So the currentSettingsData which has the configuration of the application gonna have the updated values, located in newSettingsData. These method allows updating any bean without high complexity.

참고URL : https://stackoverflow.com/questions/4041300/can-i-replace-a-spring-bean-definition-at-runtime

반응형