@Valor de la serie Spring [uso, fuente de datos, actualización dinámica]

Entrevistador: ¿Alguna vez has usado @Value en Spring? Cuéntame sobre eso

Yo: @Value se puede marcar en el campo, y los datos en el archivo de configuración externo, como cierta información de configuración de la base de datos, se pueden colocar en el archivo de configuración y luego inyectar en algunos campos del bean a través de @Value

Entrevistador: Es decir, ¿los datos de @Value vienen del archivo de configuración?

Yo: Bueno, lo más usado en nuestro proyecto es referirnos a la configuración en el archivo de Propiedades a través de @Value

Entrevistador: ¿Hay otras formas de obtener datos de @Value?

Yo: estoy muy feliz en este momento, estudié todas las preguntas que acabo de hacer y dije: por supuesto, puede poner la información de configuración en db u otro medio de almacenamiento, cuando se inicia el contenedor, puede cargar esta información en el entorno, @ El valor aplicado en Valor finalmente se analiza a través del entorno, por lo que solo necesita expandir el entorno para lograrlo.

Entrevistador: No está mal, parece que todavía puedes estudiar Spring, ¿te gusta estudiar el código fuente de Spring?

Yo: Dije con una sonrisa, um, realmente me gusta jugar con el código fuente cuando tengo tiempo libre. Siento que tengo una buena comprensión de la primavera, no competente, pero semi-competente.

Entrevistador: Mirándome y sonriendo, ¿se puede actualizar dinámicamente el valor inyectado de @Value?

Yo: Debería ser posible, recuerdo que hay una anotación @RefreshScope en springboot para realizar la función que dijiste

Entrevistador: ¿Puede decirme cómo se implementa @RefreshScope? ¿Puede presentarlo brevemente?

yo: ah. . . He visto esto antes, pero no lo entendí

Entrevistador: Está bien, puedes regresar y estudiarlo de nuevo, ¿cuánto salario esperas?

Yo: 30,000

Entrevistador: La entrevista de hoy está bien, pero sería mejor si @RefreshScope puede responderla. Este es un elemento adicional, pero de hecho es un poco difícil. ¿Qué tal 25,000?

Yo: (Pensando en silencio en mi corazón: 25.000 es solo una pregunta que no respondí bien. Recorté 5.000, que es un poco cruel. Quiero volver a estudiar de nuevo. 30.000 definitivamente no es problema), dije : El mínimo es 29.000

Entrevistador: Gracias, eso es todo por la entrevista de hoy, gire a la derecha cuando salga, ¡no lo envíe!

Tengo un buen hábito Después de cada entrevista, realizaré una revisión y debo encontrar una manera de resolver los problemas que no se han resuelto, para que valga la pena.

Las preguntas de la entrevista son las siguientes

  1. Uso de @Valor

  2. Fuente de datos @Value

  3. Problema de actualización dinámica de @Value

Resolvamos uno por uno y resolvamos estos problemas, para que todos puedan pasar la entrevista durante la epidemia y obtener un salario alto.

Uso de @Valor

El sistema necesita conectarse a la base de datos y hay mucha información de configuración para conectarse a la base de datos.

El sistema necesita enviar correo, y el envío de correo necesita configurar la información del servidor de correo.

Hay otra información de configuración.

Podemos poner esta información de configuración en un archivo de configuración, que será modificado por operación y mantenimiento cuando esté en línea.

Entonces, cómo usar esta información de configuración en el sistema, Spring proporciona la anotación @Value para resolver este problema.

Por lo general, almacenamos información de configuración en el archivo de configuración de propiedades en forma de clave=valor.

Utilice @Value("${clave en el archivo de configuración}") para hacer referencia al valor correspondiente a la clave especificada.

@Value usando pasos

Paso 1: use la anotación @PropertySource para introducir archivos de configuración

Ponga @PropertySource en la clase, de la siguiente manera

@PropertySource({"配置文件路径1","配置文件路径2"...})

La anotación @PropertySource tiene un atributo de valor, un tipo de matriz de cadenas, que se puede usar para especificar la ruta de varios archivos de configuración.

como:

@Component
@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
public class DbConfig {
}

Paso 2: use la anotación @Value para hacer referencia al valor del archivo de configuración

