복붙노트

[SPRING] Java Spring 특정 Bean 재 작성

SPRING

Java Spring 특정 Bean 재 작성

일부 DB 변경시 Runtime (서버 재시작 없음)에서 특정 bean을 다시 작성 (새 Object)하고 싶습니다. 이것이 보이는 방법입니다.

@Component
public class TestClass {

    @Autowired 
    private MyShop myShop; //to be refreshed at runtime bean

    @PostConstruct //DB listeners
    public void initializeListener() throws Exception {
        //...
        // code to get listeners config
        //...

        myShop.setListenersConfig(listenersConfig);
        myShop.initialize();
    }

    public void restartListeners() {
        myShop.shutdownListeners();
        initializeListener();
    }
}

myShop 객체가 Spring에 의해 Singleton으로 작성되었으므로이 코드는 실행되지 않으며 서버가 재시작되지 않으면 컨텍스트가 새로 고쳐지지 않습니다. myShop을 새로 고치는 방법 (새 객체 만들기)

내가 생각할 수있는 한 가지 나쁜 방법은 restartListeners () 내부에 새로운 myShop 객체를 만드는 것이지만 나에게 맞는 것 같지 않습니다.

해결법

  1. ==============================

    1.DefaultListableBeanFactory에서는 public 메서드 destroySingleton ( "beanName")을 사용하여 그걸 가지고 놀 수 있지만, 자동 채워진 bean 인 경우 처음에 autowired 된 객체의 동일한 인스턴스를 유지한다는 것을 알아야합니다. 다음과 같이 시도 할 수 있습니다.

    DefaultListableBeanFactory에서는 public 메서드 destroySingleton ( "beanName")을 사용하여 그걸 가지고 놀 수 있지만, 자동 채워진 bean 인 경우 처음에 autowired 된 객체의 동일한 인스턴스를 유지한다는 것을 알아야합니다. 다음과 같이 시도 할 수 있습니다.

    @RestController
    public class MyRestController  {
    
            @Autowired
            SampleBean sampleBean;
    
            @Autowired
            ApplicationContext context;
            @Autowired
            DefaultListableBeanFactory beanFactory;
    
            @RequestMapping(value = "/ ")
            @ResponseBody
            public String showBean() throws Exception {
    
                SampleBean contextBean = (SampleBean) context.getBean("sampleBean");
    
                beanFactory.destroySingleton("sampleBean");
    
                return "Compare beans    " + sampleBean + "==" 
    
        + contextBean;
    
        //while sampleBean stays the same contextBean gets recreated in the context
                }
    
        }
    

    그것은 꽤 아니지만 어떻게 접근 할 수 있는지 보여줍니다. 컴포넌트 클래스가 아닌 컨트롤러를 다루는 경우, 메소드 인자에 주사가있을 수 있으며, Bean은 메소드 내에서 필요할 때까지 다시 작성되지 않기 때문에 작동합니다. 적어도 그것이 어떻게 생겼는지는 알 수 있습니다. 흥미로운 질문은 콘텍스트에서 제거 되었기 때문에 원래 빈에 추가 된 객체 외에 다른 객체에 대한 참조를 가진 사람이 누구인가하는 것입니다. 콘트롤러에서 콘텍스트에서 삭제 되었기 때문에 여전히 존재하는지 또는 쓰레기 코 히트인지 궁금합니다. 위에서 컨텍스트의 일부 다른 객체가 참조를 가진 경우 문제가 발생합니다.

  2. ==============================

    2.동일한 유스 케이스가 있습니다. 이미 언급했듯이 런타임 중에 bean을 다시 만드는 주요한 문제 중 하나는 이미 삽입 된 참조를 업데이트하는 방법입니다. 이것은 주요 도전 과제를 제시합니다.

    동일한 유스 케이스가 있습니다. 이미 언급했듯이 런타임 중에 bean을 다시 만드는 주요한 문제 중 하나는 이미 삽입 된 참조를 업데이트하는 방법입니다. 이것은 주요 도전 과제를 제시합니다.

    이 문제를 해결하기 위해 Java의 AtomicReference <> 클래스를 사용했습니다. 콩을 직접 주입하는 대신 AtomicReference로 포장 한 다음 주입했습니다. AtomicReference에 의해 래핑 된 객체는 스레드 안전 방식으로 재설정 될 수 있기 때문에 데이터베이스 변경이 감지되면이 객체를 사용하여 기본 객체를 변경할 수 있습니다. 다음은이 패턴의 구성 / 사용 예입니다.

    @Configuration
    public class KafkaConfiguration {
    
        private static final String KAFKA_SERVER_LIST = "kafka.server.list";
        private static AtomicReference<String> serverList;
    
        @Resource
        MyService myService;
    
        @PostConstruct
        public void init() {
            serverList = new AtomicReference<>(myService.getPropertyValue(KAFKA_SERVER_LIST));
        }
    
        // Just a helper method to check if the value for the server list has changed
        // Not a big fan of the static usage but needed a way to compare the old / new values
        public static boolean isRefreshNeeded() {
    
            MyService service = Registry.getApplicationContext().getBean("myService", MyService.class);    
            String newServerList = service.getPropertyValue(KAFKA_SERVER_LIST);
    
            // Arguably serverList does not need to be Atomic for this usage as this is executed
            // on a single thread
            if (!StringUtils.equals(serverList.get(), newServerList)) {
                serverList.set(newServerList);
                return true;
            }
    
            return false;
        }
    
        public ProducerFactory<String, String> kafkaProducerFactory() {
    
            Map<String, Object> configProps = new HashMap<>();
            configProps.put(ProducerConfig.CLIENT_ID_CONFIG, "...");
    
            // Here we are pulling the value for the serverList that has been set
            // see the init() and isRefreshNeeded() methods above
            configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, serverList.get());
    
            configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
            configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
            return new DefaultKafkaProducerFactory<>(configProps);
        }
    
        @Bean
        @Lazy
        public AtomicReference<KafkaTemplate<String, String>> kafkaTemplate() {
    
            KafkaTemplate<String, String> template = new KafkaTemplate<>(kafkaProducerFactory());
            AtomicReference<KafkaTemplate<String, String>> ref = new AtomicReference<>(template);
            return ref;
        }
    }
    

    그런 다음 필요한 곳에 콩을 주입합니다.

    public MyClass1 {
    
        @Resource 
        AtomicReference<KafkaTemplate<String, String>> kafkaTemplate;
        ...
    }
    
    public MyClass2 {
    
        @Resource 
        AtomicReference<KafkaTemplate<String, String>> kafkaTemplate;
        ...
    }
    

    별도의 클래스에서 응용 프로그램 컨텍스트가 시작될 때 시작되는 스케줄러 스레드를 실행합니다. 클래스는 다음과 같이 보입니다.

    class Manager implements Runnable {
    
        private ScheduledExecutorService scheduler;
    
        public void start() {
            scheduler = Executors.newSingleThreadScheduledExecutor();
            scheduler.scheduleAtFixedRate(this, 0, 120, TimeUnit.SECONDS);
        }
    
        public void stop() {
            scheduler.shutdownNow();
        }
    
        @Override
        public void run() {
    
            try {
                if (KafkaConfiguration.isRefreshNeeded()) {
    
                    AtomicReference<KafkaTemplate<String, String>> kafkaTemplate = 
                        (AtomicReference<KafkaTemplate<String, String>>) Registry.getApplicationContext().getBean("kafkaTemplate");
    
                    // Get new instance here.  This will have the new value for the server list
                    // that was "refreshed"
                    KafkaConfiguration config = new KafkaConfiguration();
    
                    // The set here replaces the wrapped objet in a thread safe manner with the new bean
                    // and thus all injected instances now use the newly created object
                    kafkaTemplate.set(config.kafkaTemplate().get());
                }
    
            } catch (Exception e){
    
            } finally {
    
            }
        }
    }
    

    이것이 약간의 냄새가 나는 것처럼 내가 이것을지지한다면 나는 울타리에있다. 그러나 제한적이고주의 깊은 사용에서는 명시된 유스 케이스에 대한 대체 접근법을 제공합니다. Kafka 관점에서 볼 때이 코드 예제는 이전 제작자를 열어 둡니다. 실제로는 그것을 닫으려면 오래된 제작자에게 flush () 호출을 적절하게 수행해야합니다. 그러나 이것은 그 예가 보여 주려는 것이 아닙니다.

  3. from https://stackoverflow.com/questions/27998502/java-spring-recreate-specific-bean by cc-by-sa and MIT license