Parlons de la façon d'actualiser dynamiquement les beans dans les projets Spring

Préface

Je discutais avec un ami il y a quelque temps. Il avait un seul projet Spring sous la main. Chaque fois que la configuration de la base de données changeait, il devait redémarrer le projet pour que la configuration prenne effet. Il voulait juste savoir s'il existait un moyen de faire en sorte que la configuration prenne effet sans redémarrer le projet. À ce moment-là, je lui ai dit que nous pouvions utiliser le centre de configuration, il voulait dire que parce qu'il s'agissait d'un projet de maintenance, il ne voulait pas introduire un centre de configuration supplémentaire et augmenter les coûts d'exploitation et de maintenance. Plus tard, j'ai discuté avec lui d'un plan pour mettre en œuvre un programme qui surveille les modifications des fichiers de configuration. Lorsque les modifications des fichiers sont détectées, les opérations de modification correspondantes sont effectuées. Le processus spécifique est le suivant
Insérer la description de l'image ici
. Dans ces étapes, le plus difficile est de savoir comment actualiser dynamiquement le bean, car mon ami est un projet Spring. Aujourd'hui, parlons de la façon d'implémenter l'actualisation dynamique du bean dans le projet Spring.

Idées de mise en œuvre

Les amis qui connaissent Spring devraient savoir que le bean singleton de Spring est mis en cache dans la carte singletonObjects, afin que le bean puisse être actualisé en modifiant singletonObjects. Nous pouvons y parvenir en appelant les méthodes removeSingleton et addSingleton, mais l'inconvénient de cette implémentation est qu'elle modifiera le cycle de vie du bean, ce qui entraînera l'échec de certaines des fonctions améliorées d'origine, telles que AOP. Mais en tant que framework extrêmement excellent, spring fournit des points d'extension qui nous permettent de gérer nous-mêmes les beans. Ce point d'extension a pour effet de gérer les beans en spécifiant la portée.

Étapes de mise en œuvre

1. Portée personnalisée

public class RefreshBeanScope implements Scope {
    
    

    private final Map<String,Object> beanMap = new ConcurrentHashMap<>(256);

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
    
    
        if(beanMap.containsKey(name)){
    
    
            return beanMap.get(name);
        }

        Object bean = objectFactory.getObject();
        beanMap.put(name,bean);
        return bean;
    }

    @Override
    public Object remove(String name) {
    
    
        return beanMap.remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    
    

    }

    @Override
    public Object resolveContextualObject(String key) {
    
    
        return null;
    }

    @Override
    public String getConversationId() {
    
    
        return null;
    }
}

2. Enregistrement de la portée personnalisée

public class RefreshBeanScopeDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    
    

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    
    

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    
    
            beanFactory.registerScope(SCOPE_NAME,new RefreshBeanScope());
    }
}

3. Annotations de portée personnalisées (facultatif)