Haga referencia al valor en el archivo de configuración anterior a través de @Value:

gramática

@Value("${配置文件中的key:默认值}")
@Value("${配置文件中的key}")

como:

@Value("${password:123}")

Si la contraseña no existe arriba, se utilizará 123 como valor

@Value("${password}")

Si la contraseña no existe arriba, el valor es ${contraseña}

Si el archivo de configuración es el siguiente

jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
jdbc.username=javacode
jdbc.password=javacode

El uso es el siguiente:

@Value("${jdbc.url}")
private String url;

@Value("${jdbc.username}")
private String username;

@Value("${jdbc.password}")
private String password;

Veamos el caso

el caso

Vaya a un archivo de configuración db.properties

jdbc.url=jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8
jdbc.username=javacode
jdbc.password=javacode

Venga a una clase de configuración, use @PropertySource para importar el archivo de configuración anterior

package com.javacode2018.lesson002.demo18.test1;

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;

@Configurable
@ComponentScan
@PropertySource({"classpath:com/javacode2018/lesson002/demo18/db.properties"})
public class MainConfig1 {
}

Ven a una clase y usa @Value para usar la información en el archivo de configuración

package com.javacode2018.lesson002.demo18.test1;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class DbConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "DbConfig{" +
                "url='" + url + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

El enfoque de lo anterior es anotar la anotación @Value, preste atención a la anotación @Value

Plantéate un caso de prueba

package com.javacode2018.lesson002.demo18;

import com.javacode2018.lesson002.demo18.test1.DbConfig;
import com.javacode2018.lesson002.demo18.test1.MainConfig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ValueTest {

    @Test
    public void test1() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig1.class);
        context.refresh();

        DbConfig dbConfig = context.getBean(DbConfig.class);
        System.out.println(dbConfig);
    }
}

ejecutar salida

DbConfig{url='jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8', username='javacode', password='javacode'}

Lo anterior es relativamente simple de usar, y muchas personas que lo han usado pueden entenderlo de un vistazo. Esta es también la primera pregunta, y la mayoría de las personas están de acuerdo. Veamos si hay otras formas de obtener los datos en @Value además el archivo de configuración. .

Fuente de datos @Value

Por lo general, los datos de nuestro @Value provienen del archivo de configuración, pero también se pueden usar otros métodos, por ejemplo, podemos poner el contenido del archivo de configuración en la base de datos, lo que facilita su modificación.

Necesitamos entender de dónde provienen los datos en @Value en primavera.

hay una clase en primavera

org.springframework.core.env.PropertySource

Se puede entender como una fuente de configuración, que contiene información de configuración de clave->valor, y la información de valor correspondiente a la clave se puede obtener a través del método proporcionado en esta clase.

Hay un método dentro:

public abstract Object getProperty(String name);

Obtenga la información de configuración correspondiente por nombre.

El sistema tiene una interfaz más importante.

org.springframework.core.env.Environment

Utilizada para representar la información de configuración del entorno, esta interfaz tiene varios métodos importantes

String resolvePlaceholders(String text);
MutablePropertySources getPropertySources();

resolvePlaceholders se utilizan para el análisis ${text}y la anotación @Value finalmente se llama para el análisis.

getPropertySources devuelve el objeto MutablePropertySources, eche un vistazo a esta clase

public class MutablePropertySources implements PropertySources {

    private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();

}

Dentro contiene una propertySourceListlista.

EnvironmentHabrá un objeto en el contenedor de primavera y, finalmente resolvePlaceholders, se llamará al método de este objeto para analizar @Value.

Puede echar un vistazo al proceso de analizar finalmente @Value:

1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
2. Environment内部会访问MutablePropertySources来解析
3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值

A través del proceso anterior, si queremos cambiar la fuente de los datos de @Value, solo necesitamos envolver la información de configuración en un objeto PropertySource y colocarlo dentro de MutablePropertySources en el entorno.

Sigamos esta línea de pensamiento.

Asista a una clase de información de configuración de correo electrónico, use internamente @Value para inyectar información de configuración de correo electrónico

package com.javacode2018.lesson002.demo18.test2;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 邮件配置信息
 */
@Component
public class MailConfig {

    @Value("${mail.host}")
    private String host;

    @Value("${mail.username}")
    private String username;

    @Value("${mail.password}")
    private String password;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "MailConfig{" +
                "host='" + host + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

Otra clase DbUtil, getMailInfoFromDbel método simula obtener la información de configuración de correo de la base de datos y almacenarla en el mapa

package com.javacode2018.lesson002.demo18.test2;

import java.util.HashMap;
import java.util.Map;

public class DbUtil {
    /**
     * 模拟从db中获取邮件配置信息
     *
     * @return
     */
    public static Map<String, Object> getMailInfoFromDb() {
        Map<String, Object> result = new HashMap<>();
        result.put("mail.host", "smtp.qq.com");
        result.put("mail.username", "路人");
        result.put("mail.password", "123");
        return result;
    }
}

Ven a una clase de configuración de resortes

package com.javacode2018.lesson002.demo18.test2;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class MainConfig2 {
}

El siguiente es el código clave

@Test
public void test2() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

    /*下面这段是关键 start*/
    //模拟从db中获取配置信息
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    /*上面这段是关键 end*/

    context.register(MainConfig2.class);
    context.refresh();
    MailConfig mailConfig = context.getBean(MailConfig.class);
    System.out.println(mailConfig);
}

Los comentarios son más detallados, así que no los explicaré en detalle.

Ejecútalo directamente para ver el efecto.

MailConfig{host='smtp.qq.com', username='路人', password='123'}

¿Te sientes bien? En este momento, puedes modificarlo a voluntad DbUtil.getMailInfoFromDb. Los datos específicos provienen de db, y cuando provienen de redis u otros medios, deja que todos jueguen.

El punto clave anterior es el siguiente código, debe comprender

/*下面这段是关键 start*/
//模拟从db中获取配置信息
Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
//将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
//将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
/*上面这段是关键 end*/

Pasemos a la siguiente pregunta.

Si ponemos la información de configuración en la base de datos, es posible que modifiquemos la información de configuración a través de una interfaz, y después de guardar, esperamos que estos valores surtan efecto inmediatamente en el contenedor de primavera sin reiniciar el sistema.

El problema de la actualización dinámica de @Value se implementa usando @RefreshScope en springboot.

Realice una actualización dinámica de @Value

Conoce un punto primero

Esta pieza necesita hablar sobre un punto de conocimiento primero, y no se usa demasiado, por lo que muchas personas probablemente no lo entiendan, pero es un punto muy importante, echemos un vistazo.

Este punto de conocimiento es 自定义bean作用域, si no entiende este artículo, lea este artículo primero: Explicación detallada del alcance del frijol

Hay un lugar en el alcance del bean que no se menciona. Echemos un vistazo al código fuente de la anotación @Scope. Hay un parámetro:

ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

El valor de este parámetro es una enumeración del tipo ScopedProxyMode, y el valor tiene los siguientes 4

public enum ScopedProxyMode {
    DEFAULT,
    NO,
    INTERFACES,
    TARGET_CLASS;
}

No hablemos de los primeros tres, solo hablemos de para qué sirve el último valor.

Cuando el proxyMode en @Scope es TARGET_CLASS, se generará un objeto de proxy para el bean creado actualmente a través de cglib, y se accederá al objeto de bean de destino a través de este objeto de proxy.

Es más complicado de entender, veamos el código, es más fácil de entender, tomemos un caso de Scope personalizado.

Personalizar una anotación con alcance de bean

package com.javacode2018.lesson002.demo18.test3;

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(BeanMyScope.SCOPE_MY) //@1
public @interface MyScope {
    /**
     * @see Scope#proxyMode()
     */
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;//@2
}

@1: se usa la anotación @Scope, el valor se refiere a una constante y el valor es my, como puede ver a continuación.

@2: preste atención a este lugar, el nombre del parámetro también es proxyMode, y el tipo es ScopedProxyMode, y hay un parámetro del mismo tipo en la anotación @Scope. Cuando el contenedor de primavera analiza, el valor de este parámetro será asignado a la anotación @MyScope arriba @ El parámetro proxyMode de la anotación Scope, así que aquí establecemos el valor proxyMode, y el efecto final es cambiar directamente el valor del parámetro proxyMode en @Scope. El valor predeterminado aquí es ScopedProxyMode.TARGET_CLASS