@Target({
    
     ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refreshBean")
@Documented
public @interface RefreshBeanScope {
    
    



    /**
     * @see Scope#proxyMode()
     * @return proxy mode
     */
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

4. Écrire une logique d'actualisation du bean de portée personnalisée

@RequiredArgsConstructor
public class RefreshBeanScopeHolder implements ApplicationContextAware {
    
    
    
    private final DefaultListableBeanFactory beanFactory;

    private ApplicationContext applicationContext;
    
    
    public List<String> refreshBean(){
    
    
        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        List<String> refreshBeanDefinitionNames = new ArrayList<>();
        for (String beanDefinitionName : beanDefinitionNames) {
    
    
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
            if(SCOPE_NAME.equals(beanDefinition.getScope())){
    
    
                beanFactory.destroyScopedBean(beanDefinitionName);
                beanFactory.getBean(beanDefinitionName);
                refreshBeanDefinitionNames.add(beanDefinitionName);
                applicationContext.publishEvent(new RefreshBeanEvent(beanDefinitionName));
            }
        }

        return Collections.unmodifiableList(refreshBeanDefinitionNames);
        
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        this.applicationContext = applicationContext;
    }
}

Les étapes ci-dessus constituent le processus d'implémentation des beans de gestion de portée personnalisés. Ci-dessous, nous utilisons un exemple d'actualisation du bean via des modifications de configuration pour démontrer les étapes ci-dessus.

Exemple

1. Créez le fichier de configuration des propriétés config/config.properties dans le répertoire du projet src/main/rescoures


et remplissez le contenu du test

test:
  name: zhangsan2222

2. Chargez config.yml au printemps

    public static void setConfig() {
    
    
        String configLocation = getProjectPath() + "/src/main/resources/config/config.yml";
        System.setProperty("spring.config.additional-location",configLocation);
    }

 public static String getProjectPath() {
    
    
        String basePath = ConfigFileUtil.class.getResource("").getPath();
        return basePath.substring(0, basePath.indexOf("/target"));
    }

3. Mettre en œuvre la surveillance de la configuration

Remarque : Ceci peut être réalisé en utilisant WatchMonitor de hutool ou la surveillance des fichiers d'Apache Common io.

Prenons l'exemple d'Apache Common Io

a. Introduire common-io gav dans le fichier pom d'entreprise

  <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${common-io.version}</version>
        </dependency>

B. Écouteur de changement de fichier personnalisé

@Slf4j
public class ConfigPropertyFileAlterationListener extends FileAlterationListenerAdaptor {
    
    


    private ApplicationContext applicationContext;

    public ConfigPropertyFileAlterationListener(ApplicationContext applicationContext) {
    
    
        this.applicationContext = applicationContext;
    }

    @Override
    public void onStart(FileAlterationObserver observer) {
    
    
        super.onStart(observer);
    }

    @Override
    public void onDirectoryCreate(File directory) {
    
    
        super.onDirectoryCreate(directory);
    }

    @Override
    public void onDirectoryChange(File directory) {
    
    
       super.onDirectoryChange(directory);

    }

    @Override
    public void onDirectoryDelete(File directory) {
    
    
        super.onDirectoryDelete(directory);
    }

    @Override
    public void onFileCreate(File file) {
    
    
        super.onFileCreate(file);
    }

    @Override
    public void onFileChange(File file) {
    
    
        log.info(">>>>>>>>>>>>>>>>>>>>>>>>> Monitor PropertyFile with path --> {}",file.getName());
        refreshConfig(file);

    }

    @Override
    public void onFileDelete(File file) {
    
    
        super.onFileDelete(file);

    }

    @Override
    public void onStop(FileAlterationObserver observer) {
    
    
        super.onStop(observer);
    }
    }

c. Démarrez l'écouteur de fichiers

   @SneakyThrows
    private static void monitorPropertyChange(FileMonitor fileMonitor, File file,ApplicationContext context){
    
    
        if(fileMonitor.isFileScanEnabled()) {
    
    
            String ext = "." + FilenameUtils.getExtension(file.getName());
            String monitorDir = file.getParent();
            //轮询间隔时间
            long interval = TimeUnit.SECONDS.toMillis(fileMonitor.getFileScanInterval());
            //创建文件观察器
            FileAlterationObserver observer = new FileAlterationObserver(
                    monitorDir, FileFilterUtils.and(
                    FileFilterUtils.fileFileFilter(),
                    FileFilterUtils.suffixFileFilter(ext)));
            observer.addListener(new ConfigPropertyFileAlterationListener(context));

            //创建文件变化监听器
            FileAlterationMonitor monitor = new FileAlterationMonitor(interval, observer);
            //开始监听
            monitor.start();
        }
    }

4. Surveillez les modifications des fichiers et actualisez PropertySource et les beans

  @SneakyThrows
    private void refreshConfig(File file){
    
    
        ConfigurableEnvironment environment = applicationContext.getBean(ConfigurableEnvironment.class);
        MutablePropertySources propertySources = environment.getPropertySources();
        PropertySourceLoader propertySourceLoader = new YamlPropertySourceLoader();
        List<PropertySource<?>> propertySourceList = propertySourceLoader.load(file.getAbsolutePath(), applicationContext.getResource("file:"+file.getAbsolutePath()));
        for (PropertySource<?> propertySource : propertySources) {
    
    
           if(propertySource.getName().contains(file.getName())){
    
    
               propertySources.replace(propertySource.getName(),propertySourceList.get(0));
           }


        }


        RefreshBeanScopeHolder refreshBeanScopeHolder = applicationContext.getBean(RefreshBeanScopeHolder.class);
        List<String> strings = refreshBeanScopeHolder.refreshBean();
        log.info(">>>>>>>>>>>>>>> refresh Bean :{}",strings);


    }

5. Testez

a. Écrivez le contrôleur et définissez la portée du contrôleur sur notre portée personnalisée.

@RestController
@RequestMapping("test")
@RefreshBeanScope
public class TestController {
    
    


    @Value("${test.name: }")
    private String name;


    @GetMapping("print")
    public String print(){
    
    
        return name;
    }
}

Le contenu original de test.name est le suivant

test:
  name: zhangsan2222

Nous accédons via le navigateur


b. Nous ne redémarrons pas le serveur pour le moment et modifions test.name comme suit

test:
  name: zhangsan3333

À ce stade, nous avons constaté que la console afficherait nos informations de journal.


Visitez à nouveau via le navigateur


J'ai constaté que le contenu avait changé

Annexe : Déclenchement de la méthode de portée personnalisée

1.Comment obtenir la portée

	// Create bean instance.
				if (mbd.isSingleton()) {
    
    
					sharedInstance = getSingleton(beanName, () -> {
    
    
						try {
    
    
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
    
    
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

				else if (mbd.isPrototype()) {
    
    
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
    
    
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
    
    
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
    
    
					String scopeName = mbd.getScope();
					final Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
    
    
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
    
    
						Object scopedInstance = scope.get(beanName, () -> {
    
    
							beforePrototypeCreation(beanName);
							try {
    
    
								return createBean(beanName, mbd, args);
							}
							finally {
    
    
								afterPrototypeCreation(beanName);
							}
						});
						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
    
    
						throw new BeanCreationException(beanName,
								"Scope '" + scopeName + "' is not active for the current thread; consider " +
								"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
								ex);
					}
				}
			}
			catch (BeansException ex) {
    
    
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}

L'heure de déclenchement correspond à l'appel de getBean.

2, méthode de suppression de la portée


	@Override
	public void destroyScopedBean(String beanName) {
    
    
		RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
		if (mbd.isSingleton() || mbd.isPrototype()) {
    
    
			throw new IllegalArgumentException(
					"Bean name '" + beanName + "' does not correspond to an object in a mutable scope");
		}
		String scopeName = mbd.getScope();
		Scope scope = this.scopes.get(scopeName);
		if (scope == null) {
    
    
			throw new IllegalStateException("No Scope SPI registered for scope name '" + scopeName + "'");
		}
		Object bean = scope.remove(beanName);
		if (bean != null) {
    
    
			destroyBean(beanName, bean, mbd);
		}
	}

L'heure de déclenchement appelle en fait la méthode destroyScopedBean.

Résumer

Si vous effectuez des recherches sur Spring Cloud RefreshScope, vous constaterez que l'implémentation ci-dessus est une version approximative de RefreshScope.

lien de démonstration

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-bean-refresh

Je suppose que tu aimes

Origine blog.csdn.net/kingwinstar/article/details/130967686
conseillé
Classement