La implementación de Scope correspondiente a la anotación @MyScope es la siguiente

package com.javacode2018.lesson002.demo18.test3;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.lang.Nullable;

/**
 * @see MyScope 作用域的实现
 */
public class BeanMyScope implements Scope {

    public static final String SCOPE_MY = "my"; //@1

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) { 
        System.out.println("BeanMyScope >>>>>>>>> get:" + name); //@2
        return objectFactory.getObject(); //@3
    }

    @Nullable
    @Override
    public Object remove(String name) {
        return null;
    }

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

    }

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

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

@1: una constante se define como el valor del alcance

@2: Este método get es la clave. El alcance personalizado llamará automáticamente a este método get para crear un objeto bean. Este lugar genera una línea de registros para la conveniencia de ver el efecto más tarde.

@3: Obtenga la devolución de la instancia del bean a través de objectFactory.getObject().

Vamos a crear una clase con el ámbito definido anteriormente

package com.javacode2018.lesson002.demo18.test3;

import org.springframework.stereotype.Component;

import java.util.UUID;

@Component
@MyScope //@1 
public class User {

    private String username;

    public User() { 
        System.out.println("---------创建User对象" + this); //@2
        this.username = UUID.randomUUID().toString(); //@3
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

}

@1: Uso de un alcance personalizado @MyScope

@2: genera una línea de registro en el constructor

@3: Asigne un valor al nombre de usuario y genere uno aleatoriamente a través de uuid

Venga a una clase de configuración de resortes para cargar los componentes marcados por @Component arriba

package com.javacode2018.lesson002.demo18.test3;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ComponentScan
@Configuration
public class MainConfig3 {
}

Aquí viene el punto clave, el caso de prueba

@Test
public void test3() throws InterruptedException {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //将自定义作用域注册到spring容器中
    context.getBeanFactory().registerScope(BeanMyScope.SCOPE_MY, new BeanMyScope());//@1
    context.register(MainConfig3.class);
    context.refresh();

    System.out.println("从容器中获取User对象");
    User user = context.getBean(User.class); //@2
    System.out.println("user对象的class为:" + user.getClass()); //@3

    System.out.println("多次调用user的getUsername感受一下效果\n");
    for (int i = 1; i <= 3; i++) {
        System.out.println(String.format("********\n第%d次开始调用getUsername", i));
        System.out.println(user.getUsername());
        System.out.println(String.format("第%d次调用getUsername结束\n********\n", i));
    }
}

@1: Registre el alcance personalizado en el contenedor de primavera

@2: obtener el bean correspondiente al usuario del contenedor

@ 3: genera la clase correspondiente a este bean y observa más de cerca más tarde para ver si este tipo es el tipo de usuario

Después del código, se realizan otros 3 bucles, se llama al método getUsername del usuario y se genera una línea de registros antes y después del método.

Es hora de presenciar el milagro, ejecutar la salida.

从容器中获取User对象
user对象的class为:class com.javacode2018.lesson002.demo18.test3.User$$EnhancerBySpringCGLIB$$80233127
多次调用user的getUsername感受一下效果

********
第1次开始调用getUsername
BeanMyScope >>>>>>>>> get:scopedTarget.user
---------创建User对象com.javacode2018.lesson002.demo18.test3.User@6a370f4
7b41aa80-7569-4072-9d40-ec9bfb92f438
第1次调用getUsername结束
********

********
第2次开始调用getUsername
BeanMyScope >>>>>>>>> get:scopedTarget.user
---------创建User对象com.javacode2018.lesson002.demo18.test3.User@1613674b
01d67154-95f6-44bb-93ab-05a34abdf51f
第2次调用getUsername结束
********

********
第3次开始调用getUsername
BeanMyScope >>>>>>>>> get:scopedTarget.user
---------创建User对象com.javacode2018.lesson002.demo18.test3.User@27ff5d15
76d0e86f-8331-4303-aac7-4acce0b258b8
第3次调用getUsername结束
********

Como se puede ver en las primeras 2 líneas de salida:

  1. Al llamar a context.getBean(User.class) para obtener el bean del contenedor, no se llama al constructor User para crear el objeto User

  2. Puede verse en el tipo de salida de la segunda línea que el objeto de usuario devuelto por getBean es un objeto proxy cglib.

Se puede ver en la siguiente salida de registro que cada vez que se llama al método user.getUsername, el método BeanMyScope#get y el constructor User se llaman automáticamente internamente.

Como puede verse en el caso anterior, cuando proxyMode=ScopedProxyMode.TARGET_CLASS en el ámbito personalizado, se creará un objeto proxy para este bean, y cualquier método para llamar al objeto proxy llamará a esta clase de implementación de ámbito personalizado (por encima de BeanMyScope) en el método get para volver a adquirir el objeto bean.

Actualización dinámica @Value implementación específica

Luego, podemos usar la función explicada anteriormente para realizar la actualización dinámica de @Value. Podemos implementar un Scope personalizado. Este Scope personalizado admite la actualización automática de la anotación @Value. Se puede marcar en la clase que debe actualizarse automáticamente con la anotación @Value Esta anotación personalizada, cuando se modifica la configuración, al llamar a cualquier método de estos beans, permita que Spring reinicie e inicialice el bean, esta idea se puede realizar, escribamos el código a continuación.

Primero definamos un alcance: RefreshScope

package com.javacode2018.lesson002.demo18.test4;

import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope(BeanRefreshScope.SCOPE_REFRESH)
@Documented
public @interface RefreshScope {
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; //@1
}

Las clases anotadas con @RefreshScope son necesarias para admitir la actualización dinámica de la configuración de @Value

@1: Este lugar es la clave, usando ScopedProxyMode.TARGET_CLASS

La clase de análisis correspondiente a este Ámbito personalizado

Hay varios métodos irrelevantes en las siguientes clases que se han eliminado y se pueden ignorar

package com.javacode2018.lesson002.demo18.test4;


import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.lang.Nullable;

import java.util.concurrent.ConcurrentHashMap;

public class BeanRefreshScope implements Scope {

    public static final String SCOPE_REFRESH = "refresh";

    private static final BeanRefreshScope INSTANCE = new BeanRefreshScope();

    //来个map用来缓存bean
    private ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>(); //@1

    private BeanRefreshScope() {
    }

    public static BeanRefreshScope getInstance() {
        return INSTANCE;
    }

    /**
     * 清理当前
     */
    public static void clean() {
        INSTANCE.beanMap.clear();
    }

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Object bean = beanMap.get(name);
        if (bean == null) {
            bean = objectFactory.getObject();
            beanMap.put(name, bean);
        }
        return bean;
    }

}

El método get anterior lo obtendrá primero del beanMap, si no se obtiene, llamará al getObject de objectFactory para permitir que Spring cree una instancia del bean y luego lo arroje al beanMap.

El método de limpieza anterior se usa para limpiar todos los beans actualmente almacenados en caché en el beanMap

Venga a una clase de configuración de correo, use la anotación @Value para inyectar la configuración, el alcance de este bean es el @RefreshScope personalizado

package com.javacode2018.lesson002.demo18.test4;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 邮件配置信息
 */
@Component
@RefreshScope //@1
public class MailConfig {

    @Value("${mail.username}") //@2
    private String username;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String toString() {
        return "MailConfig{" +
                "username='" + username + '\'' +
                '}';
    }
}

@1: Uso de un alcance personalizado @RefreshScope

@2: Inyecte mail.username a un valor a través de @Value

El método toString se ha reescrito y el efecto se puede ver cuando se prueba durante un tiempo.

Otro bean común, que inyectará MailConfig internamente

package com.javacode2018.lesson002.demo18.test4;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MailService {
    @Autowired
    private MailConfig mailConfig;

    @Override
    public String toString() {
        return "MailService{" +
                "mailConfig=" + mailConfig +
                '}';
    }
}

El código es relativamente simple, el método toString se ha reescrito y el efecto se puede ver en una prueba más adelante.

Ven a una clase para obtener información de configuración de correo de db

package com.javacode2018.lesson002.demo18.test4;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class DbUtil {
    /**
     * 模拟从db中获取邮件配置信息
     *
     * @return
     */
    public static Map<String, Object> getMailInfoFromDb() {
        Map<String, Object> result = new HashMap<>();
        result.put("mail.username", UUID.randomUUID().toString());
        return result;
    }
}

Venga a una clase de configuración de resortes, escanee y cargue los componentes anteriores

package com.javacode2018.lesson002.demo18.test4;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class MainConfig4 {
}

ven a herramientas

Hay 2 métodos en el interior, de la siguiente manera:

package com.javacode2018.lesson002.demo18.test4;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.env.MapPropertySource;

import java.util.Map;

public class RefreshConfigUtil {
    /**
     * 模拟改变数据库中都配置信息
     */
    public static void updateDbConfig(AbstractApplicationContext context) {
        //更新context中的mailPropertySource配置信息
        refreshMailPropertySource(context);

        //清空BeanRefreshScope中所有bean的缓存
        BeanRefreshScope.getInstance().clean();
    }

    public static void refreshMailPropertySource(AbstractApplicationContext context) {
        Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
        //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
        MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
        context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    }

}

El método updateDbConfig simula el método que debe llamarse al modificar la configuración en la base de datos. Hay 2 líneas de código en el método, y la primera línea de código llama al método refreshMailPropertySource para modificar la información de configuración del correo en el contenedor. .

BeanRefreshScope.getInstance().clean() se usa para borrar todos los beans almacenados en caché en BeanRefreshScope, luego, al llamar a cualquier método del bean, el contenedor de primavera se volverá a crear para crear el bean, y cuando el contenedor de primavera vuelva a crear el bean, volverá a analizar la información @ Value. En este momento, la información de configuración de correo en el contenedor es nueva, por lo que la información inyectada por @Value también es nueva.

Plantéate un caso de prueba

@Test
public void test4() throws InterruptedException {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.getBeanFactory().registerScope(BeanRefreshScope.SCOPE_REFRESH, BeanRefreshScope.getInstance());
    context.register(MainConfig4.class);
    //刷新mail的配置到Environment
    RefreshConfigUtil.refreshMailPropertySource(context);
    context.refresh();

    MailService mailService = context.getBean(MailService.class);
    System.out.println("配置未更新的情况下,输出3次");
    for (int i = 0; i < 3; i++) { //@1
        System.out.println(mailService);
        TimeUnit.MILLISECONDS.sleep(200);
    }

    System.out.println("模拟3次更新配置效果");
    for (int i = 0; i < 3; i++) { //@2
        RefreshConfigUtil.updateDbConfig(context); //@3
        System.out.println(mailService);
        TimeUnit.MILLISECONDS.sleep(200);
    }
}

@ 1: bucle 3 veces, información de servicio de correo de salida

@2: Bucle 3 veces, use internamente @3 para simular la actualización de la información de configuración en la base de datos y luego envíe la información del servicio de correo

Sea testigo del momento del milagro y vea el efecto.

配置未更新的情况下,输出3次
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
MailService{mailConfig=MailConfig{username='df321543-8ca7-4563-993a-bd64cbf50d53'}}
模拟3次更新配置效果
MailService{mailConfig=MailConfig{username='6bab8cea-9f4f-497d-a23a-92f15d0d6e34'}}
MailService{mailConfig=MailConfig{username='581bf395-f6b8-4b87-84e6-83d3c7342ca2'}}
MailService{mailConfig=MailConfig{username='db337f54-20b0-4726-9e55-328530af6999'}}

El servicio de correo anterior tiene salida 6 veces, el valor de nombre de usuario es el mismo las primeras 3 veces y el valor de nombre de usuario es diferente las siguientes 3 veces, lo que indica que la configuración modificada ha tenido efecto.

resumen

La clave para realizar @Value dinámico es el parámetro proxyMode en @Scope, cuyo valor es ScopedProxyMode.DEFAULT, que generará un proxy a través del cual lograr el efecto de actualización dinámica de @Value.Este lugar es la clave.

Si está interesado, puede echar un vistazo al código fuente de la anotación @RefreshScope en springboot, que es similar al @RefreshScope que personalizamos anteriormente, y el principio de implementación es similar.

Supongo que te gusta

Origin blog.csdn.net/weixin_46228112/article/details/124623568
Recomendado
Clasificación