Una revisión de 30.000 palabras de las 9 funciones básicas principales de Spring

imagen

Recordatorio amistoso, este artículo es demasiado largo, se recomienda guardarlo, ¡jejeje!

Administracion de recursos

La gestión de recursos es una función básica central de Spring, pero antes de hablar sobre la gestión de recursos de Spring, hablemos brevemente sobre la gestión de recursos en Java.

gestión de recursos java

La gestión de recursos en Java se java.net.URLimplementa principalmente a través del openConnectionmétodo URL: se puede abrir una conexión al recurso y el contenido del recurso se puede leer a través de esta conexión.

Los recursos no solo se refieren a recursos de red, sino también a archivos locales, un paquete jar, etc.

1. Hagamos una demostración

Por ejemplo, si desea acceder www.baidu.coma este recurso de red de la página de inicio de Baidu, puede escribir así

public class JavaResourceDemo {

    public static void main(String[] args) throws IOException {
        //构建URL 指定资源的协议为http协议
        URL url = new URL("http://www.baidu.com");
        //打开资源连接
        URLConnection urlConnection = url.openConnection();
        //获取资源输入流
        InputStream inputStream = urlConnection.getInputStream();
        //通过hutool工具类读取流中数据
        String content = IoUtil.read(new InputStreamReader(inputStream));
        System.out.println(content);
    }
}

Explique lo que significa el código anterior:

  • Primero, cree una URL y especifique el protocolo de acceso del recurso como protocolo http.

  • Abra una conexión de acceso a recursos a través de la URL, luego obtenga un flujo de entrada y lea el contenido.

resultado de la operación

imagen

Lea con éxito los datos de la página de inicio de Baidu.

Por supuesto, también puede acceder a los recursos de archivos locales a través de URL. Al crear la URL, solo necesita especificar el tipo de protocolo y file://la ruta del archivo.

URL url = new URL("file://" + "文件的路径");

No demostraré este método aquí.

De hecho, este método finalmente FileInputStreamlee los datos del archivo. Si no me cree, puede intentar depurarlo usted mismo.

2. Principio

El recurso URL de cada protocolo requiere un URLStreamHandler correspondiente para manejarlo.

URLStreamHandler

Por ejemplo, http://el protocolo tiene una implementación URLStreamHandler correspondiente y file://el protocolo tiene una implementación URLStreamHandler correspondiente.

http://Además de admitir el protocolo y , Java file://también admite otros protocolos, como se muestra en la siguiente figura:

imagen

Para URLStreamHandler como se muestra a continuación

imagen

Al crear una URL, se analizará el protocolo de acceso del recurso y se encontrará la implementación de URLStreamHandler correspondiente en función del protocolo de acceso.

Por supuesto, además de los protocolos soportados por el propio Java, también podemos extender este protocolo nosotros mismos, lo que generalmente solo requiere dos pasos:

  • Implemente URLConnection, a través del cual se puede leer el contenido del recurso.

  • Implemente URLStreamHandler y obtenga URLConnection a través de URLStreamHandler

Sin embargo, cabe señalar que la implementación de URLStreamHandler debe colocarse sun.net.www.protocol.协议名称debajo del paquete y el nombre de la clase debe ser Handler. Es por eso que los nombres de las clases de implementación en las capturas de pantalla se llaman Handler.

Por supuesto, está bien si no se coloca en el paquete especificado, pero java.net.URLStreamHandlerFactoryes necesario implementar la interfaz.

No haré una demostración de la extensión. Si está interesado, puede buscarla en Google usted mismo.

Gestión de recursos de primavera.

Aunque Java proporciona un método de gestión de recursos estándar, Spring no lo utiliza, sino que desarrolla su propio método de gestión de recursos.

1. Abstracción de recursos

En Spring, los recursos se abstraen aproximadamente en dos interfaces

  • Recurso: recurso legible, que puede obtener el flujo de entrada del recurso.

  • WritableResource: lee y escribe recursos. Además del flujo de entrada de recursos, también puede obtener el flujo de salida de recursos.

Recurso

imagen

La interfaz Resource hereda la interfaz InputStreamSource, y la interfaz InputStreamSource puede obtener y definir métodos para obtener flujos de entrada.

imagen

recurso grabable

imagen

WritableResourceAl heredar Resourcela interfaz, puede obtener el flujo de salida del recurso, porque algunos recursos no solo se pueden leer sino también escribir, por ejemplo, algunos recursos de archivos locales a menudo se pueden leer y escribir.

ResourceHay muchas implementaciones, aquí enumeraré algunas más comunes:

  • FileSystemResource: leer los recursos del sistema de archivos

  • UrlResource: la encapsulación de la gestión de recursos estándar de Java mencionada anteriormente. La capa inferior es para acceder a los recursos a través de URL.

  • ClassPathResource: lee recursos en la ruta de clase

  • ByteArrayResource: lee los datos de la matriz de bytes estática

Por ejemplo, si desea acceder a los recursos de red de la página de inicio de Baidu mencionados anteriormente a través del método de administración de recursos de Spring, puede escribir así

//构建资源
Resource resource = new UrlResource("http://www.baidu.com");
//获取资源输入流
InputStream inputStream = resource.getInputStream();

Si es un recurso de archivo local, además de usar UrlResource, también puede usar FileSystemResource.

2. Carga de recursos

Aunque Resourcehay muchas implementaciones, en el uso real, es posible que no sea posible determinar qué implementación específica usar, por lo que Spring proporciona un ResourceLoadercargador de recursos para cargar recursos según su tipo.

imagen

Cargador de recursos

A través getResourcedel método, el recurso correspondiente se puede cargar pasando una ruta, y esta ruta no tiene por qué ser un archivo local, pero puede ser cualquier ruta cargable.

ResourceLoaderHay una implementación única.DefaultResourceLoader

imagen

Por ejemplo, para el ejemplo anterior, puede ResourceLoadercargar recursos en lugar de implementar directamente nuevos.

//创建ResourceLoader
ResourceLoader resourceLoader = new DefaultResourceLoader();
//获取资源
Resource resource = resourceLoader.getResource("http://www.baidu.com");

Además ResourceLoader, también hay un ResourcePatternResolverrecurso que se puede cargar.

imagen

ResourcePatternResolverheredarResourceLoader

Se puede ver en ResourcePatternResolverlos métodos proporcionados que puede cargar múltiples recursos y admite el uso de comodines, por ejemplo classpath*:, puede cargar todos los recursos de classpath.

ResourcePatternResolverSolo hay una implementación.PathMatchingResourcePatternResolver

imagen

3. Resumen

Con esto concluye la discusión sobre la gestión de recursos de Spring. Aquí hay un resumen del contenido general de esta sección.

Gestión de recursos estándar en Java:

  • URL

  • URLStreamHandler

Gestión de recursos de primavera:

  • Abstracción de recursos: recurso, recurso grabable

  • Carga de recursos: ResourceLoader, ResourcePatternResolver

La administración de recursos de Spring se usa mucho en Spring, por ejemplo, en Spring Boot, application.ymllos archivos se cargan en Recursos a través de ResourceLoader y luego se lee el contenido de los archivos.

imagen

ambiente

Como se menciona en el ejemplo al final de la sección anterior, el archivo de configuración SpringBoot carga el archivo de configuración a través de ResourceLoader y lee el contenido de configuración del archivo.

Entonces, una vez cargados los archivos de configuración, ¿dónde se debe almacenar esta configuración y cómo se puede leer?

Esto lleva a un concepto clave en el marco Spring, el entorno, que en realidad se utiliza para gestionar la configuración de la aplicación.

1 、 Medio ambiente

El entorno es una interfaz abstraída del entorno.

imagen

El entorno hereda PropertyResolver

public interface PropertyResolver {

    boolean containsProperty(String key);

    String getProperty(String key);

    <T> T getProperty(String key, Class<T> targetType);

    <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

    String resolvePlaceholders(String text);

}

Los anteriores son algunos de los métodos proporcionados por PropertyResolver. Aquí hay una breve introducción a las funciones de los métodos anteriores.

  • getProperty(String key), obviamente el valor correspondiente se obtiene a través de la clave configurada

  • getProperty(String key, Class<T> targetType), esto es para obtener la configuración y convertirla al tipo correspondiente. Por ejemplo, si obtiene una cadena "true", puede convertirla en un valor booleano aquí true. La implementación subyacente específica se deja para la siguiente sección.

  • resolvePlaceholders(String text), este tipo de método puede manejar ${...}marcadores de posición, es decir, primero sacar ${...}la clave en el marcador de posición y luego obtener el valor a través de la clave.

Entonces el Medio Ambiente tiene principalmente las siguientes funciones:

  • Obtener configuración basada en clave

  • Obtener el tipo de configuración especificado

  • Manejo de marcadores de posición

Hagamos una demostración

Primero application.ymlagregue la configuración al archivo de configuración.

imagen

El código de prueba es el siguiente.

@SpringBootApplication
public class EnvironmentDemo {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(EnvironmentDemo.class, args);

        //从ApplicationContext中获取到ConfigurableEnvironment
        ConfigurableEnvironment environment = applicationContext.getEnvironment();

        //获取name属性对应的值
        String name = environment.getProperty("name");
        System.out.println("name = " + name);
    }

}

Inicie la aplicación, obtenga el objeto ConfigurableEnvironment y luego obtenga el valor

ConfigurableEnvironment es una subinterfaz del Entorno, también puedes conocerla nombrándola y puede configurar algunas funciones del Entorno.

resultado de la operación:

name = 三友的java日记

2. Configurar la propiedad sourcePropertySource

PropertySource es el lugar donde realmente se almacena la configuración y es la fuente de la configuración. Proporciona una interfaz de acceso unificada para que las aplicaciones puedan obtener la configuración y las propiedades de forma unificada.

imagen

Fuente de propiedad

Hagamos una demostración sencilla.

public class PropertySourceDemo {

    public static void main(String[] args) {

        Map<String, Object> source = new HashMap<>();
        source.put("name", "三友的java日记");

        PropertySource<Map<String, Object>> propertySource = new MapPropertySource("myPropertySource", source);

        Object name = propertySource.getProperty("name");

        System.out.println("name = " + name);
    }

}

Explique brevemente el significado del código anterior.

  • Primero, cree un mapa, que es la fuente de configuración, y agréguele un valor-clave de configuración.

  • Se crea un PropertySource y la implementación utilizada es MapPropertySource, es necesario pasar el mapa de configuración, de modo que cuando finalmente se obtengan las propiedades, sea fácil saber que se obtuvieron del mapa sin siquiera pensar en ello.

Finalmente se obtienen los atributos

imagen

Además de MapPropertySource, existen muchas implementaciones.

imagen

Implementación de PropertySource

Por ejemplo, CommandLinePropertySource en realidad encapsula los parámetros de configuración pasados ​​al iniciar el comando.

Dado que PropertySource es donde realmente se almacena la configuración, la configuración obtenida por Environment en realidad se obtiene de PropertySource y, en realidad, son una relación de uno a muchos.

imagen

De hecho, es fácil entender la relación de uno a muchos, porque la configuración de una aplicación puede provenir de muchos lugares. Por ejemplo, en el entorno SpringBoot, además de nuestra configuración personalizada, también existen configuraciones del entorno del sistema. etc., que se pueden obtener a través de Medio Ambiente llegar

Al obtener la configuración del Entorno se recorrerán todos los PropertySources, una vez encontrado el valor correspondiente a la clave de configuración se devolverá.

Por lo tanto, si hay varios PropertySources que contienen el mismo elemento de configuración, es decir, la clave de configuración es la misma, entonces la configuración obtenida se obtiene del PropertySource clasificado en primer lugar.

Es por eso que, cuando configura el atributo de nombre de usuario en el archivo de configuración, lo que obtiene es el valor correspondiente a la variable del sistema nombre de usuario, porque el PropertySource del sistema se clasifica antes que el PropertySource correspondiente al archivo de configuración.

3. Cómo SpringBoot analiza los archivos de configuración

SpringBoot analiza archivos de configuración a través de PropertySourceLoader

imagen

El segundo parámetro del método de carga es el recurso de interfaz que mencionamos anteriormente.

El flujo de entrada del archivo de configuración se puede obtener a través de Recursos, y luego se puede leer el contenido del archivo de configuración, y luego el archivo de configuración se puede analizar en múltiples PropertySources, y luego PropertySource se puede colocar en el Entorno, de modo que Podemos obtenerlo a través del Entorno, el contenido del archivo de configuración.

PropertySourceLoader tiene dos implementaciones de forma predeterminada, que se utilizan para analizar propertiesy ymlformatear archivos de configuración.

imagen

En este punto, la imagen de arriba se puede optimizar así

imagen

conversión de tipo

Al presentar el Medio Ambiente en la sección anterior, mencionamos que getProperty(String key, Class<T> targetType)puede convertir la cadena configurada al tipo correspondiente, entonces, ¿cómo la convierte?

Esto está relacionado con el mecanismo de conversión de tipo Spring del que hablará este artículo.

1. API de conversión de tipos

La conversión de tipo Spring implica principalmente las siguientes API:

  • Editor de propiedades

  • Convertidor

  • Convertidor genérico

  • Servicio de conversión

  • Convertidor de tipos

A continuación, presentaré en detalle los principios de estas API y la relación entre ellas.

1.1 、 Editor de propiedades

PropertyEditor no es una API proporcionada por Spring, sino una API proporcionada por JDK. Su función principal es convertir cadenas de tipo String en valores de propiedad de objetos Java.

public interface PropertyEditor {

    void setValue(Object value);

    Object getValue();

    String getAsText();

    void setAsText(String text) throws java.lang.IllegalArgumentException;
    
}

@ValueTomemos como ejemplo los de uso común en proyectos: cuando @Valueinyectamos configuración en campos a través de anotaciones, los pasos generales son los siguientes:

imagen

  • Obtener @Valuela clave configurada

  • Llame al método Environment según @Valuela clave configurada resolvePlaceholders(String text), analice el marcador de posición y busque el valor correspondiente en el archivo de configuración.

  • Llame a PropertyEditor para convertir el valor correspondiente al tipo de campo de propiedad inyectado. Por ejemplo, si el tipo de campo inyectado es un número, entonces la cadena se convertirá en un número.

Durante el proceso de conversión, Spring primero llamará al método setAsText del PropertyEditor para pasar la cadena y luego llamará al método getValue para obtener el valor convertido.

Spring proporciona muchas implementaciones de PropertyEditor que pueden convertir cadenas en varios tipos.

imagen

Entre tantas implementaciones, hay una relacionada con el recurso que mencionamos anteriormente ResourceEditor, que convierte cadenas en objetos de recurso.

imagen

Editor de recursos

En otras palabras, puede inyectar directamente un objeto Recurso a través de @Value, como se muestra a continuación

@Value("http://www.baidu.com")
private Resource resource;

De hecho, en el análisis final, la capa inferior también se carga a través de ResourceLoader, y esta conclusión permanece sin cambios.

Entonces, si desea saber qué tipos de campos admite @Value inyectar, simplemente mire la implementación de PropertyEditor. Por supuesto, si los campos integrados de Spring no cumplen con sus requisitos, puede implementar PropertyEditor usted mismo, como convertir String en Date. El tipo no es compatible con Spring.

1.2 、 Convertidor

Dado que PropertyEditor se limita a la conversión de cadenas, Spring proporciona una interfaz llamada Converter en versiones posteriores, que también se utiliza para la conversión de tipos y es más flexible y versátil que PropertyEditor.

imagen

Convertidor

El convertidor es una interfaz, el S genérico es el tipo de objeto que se está convirtiendo y el T genérico es el tipo que se debe convertir.

De manera similar, Spring también proporciona muchas implementaciones de convertidores.

imagen

Estos incluyen principalmente la conversión de tipo de fecha y la conversión de tipo de cadena a otros tipos.

1.3、Convertidor Genérico

GenericConverter también es una interfaz para conversión de tipos.

imagen

La función principal de esta interfaz es manejar conversiones con tipos genéricos, principalmente para operaciones de conversión de matrices de colecciones, como se puede ver en la implementación predeterminada proporcionada por Spring.

imagen

Entonces, ¿cuál es la relación entre Converter y GenericConverter?

Aquí daré un ejemplo, suponiendo que ahora necesitamos Collection<String>convertir la colección de origen en la colección de destino.Collection<Date>

imagen

Supongamos que hay un convertidor que convierte una cadena en un tipo de fecha, llamémoslo StringToDateConverter, entonces todo el proceso de conversión es el siguiente:

  • Primero encontrará CollectionToCollectionConverter, una implementación de GenericConverter. Como puede ver por el nombre, convierte una o más colecciones en otra colección.

  • Luego recorra la colección fuente Collection<String>y saque los elementos.

  • De acuerdo con la Fecha genérica de la colección de destino, busque StringToDateConverter, convierta la Cadena en Fecha y guarde la Fecha convertida en una nueva colección.

  • Devolver esta nueva colección, realizando así la conversión de colección en colección. 

De esto podemos ver que Converter y GenericConverter son en realidad dependencias.

1.4、Servicio de conversión

Para nuestros usuarios, ya sea Converter o GenericConverter, en realidad son conversiones de tipos y hay muchas implementaciones de conversión de tipos, por lo que Spring proporciona una interfaz de fachada ConversionService para facilitar nuestro uso de Converter o GenericConverter.

imagen

Servicio de conversión

Podemos realizar la conversión de tipos directamente a través de ConversionService sin enfrentarnos a un Converter o GenericConverter específico.

ConversionService tiene una implementación básica GenericConversionService

imagen

Servicio de conversión genérico

Al mismo tiempo, GenericConversionService también implementa la interfaz de ConverterRegistry.

imagen

ConverterRegistry proporciona métodos para agregar, eliminar, modificar y verificar Converter y GenericConverter.

imagen

RegistroConvertidor

De esta manera, puede agregar Converter o GenericConverter a ConversionService, porque la conversión finalmente se logra a través de Converter y GenericConverter.

Pero generalmente no usamos GenericConversionService directamente, sino que usamos DefaultConversionService o ApplicationConversionService (usado en el entorno SpringBoot)

Debido a que DefaultConversionService y ApplicationConversionService agregarán muchos de los convertidores y genéricos de Spring cuando se creen, no es necesario agregarlos manualmente.

1.5 、 Convertidor de tipos

TypeConverter es en realidad una interfaz de fachada que también define métodos de conversión.

imagen

Integra PropertyEditor y ConversionService para facilitarnos el uso de PropertyEditor y ConversionService al mismo tiempo.

El método convertIfNecessary llamará a PropertyEditor y ConversionService para la conversión de tipos. Vale la pena señalar que PropertyEditor se usa primero para la conversión. Si no se encuentra el PropertyEditor correspondiente, se usará ConversionService para la conversión.

TypeConverter tiene una implementación simple SimpleTypeConverter, aquí hay una demostración simple

public class TypeConverterDemo {

    public static void main(String[] args) {
        SimpleTypeConverter typeConverter = new SimpleTypeConverter();
        
        //设置ConversionService
        typeConverter.setConversionService(DefaultConversionService.getSharedInstance());

        //将字符串"true"转换成Boolean类型的true
        Boolean b = typeConverter.convertIfNecessary("true", Boolean.class);
        System.out.println("b = " + b);
    }

}

Cabe señalar aquí que ConversionService requiere que lo configuremos manualmente, pero PropertyEditor no, porque SimpleTypeConverter agregará la implementación de PropertyEditor de forma predeterminada.

resumen

Con esto terminamos hablando de varias API comunes para la conversión de tipos, aquí hay un breve resumen:

  • PropertyEditor: Cadena convertida al tipo de destino

  • Convertidor: utilizado para convertir un tipo en otro tipo

  • GenericConverter: utilizado para manejar conversiones genéricas, utilizado principalmente para colecciones

  • ConversionService: La interfaz Facade, Converter y GenericConverter se llamarán internamente

  • TypeConverter: La interfaz Facade, PropertyEditor y ConversionService se llamarán internamente

Haz un dibujo para resumir su relación.

imagen

Cuando di el ejemplo de @Value anteriormente, dije que la conversión de tipo se basa en PropertyEditor. De hecho, es solo la mitad de la historia. Debido a que la capa inferior en realidad se convierte en función de TypeConverter, por lo que la conversión de tipo @Value también puede usar la conversión de clase ConversionService, entonces así es como debería dibujarse realmente la imagen.

imagen

2. ¿Cómo se realiza la conversión de tipos en Environment?

Aquí volvemos al tema mencionado al principio, cómo se realiza la conversión de tipos en Environment, echemos un vistazo al sistema de interfaz de la clase Environment.

El entorno tiene una subinterfaz ConfigurableEnvironment, que también se mencionó anteriormente

Hereda la interfaz ConfigurablePropertyResolver.

imagen

Y ConfigurablePropertyResolver tiene un setConversionServicemétodo

imagen

De esto se puede ver que la capa inferior del Entorno en realidad implementa la conversión de tipos a través de ConversionService.

Esto en realidad causa un problema, porque ConversionService y PropertyEditor están en una relación paralela, lo que hará que Environment no pueda usar PropertyEditor para la conversión de tipos y también perderá algunas de las funciones de conversión de tipos proporcionadas por Spring. Por ejemplo, String no puede ser convertido a través de Environment en un objeto Resource porque Spring no implementa un convertidor que convierta String en Resource.

Por supuesto, usted mismo puede implementar un convertidor que convierta cadenas en recursos y luego agregarlo a ConversionService, y luego el entorno admitirá la conversión de cadenas en recursos.

el enlace de datos

Hablamos de la conversión de tipos en la sección anterior, y dado que mencionamos la conversión de tipos, debemos mencionar el enlace de datos, que son inseparables, porque el enlace de datos a menudo va acompañado de la conversión de tipos.

La vinculación de datos significa vincular algunas propiedades de configuración a las propiedades de nuestro objeto Bean.

Me pregunto si recuerdas que en la antigua era SSM, cuando generalmente declarábamos Beans a través de xml, podíamos <property/>establecer las propiedades del Bean a través de

<bean class="com.sanyou.spring.core.basic.User">
    <property name="username" value="三友的java日记"/>
</bean>
@Data
public class User {

    private String username;

}

usernameLuego Spring establecerá la propiedad durante el proceso de creación del Usuario 三友的java日记.

Este es un enlace de datos, que estará 三友的java日记vinculado al atributo de nombre de usuario.

Las API principales del enlace de datos incluyen principalmente las siguientes:

  • Valores de propiedad

  • Envoltorio de frijol

  • Carpeta de datos

1、Valores de propiedad

Aquí primero hablamos de PropertyValue (tenga en cuenta que no hay s)

imagen

Gu Ming pensó que PropertyValue encapsula el nombre de la propiedad y el valor de propiedad correspondiente y es la fuente del valor de la propiedad durante el enlace de datos.

Tomando como ejemplo el Bean de creación xml mencionado anteriormente, Spring analizará las etiquetas en el xml cuando se inicie <property/>y luego encapsulará namey valueen PropertyValue.

Cuando se crea el bean Usuario, cuando se alcanza la etapa de enlace de propiedad, el PropertyValue se eliminará y se establecerá en el atributo de nombre de usuario del Usuario.

PropertyValues ​​​​tiene uno más que PropertyValue, lo que significa plural, por lo que, de hecho, PropertyValues ​​​​es esencialmente una colección de PropertyValues.

imagen

Debido a que un Bean puede tener múltiples configuraciones de propiedades, se utiliza PropertyValues ​​para guardarlas.

2、Envoltorio de frijol

BeanWrapper es en realidad la API central del enlace de datos, porque el enlace de datos en Spring se realiza a través de BeanWrapper. Por ejemplo, el enlace de las propiedades de Bean mencionadas anteriormente se realiza a través de BeanWrapper.

BeanWrapper es una interfaz, tiene una implementación única BeanWrapperImpl.

imagen

Primero hagamos una demostración.

public class BeanWrapperDemo {

    public static void main(String[] args) {
        //创建user对象
        User user = new User();

        //创建BeanWrapper对象,把需要进行属性绑定的user对象放进去
        BeanWrapper beanWrapper = new BeanWrapperImpl(user);

        //进行数据绑定,将三友的java日记这个属性值赋值到username这个属性上
        beanWrapper.setPropertyValue(new PropertyValue("username", "三友的java日记"));

        System.out.println("username = " + user.getUsername());
    }

}

resultado

imagen

Obtenido con éxito, lo que indica una configuración exitosa

BeanWrapperImpl también implementa indirectamente la interfaz TypeConverter

imagen

Por supuesto, la capa inferior todavía se implementa a través de ConversionService y PropertyEditor mencionados anteriormente.

Entonces, cuando el tipo de configuración es diferente del tipo de atributo, el tipo de configuración se puede convertir y luego vincular al atributo.

Aquí hay una breve charla sobre las similitudes y diferencias entre el enlace de datos y @Value, porque los dos parecen ser iguales, pero en realidad hay algunas diferencias.

Mismo punto:

  • Ambos implican conversión de tipo: @Value y el enlace de datos convertirán el valor al tipo correspondiente al atributo de destino, y ambos se convierten a través de TypeConverter.

diferencia:

  • 1. El momento de aparición es diferente. @Value es anterior al enlace de datos. El enlace de datos (asignación de propiedad) no se producirá hasta que se inyecte @Value.

  • 2. Los métodos de asignación de atributos son diferentes: @Value se obtiene a través de la reflexión, pero el enlace de datos se realiza a través del método setter. Si no hay un método setter, el atributo no se puede vincular.

3、Encuadernador de datos

DataBinder también se utiliza para el enlace de datos, y su capa inferior también se implementa indirectamente a través de BeanWrapper para el enlace de datos.

imagen

Pero tiene algunas funciones más que BeanWrapper, por ejemplo, después del enlace de datos, puede verificar los datos, como verificar la longitud del campo, etc.

Hablando de verificación de datos, ¿ha pensado en la verificación de parámetros en SpringMVC? Usando @Valid con algunas anotaciones como @NotBlank, @NotNull, etc., puede lograr una verificación de parámetros elegante.

De hecho, la verificación de parámetros de SpringMVC se realiza a través de DataBinder, por lo que DataBinder se usa más en SpringMVC, pero rara vez se usa en Spring.

Si está interesado, puede consultar la implementación de HandlerMethodArgumentResolver en SpringMVC sobre el procesamiento de parámetros de solicitud. Algunas implementaciones usan DataBinder (WebDataBinder) para realizar enlace de datos, conversión de tipos, verificación de datos, etc. de parámetros de solicitud de datos y clases de entidad. Espere.

No sé si habrás notado que al escribir interfaces, la cadena de tiempo del parámetro tipo String pasado desde el front-end no se puede convertir en tipo Date a través del propio marco Spring. Parte de la razón es que Spring no tiene la información relevante. Implementación del convertidor mencionada anteriormente.

En general, el enlace de datos se usa comúnmente en la configuración xml y SpringMVC, y el enlace de datos también es un enlace muy importante en el ciclo de vida de Spring Bean.

Manejo genérico

Para facilitar la operación y el procesamiento de tipos genéricos, Spring proporciona una poderosa clase de herramienta: ResolvableType.

El procesamiento genérico es en realidad algo relativamente independiente, porque es solo una clase de herramienta, ¡pero esta clase de herramienta está en todas partes en Spring!

ResolvableType proporciona una API flexible que puede obtener y procesar información como tipos genéricos en tiempo de ejecución.

imagen

Tipo resoluble

A continuación, echemos un vistazo a un caso para ver cómo obtener genéricos rápida y fácilmente a través de ResolvableType.

Primero, declaré una clase MyMap, que hereda HashMap. El primer parámetro genérico es de tipo Integer, el segundo parámetro genérico es de tipo Lista y el parámetro genérico de Lista es Cadena.

public class MyMap extends HashMap<Integer, List<String>> {

}

A continuación, demostremos cómo obtener los parámetros genéricos de HashMap y los parámetros genéricos de List.

El primer paso es ResolvableType#forClasscrear un ResolvableType correspondiente al tipo MyMap mediante el método

//创建MyMap对应的ResolvableType
ResolvableType myMapType = ResolvableType.forClass(MyMap.class);

Debido a que los parámetros genéricos están en la clase principal HashMap, tenemos que obtener el ResolvableType correspondiente a la clase principal HashMap a través del ResolvableType#getSuperType()método

//获取父类HashMap对应的ResolvableType
ResolvableType hashMapType = myMapType.getSuperType();

A continuación, debe obtener el tipo ResolvableType correspondiente al parámetro genérico de HashMap. Puede ResolvableType#getGeneric(int... indexes)obtener el parámetro genérico ResolvableType en la posición especificada. El parámetro del método se refiere al parámetro genérico en qué posición, comenzando desde 0.

Por ejemplo, obtenga el tipo ResolvableType correspondiente de la primera posición

//获取第一个泛型参数对应的ResolvableType
ResolvableType firstGenericType = hashMapType.getGeneric(0);

Ahora que tiene el tipo ResolvableType del primer parámetro genérico, solo necesita usar ResolvableType#resolve()el método para obtener el tipo de clase correspondiente al tipo ResolvableType, de modo que pueda obtener el tipo de clase de un parámetro genérico.

//获取第一个泛型参数对应的ResolvableType对应的class类型,也就是Integer的class类型
Class<?> firstGenericClass = firstGenericType.resolve();

Si desea obtener el tipo genérico del segundo parámetro genérico de HashMap, que es el tipo genérico Lista, puede escribir así

//HashMap第二个泛型参数的对应的ResolvableType,也就是List<String>
ResolvableType secondGenericType = hashMapType.getGeneric(1);
//HashMap第二个泛型参数List<String>的第一个泛型类型String对应的ResolvableType
ResolvableType secondFirstGenericType = secondGenericType.getGeneric(0);
//这样就获取到了List<String>的泛型类型String
Class<?> secondFirstGenericClass = secondFirstGenericType.resolve();

De la demostración anterior, podemos encontrar que, de hecho, cada paso de cambio es obtener el ResolvableType correspondiente a la clase genérica o principal correspondiente, etc. La clase principal o los parámetros genéricos pueden tener genéricos y similares, y solo necesita obtenlos paso a paso. Eso es todo. Cuando necesites obtener el tipo de clase específico, ResolvableType#resolve()simplemente usa el método.

Además de crear ResolvableType mediante los métodos mencionados anteriormente ResolvableType#forClass, también se puede crear mediante los siguientes métodos:

  • forField(Field field): Obtenga el ResolvableType correspondiente al tipo de campo

  • forMethodReturnType(Method method): Obtenga el ResolvableType correspondiente al tipo de valor de retorno del método

  • forMethodParameter(Method method, int parameterIndex): Obtenga el ResolvableType correspondiente al parámetro del método en una determinada posición del método

  • forConstructorParameter(Constructor<?> constructor, int parameterIndex): Obtiene el ResolvableType correspondiente a un determinado parámetro de construcción del método constructor

Como se puede ver en la explicación anterior, para un parámetro de método de clase, valor de retorno de método, campo, etc., se puede obtener el ResolvableType correspondiente

globalización

La internacionalización (i18n para abreviar) también es una función central proporcionada por Spring y, en realidad, es una función relativamente independiente.

En realidad, la llamada internacionalización se entiende simplemente como que el contenido del texto de salida está en diferentes idiomas para diferentes regiones y países.

La internacionalización de Spring en realidad se basa principalmente en la internacionalización y los métodos de procesamiento de texto en Java.

1. Internacionalización en Java

Lugar

La configuración regional es una clase proporcionada por Java que se puede utilizar para identificar diferentes idiomas y regiones. Por ejemplo, en_US representa inglés americano, zh_CN representa chino continental, etc.

imagen

En la actualidad, Java ha agotado la localización de muchos países.

Podemos usar la clase Locale para obtener la configuración regional predeterminada del sistema, o podemos configurar manualmente la configuración regional para que se adapte a diferentes entornos lingüísticos.

Paquete de recursos

ResourceBundle es una clase que carga recursos locales y puede cargar diferentes recursos según la configuración regional pasada.

Hagamos una demostración

Primero prepare el archivo de recursos. El archivo de recursos suele ser un archivo .properties. Las reglas de nomenclatura de nombres de archivos son las siguientes:

nombrebase_idioma_país.properties

El nombre base no importa, puedes llamarlo como quieras y el idioma y el país se obtienen de la configuración regional.

Por ejemplo, echemos un vistazo a la configuración regional en la región inglesa.

imagen

Como se puede ver en la figura anterior, el idioma de la configuración regional en inglés es en y el país es una cadena vacía. Luego, el archivo de recursos correspondiente a la región en inglés se puede nombrar: basename_en.properties. Dado que el país es una cadena vacía, se puede omitir.

La configuración regional de China continental es como se muestra a continuación

imagen

En este punto, el archivo puede denominarse: basename_zh_CN.properties

Bien, ahora que conocemos las reglas de nomenclatura, crearemos dos archivos, el nombre base se llama mensaje, uno en inglés y otro en chino, y los colocaremos en el classpath.

Archivo de recursos chino: message_zh_CN.properties, el contenido es:

name=三友的java日记

Archivo de recursos en inglés: message_en.properties, el contenido es:

name=sanyou's java diary

Una vez que tenga el archivo, puede ResourceBundle#getBundle(String baseName,Locale locale)obtener el ResourceBundle mediante el método

  • El primer parámetro baseName es el nombre base en nuestro nombre de archivo. Para nuestra demostración, es el mensaje.

  • El segundo parámetro es la región. Los archivos de diferentes regiones se cargan según la región.

tener una prueba

public class ResourceBundleDemo {

    public static void main(String[] args) {

        //获取ResourceBundle,第一个参数baseName就是我们的文件名称,第二个参数就是地区
        ResourceBundle chineseResourceBundle = ResourceBundle.getBundle("message", Locale.SIMPLIFIED_CHINESE);
        //根据name键取值
        String chineseName = chineseResourceBundle.getString("name");
        System.out.println("chineseName = " + chineseName);

        ResourceBundle englishResourceBundle = ResourceBundle.getBundle("message", Locale.ENGLISH);
        String englishName = englishResourceBundle.getString("name");
        System.out.println("englishName = " + englishName);

    }

}

resultado de la operación

imagen

De hecho, se puede ver en los resultados de la ejecución que en realidad se obtuvo con éxito, pero los caracteres chinos están confusos, esto se debe principalmente a que la capa inferior de ResourceBundle está realmente codificada, por lo que provocará caracteres confusos ISO-8859-1.

La solución más simple es usar secuencias Java Unicode para representar chino, y luego podrá leer chino. Por ejemplo, 三友的java日记use secuencias Java Unicode para representar\u4e09\u53cb\u7684java\u65e5\u8bb0

Además de este método, puedes heredar una clase de Control dentro de ResourceBundle.

imagen

Control

Anular el método newBundle

imagen

nuevoPaquete

newBundle es el método principal correspondiente a la creación de ResourceBundle. Al reescribir, puede hacer que admita otros métodos de codificación que desee.

Con el nuevo Control, solo necesita ResourceBundle#getBundle(String baseName, Locale targetLocale,Control control)especificar el Control a través del método al obtener el ResourceBundle.

En realidad, Spring se extiende de esta manera para admitir diferentes codificaciones, lo que también se mencionará más adelante.

Formato de mensaje

MessageFormat Gu Mingsi cree que el mensaje está formateado. Puede recibir una plantilla de mensaje que contenga marcadores de posición, reemplazarlos según los parámetros proporcionados y generar el mensaje final.

MessageFormat es útil para insertar valores dinámicos en mensajes como mensajes de bienvenida, mensajes de error, etc.

Primero hagamos una demostración.

public class MessageFormatDemo {

    public static void main(String[] args) {
        String message = MessageFormat.format("你好:{0}", "张三");
        System.out.println("message = " + message);
    }

}

Explique el código anterior:

  • 你好:{0}De hecho, es la plantilla del mensaje mencionada anteriormente, {0}que es un marcador de posición . El 0 en el medio significa que al formatear el mensaje, el primer parámetro del parámetro proporcionado se reemplazará con el valor del marcador de posición.

  • 张三Es el parámetro proporcionado. Puede escribir muchos parámetros, pero nuestra demostración solo tomará el primer parámetro, porque es{0}

Entonces la salida es:

message = 你好:张三

Mensaje formateado correctamente.

2. Internacionalización de primavera

Spring proporciona una interfaz internacional MessageSource

imagen

Fuente del mensaje

Tiene una implementación ResourceBundleMessageSource basada en ResourceBundle + MessageFormat

imagen

Fuente del mensaje del paquete de recursos

Su esencia puede ser almacenar la plantilla de mensaje en el archivo de recursos y luego reemplazar el marcador de posición a través de MessageFormat . El método getMessage de MessageSource puede pasar parámetros específicos.

Hagamos una demostración

Ahora, la declaración de bienvenida de inicio de sesión simulada debe tener diferentes nombres para diferentes personas, por lo que el archivo de recursos debe almacenar plantillas y se deben agregar diferentes plantillas a diferentes archivos de recursos.

Archivo de recursos chino: message_zh_CN.properties

welcome=您好:{0}

Archivo de recursos en inglés: message_en.properties

welcome=hello:{0}

Los marcadores de posición son nombres diferentes para diferentes personas.

código de prueba

public class MessageSourceDemo {

    public static void main(String[] args) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();

        //Spring已经扩展了ResourceBundle的Control,支持资源文件的不同编码方式,但是需要设置一下
        messageSource.setDefaultEncoding("UTF-8");

        //添加 baseName,就是前面提到的文件中的basename
        messageSource.addBasenames("message");

        //中文,传个中文名字
        String chineseWelcome = messageSource.getMessage("welcome", new Object[]{"张三"}, Locale.SIMPLIFIED_CHINESE);
        System.out.println("chineseWelcome = " + chineseWelcome);

        //英文,英语国家肯定是英文名
        String englishWelcome = messageSource.getMessage("welcome", new Object[]{"Bob"}, Locale.ENGLISH);
        System.out.println("englishWelcome = " + englishWelcome);
    }

}

resultado de la operación

chineseWelcome = 您好:张三
englishWelcome = hello:Bob

Se completó con éxito la carga de recursos de diferentes países y el formato de los mensajes de plantilla.

resumen

Aquí hay un breve resumen de lo que dice esta sección:

  • Localidad: encapsulación de información en diferentes países y regiones.

  • ResourceBundle: cargue los archivos de recursos correspondientes según la configuración regional de diferentes países. El nombre de este archivo de recursos debe cumplir con la basename_lang_country.propertiesconvención de nomenclatura .

  • MessageFormat: en realidad es un método de procesamiento de texto que puede analizar plantillas y reemplazar marcadores de posición de plantillas según los parámetros.

  • MessageSource: la interfaz internacional proporcionada por Spring. De hecho, su capa inferior se basa principalmente en ResourceBundle y MessageFormat de Java. El archivo de recursos almacena información de la plantilla. MessageFormat reemplaza los marcadores de posición en la plantilla de acuerdo con los parámetros pasados ​​por el método MessageSource.

fábrica de frijoles

Sabemos que el núcleo de Spring es IOC y AOP, y BeanFactory es el famoso contenedor IOC, que puede ayudarnos a producir objetos.

1. Sistema de interfaz BeanFactory

BeanFactory en sí es una interfaz

imagen

fábrica de frijoles

De la definición de interfaz anterior, podemos ver que los Beans se pueden obtener de BeanFactory.

También tiene muchas subinterfaces y diferentes subinterfaces tienen diferentes funciones.

  • ListableBeanFactory

  • JerárquicoBeanFactory

  • ConfigurableBeanFactory

  • AutowireCapableBeanFactory

ListableBeanFactory

imagen

ListableBeanFactory

Como se puede ver en los métodos proporcionados, se proporcionan algunas funciones para obtener colecciones. Por ejemplo, algunas interfaces pueden tener múltiples implementaciones y las colecciones de estos objetos de implementación se pueden obtener a través de estos métodos.

JerárquicoBeanFactory

imagen

JerárquicoBeanFactory

Se puede ver en la definición de la interfaz que se puede obtener el contenedor principal, lo que indica que BeanFactory tiene el concepto de contenedores secundarios y principales.

ConfigurableBeanFactory

imagen

ConfigurableBeanFactory

Como se puede ver en el nombre, BeanFactory se puede configurar, por lo que se puede configurar BeanFactory. Por ejemplo, el método en la captura de pantalla puede configurar las cosas de conversión de tipo que mencionamos anteriormente, de modo que los atributos de tipo se puedan convertir al generar el Frijol.

AutowireCapableBeanFactory

imagen

Proporciona las funciones de ensamblaje automático de beans, llenado de propiedades, inicialización y procesamiento para obtener objetos de inyección de dependencia.

Por ejemplo, eventualmente se llamará a @Autowired para AutowireCapableBeanFactory#resolveDependencymanejar las dependencias inyectadas.

De hecho, se puede ver desde aquí que Spring todavía divide las interfaces en función de diferentes responsabilidades en el diseño de la interfaz de BeanFactory. De hecho, no solo en BeanFactory, las interfaces mencionadas anteriormente también cumplen básicamente con este principio.

2. BeanDefinition y sus componentes relacionados

Definición de frijol

BeanDefinition es algo muy importante en el proceso de creación de Spring Bean: encapsula la metainformación requerida en el proceso de creación de Bean.

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    //设置Bean className
    void setBeanClassName(@Nullable String beanClassName);

    //获取Bean className
    @Nullable
    String getBeanClassName();
    
    //设置是否是懒加载
    void setLazyInit(boolean lazyInit);

    //判断是否是懒加载
    boolean isLazyInit();
    
    //判断是否是单例
    boolean isSingleton();

}

El código anterior es parte de los métodos de la interfaz BeanDefinition. Como se puede ver en el nombre de la definición de este método, parte de la información requerida en el proceso de creación de un Bean se puede obtener de BeanDefinition, como el tipo de clase del Bean. y si el Bean se carga de forma diferida, si este Bean es un singleton, etc., porque con esta información, Spring sabe qué tipo de Bean crear.

LeerBeanDefinition

Reading BeanDefinition se divide aproximadamente en las siguientes categorías:

  • BeanDefinitionReader

  • ClassPathBeanDefinitionScanner

BeanDefinitionReader

imagen

BeanDefinitionReader

BeanDefinitionReader puede loadBeanDefinitions(Resource resource)cargar BeanDefinition a través de métodos. Los parámetros del método son los recursos que mencionamos anteriormente. Por ejemplo, el Bean se puede definir en un archivo xml. Este archivo xml es un recurso.

Implementaciones relacionadas de BeanDefinitionReader:

  • XmlBeanDefinitionReader: Leer beans configurados xml

  • PropertiesBeanDefinitionReader: Lee los beans configurados en el archivo de propiedades. Sí, has leído bien. Los beans se pueden definir en la configuración del archivo de propiedades.

  • AnnotatedBeanDefinitionReader: lee beans definidos mediante anotaciones, como anotaciones @Lazy, etc. AnnotatedBeanDefinitionReader no es una implementación de BeanDefinitionReader, pero su función es la misma.

ClassPathBeanDefinitionScanner

imagen

Esta función escanea los beans definidos por @Component y sus anotaciones derivadas (@Service, etc.) en el paquete especificado. De hecho, es la implementación subyacente de la anotación @ComponentScan.

La clase ClassPathBeanDefinitionScanner en realidad se usa en muchos otros marcos, porque esta clase puede escanear paquetes específicos y generar BeanDefinitions. Se usa mucho para aquellos que necesitan escanear paquetes para generar BeanDefinitions.

Por ejemplo, en el marco común MyBatis, su anotación @MapperScan puede escanear la interfaz del Mapper en el paquete especificado y, de hecho, también escanea la interfaz del Mapper heredando ClassPathBeanDefinitionScanner.

imagen

BeanDefinitionRegistry

Como puede ver en el nombre, este es el centro de registro de BeanDefinition, que se utiliza para guardar BeanDefinition.

imagen

Proporciona la función de agregar, eliminar y verificar BeanDefinition.

En este punto, puedes usar una imagen para conectar las cosas mencionadas anteriormente.

imagen

  • Genere una BeanDefinition para cada Bean a través de BeanDefinitionReader o ClassPathBeanDefinitionScanner

  • Después de generar BeanDefinition, agréguelo a BeanDefinitionRegistry

  • Cuando se obtiene un Bean de BeanFactory, la BeanDefinition correspondiente al Bean que debe crearse se extraerá del BeanDefinitionRegistry y el Bean se generará en función de la información de BeanDefinition.

  • Cuando el Bean generado es un singleton, Spring guardará el Bean en SingletonBeanRegistry, que generalmente es el caché de primer nivel en el caché de tres niveles, para evitar la creación repetida. Cuando necesite usarlo, puede buscarlo directamente. del SingletonBeanRegistry.

3. Implementación central de BeanFactory

El sistema BeanFactory mencionado anteriormente es una interfaz, entonces, ¿qué clase es la clase de implementación de BeanFactory?

En realidad, solo existe una clase de implementación subyacente real de BeanFactory, y esa es la clase DefaultListableBeanFactory. Esta clase y su clase principal realmente implementan todas las funciones de BeanFactory y sus subinterfaces.

imagen

Y se puede ver en la implementación de la interfaz que también implementa BeanDefinitionRegistry. En otras palabras, en términos de implementación subyacente, BeanFactory y BeanDefinitionRegistry son en realidad la misma clase de implementación.

Habiendo dicho todo lo anterior, aquí hay una demostración.

public class BeanFactoryDemo {

    public static void main(String[] args) {
        //创建一个BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        //创建一个BeanDefinitionReader,构造参数是一个BeanDefinitionRegistry
        //因为DefaultListableBeanFactory实现了BeanDefinitionRegistry,所以直接把beanFactory当做构造参数传过去
        AnnotatedBeanDefinitionReader beanDefinitionReader = new AnnotatedBeanDefinitionReader(beanFactory);

        //读取当前类 BeanFactoryDemo 为一个Bean,让Spring帮我们生成这个Bean
        beanDefinitionReader.register(BeanFactoryDemo.class);

        //从容器中获取注册的BeanFactoryDemo的Bean
        BeanFactoryDemo beanFactoryDemo = beanFactory.getBean(BeanFactoryDemo.class);

        System.out.println("beanFactoryDemo = " + beanFactoryDemo);
    }

}

Explique brevemente el significado del código anterior.

  • Cree un BeanFactory, que es DefaultListableBeanFactory

  • Cree un AnnotatedBeanDefinitionReader. El parámetro de construcción es un BeanDefinitionRegistry, porque BeanDefinitionReader necesita guardar el BeanDefinition leído en BeanDefinitionRegistry. Al mismo tiempo, debido a que DefaultListableBeanFactory implementa BeanDefinitionRegistry, beanFactory se pasa directamente como parámetro de construcción.

  • Lea la clase actual BeanFactoryDemo como Bean y deje que Spring nos ayude a generar este Bean.

  • El siguiente paso es conseguir la impresión.

resultado de la operación

imagen

Obtuvimos con éxito nuestro Bean registrado

Resumir

Esta sección habla principalmente sobre varios componentes centrales para implementar la COI.

BeanFactory y su sistema de interfaz:

  • ListableBeanFactory

  • JerárquicoBeanFactory

  • ConfigurableBeanFactory

  • AutowireCapableBeanFactory

BeanDefinition y sus componentes relacionados:

  • Definición de frijol

  • BeanDefinitionReader y ClassPathBeanDefinitionScanner: lee recursos y genera BeanDefinition

  • BeanDefinitionRegistry: almacena BeanDefinition

Implementación central de BeanFactory:

  • DefaultListableBeanFactory: contenedor IOC, que también implementa la interfaz BeanDefinitionRegistry

Contexto de aplicación

Finalmente, hablemos de ApplicationContext, porque lo que dije antes en realidad está allanando el camino para ApplicationContext.

Primero echemos un vistazo a la interfaz ApplicationContext.

imagen

Le sorprenderá descubrir que, a excepción de EnvironmentCapable y ApplicationEventPublisher, varias interfaces heredadas por ApplicationContext se mencionan anteriormente.

La interfaz EnvironmentCapable es relativamente simple y proporciona la función de obtener Environment.

imagen

MedioambienteCapaz

Muestra que Environment se puede obtener de ApplicationContext, por lo que EnvironmentCapable se puede considerar como se mencionó anteriormente.

En cuanto a ApplicationEventPublisher, lo dejamos para la siguiente sección.

ApplicationContext también hereda ListableBeanFactory y HierarchicalBeanFactory, lo que significa que ApplicationContext es en realidad un BeanFactory, por lo que no hay nada de malo en decir que ApplicationContext es un contenedor IOC, pero debido a que también hereda otras interfaces, tiene muchas más funciones que BeanFactory.

Por lo tanto, ApplicationContext es una interfaz que integra miles de funciones. Una vez que obtienes el ApplicationContext (que se puede inyectar con @Autowired), puedes usarlo para obtener beans, cargar recursos, obtener el entorno e internacionalizarlo. Es realmente impresionante. . .

Aunque ApplicationContext hereda estas interfaces, ApplicationContext implementa las interfaces a través de un método de delegación, y las implementaciones reales son las implementaciones que mencionamos anteriormente.

¿Qué es la delegación?Escribamos un ejemplo y lo sabrás.

public class MyApplicationContext implements ApplicationContext {

    private final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

    @Override
    public Resource[] getResources(String locationPattern) throws IOException {
        return resourcePatternResolver.getResources(locationPattern);
    }
    
}

Como se indicó anteriormente, en realidad es un pseudocódigo.

Debido a que ApplicationContext hereda la interfaz ResourcePatternResolver, implementé el método getResources, pero la implementación real en realidad la implementa PathMatchingResourcePatternResolver en la variable. Esto en realidad es una delegación, no se implementa directamente, sino que se entrega a otros que realmente implementan esta interfaz. manejar

De la misma manera, la implementación de ApplicationContext de la interfaz BeanFactory finalmente se delega a DefaultListableBeanFactory.

La delegación todavía se usa mucho dentro de Spring y algunas de las interfaces mencionadas anteriormente también se implementan mediante la delegación.

ApplicationContext tiene una subinterfaz, ConfigurableApplicationContext

imagen

A partir de los métodos proporcionados, podemos ver que ApplicationContext se puede configurar, como configurar el entorno y también configurar el padre, lo que muestra que ApplicationContext también tiene el concepto de hijo y padre.

Hemos visto muchas interfaces que comienzan con Configurable. Esta es la convención de nomenclatura, lo que significa que es configurable. Proporciona métodos como set y add.

Hay muchas implementaciones de ApplicationContext, pero tiene una implementación abstracta muy importante, AbstractApplicationContext, porque otras implementaciones heredan esta implementación abstracta.

imagen

ResumenAplicaciónContexto

Esta clase implementa principalmente algunos métodos de interfaz heredados mediante delegación, como la implementación de la interfaz BeanFactory.

imagen

Y la clase AbstractApplicationContext también implementa un método de actualización muy central.

imagen

Todos los ApplicationContexts deben llamar a este método de actualización después de su creación antes de poder usarse. En cuanto a lo que hace este método, escribiré otro artículo para centrarme en él más adelante.

evento

Cuando hablamos de la interfaz heredada por ApplicationContext en la sección anterior, dejamos un suspenso, es decir, el rol de ApplicationEventPublisher, y ApplicationEventPublisher está relacionado con los eventos discutidos en esta sección.

Los eventos de primavera son una implementación del patrón de observador y su función principal es desacoplar.

Cuando sucede algo, siempre que se publique un evento, los oyentes (observadores) del evento pueden responder o procesar el evento.

Por ejemplo, supongamos que se produce un incendio y es posible que necesite llamar al 119 y rescatar a las personas. Esto se puede implementar basándose en el modelo de eventos. Solo necesita llamar al 119 y rescatar a las personas para monitorear la ocurrencia del incendio. Cuando ocurre un incendio , estas personas serán notificadas, llame al 119 y guarde a las personas para activar las operaciones lógicas correspondientes.

imagen

1. ¿Qué es el Evento de Primavera?

Spring Event es la implementación de Spring de este modelo de eventos. Solo necesita extenderlo según la API proporcionada por Spring para completar fácilmente la publicación y suscripción de eventos.

Las API relacionadas con eventos de Spring incluyen principalmente lo siguiente:

  • Evento de aplicación

  • Oyente de aplicaciones

  • AplicaciónEventPublisher

Evento de aplicación

imagen

Evento de aplicación

La clase principal de eventos. Todos los eventos específicos deben heredar esta clase. Los parámetros del constructor son los parámetros transportados por este evento. El oyente puede usar estos parámetros para realizar algunas operaciones comerciales.

Oyente de aplicaciones

imagen

Oyente de aplicaciones

La interfaz para el monitoreo de eventos. El tipo genérico es el tipo de evento que necesita ser monitoreado. La subclase necesita implementar onApplicationEvent. El parámetro es el tipo de evento a monitorear. La implementación del método onApplicationEvent representa el procesamiento del evento. Cuando Cuando ocurre un evento, Spring volverá a llamar al método onApplicationEvent Implementarlo y pasar el evento publicado.

AplicaciónEventPublisher

imagen

AplicaciónEventPublisher

La interfaz sobrante de la sección anterior, el editor de eventos, puede publicar un evento a través del método PublishEvent y luego activar la devolución de llamada del oyente que escucha el evento.

ApplicationContext hereda ApplicationEventPublisher, lo que significa que mientras exista ApplicationContext, se pueden publicar eventos.

Sin más preámbulos, entremos en el código.

Tome el fuego de arriba como ejemplo.

Crear una clase de evento de incendio

La clase de evento de incendio hereda ApplicationEvent

// 火灾事件
public class FireEvent extends ApplicationEvent {

    public FireEvent(String source) {
        super(source);
    }

}

Crear un oyente para eventos de incendio.

Oyente de eventos de incendio que llaman al 119:

public class Call119FireEventListener implements ApplicationListener<FireEvent> {

    @Override
    public void onApplicationEvent(FireEvent event) {
        System.out.println("打119");
    }

}

Oyentes en vivo para eventos de incendio:

public class SavePersonFireEventListener implements ApplicationListener<FireEvent> {

    @Override
    public void onApplicationEvent(FireEvent event) {
        System.out.println("救人");
    }

}

Ahora que los eventos y los monitores correspondientes están disponibles, probemos:

public class Application {

    public static void main(String[] args) {
        //创建一个Spring容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        //将 事件监听器 注册到容器中
        applicationContext.register(Call119FireEventListener.class);
        applicationContext.register(SavePersonFireEventListener.class);
        applicationContext.refresh();

        // 发布着火的事件,触发监听
        applicationContext.publishEvent(new FireEvent("着火了"));
    }

}

Registre los dos eventos en el contenedor Spring y luego publique el evento FireEvent

resultado de la operación:

打119
救人

La consola imprimió los resultados y activó el monitoreo.

Si necesita apagar un incendio ahora, solo necesita escuchar FireEvent, implementar la lógica de extinción de incendios e inyectarla en el contenedor Spring, y no es necesario tocar el resto del código en absoluto.

2. Eventos integrados en la primavera

Hay muchos eventos integrados en Spring, aquí enumeraré algunos.

tipo de evento tiempo de activación
ContextRefreshedEvent Se activa cuando se llama al método de actualización() en la interfaz ConfigurableApplicationContext
ContextoEvento iniciado Se activa cuando se llama al método start() de ConfigurableApplicationContext
Contexto detenidoEvento Se activa cuando se llama al método stop() de ConfigurableApplicationContext
ContextoCerradoEvento Este evento se activa cuando se cierra ApplicationContext, que se activa llamando al método close().

Durante el inicio de ApplicationContext (contenedor Spring), Spring publicará estos eventos. Si necesita realizar alguna operación en un momento determinado cuando se inicia el contenedor Spring, solo necesita escuchar los eventos correspondientes.

3. Características de propagación de los eventos primaverales.

¿Qué significa la propagación de los eventos de primavera?

Como se mencionó anteriormente, ApplicationContext tiene el concepto de contenedores secundarios y principales, y la propagación de eventos Spring significa que cuando un evento se publica a través de un contenedor secundario, no solo puede activar el detector de eventos en el contenedor secundario, sino también activar el evento. en el contenedor principal.oyente.

código arriba

public class EventPropagateApplication {

    public static void main(String[] args) {

        // 创建一个父容器
        AnnotationConfigApplicationContext parentApplicationContext = new AnnotationConfigApplicationContext();
        //将 打119监听器 注册到父容器中
        parentApplicationContext.register(Call119FireEventListener.class);
        parentApplicationContext.refresh();

        // 创建一个子容器
        AnnotationConfigApplicationContext childApplicationContext = new AnnotationConfigApplicationContext();
        //将 救人监听器 注册到子容器中
        childApplicationContext.register(SavePersonFireEventListener.class);
        childApplicationContext.refresh();

        // 设置一下父容器
        childApplicationContext.setParent(parentApplicationContext);

        // 通过子容器发布着火的事件,触发监听
        childApplicationContext.publishEvent(new FireEvent("着火了"));

    }

}

Se crearon dos contenedores: el contenedor principal registró un oyente para llamar al 119 y el contenedor secundario registró un oyente para rescatar personas. Luego, los contenedores secundario y principal se asociaron a través de setParent y, finalmente, el evento de incendio se liberó a través del contenedor secundario.

resultado de la operación:

救人
打119

En el registro impreso, se puede ver que, aunque el contenedor secundario publicó el evento de incendio, el oyente del contenedor principal también escuchó con éxito el evento de incendio.

Esta característica de propagación también se puede ver en el código fuente.

imagen

Código fuente de propagación de eventos

Si el contenedor principal no está vacío, el evento se publicará nuevamente a través del contenedor principal.

Un pequeño problema en las características de propagación.

Como se mencionó anteriormente, se lanzarán muchos eventos durante el proceso de inicio del contenedor Spring. Si necesita las extensiones correspondientes, puede escuchar estos eventos.

Sin embargo, no sé si alguna vez se ha encontrado con tal error. En el entorno de Spring Cloud, sus oyentes para escuchar estos eventos de Spring se ejecutarán muchas veces, lo que en realidad está relacionado con las características de propagación.

En el entorno Spring Cloud, para aislar las configuraciones de diferentes servicios como FeignClient y RibbonClient entre sí, se creará un contenedor Spring para cada FeignClient o RibbonClient, y estos contenedores tienen un contenedor principal común, que se crea cuando SpringBoot inicio del proyecto contenedor

imagen

Suponiendo que escucha el evento ContextRefreshedEvent de actualización del contenedor, entonces el oyente que usted mismo escribió está en el contenedor creado cuando se inició el proyecto SpringBoot.

El contenedor de configuración de cada servicio también es un contenedor Spring y también publicará ContextRefreshedEvent cuando se inicie. Luego, debido a las características de propagación, su detector de eventos se activará y ejecutará varias veces.

imagen

¿Cómo solucionar este hoyo?

Puede juzgar si estos oyentes se han ejecutado, como agregar un indicador de juicio, o escuchar eventos similares, como el evento ApplicationStartedEvent. Este evento es un evento publicado en el inicio de SpringBoot y el subcontenedor no es SpringBoot, por lo que no Este evento se enviará varias veces y solo se ejecutará una vez.

Resumir

Finalmente se ha escrito todo este artículo. Aquí hay una breve revisión de varias funciones principales mencionadas en este artículo:

  • Gestión de recursos: encapsule los recursos de manera uniforme para facilitar la lectura y la gestión de recursos.

  • Entorno: Gestionar la configuración de contenedores o proyectos.

  • Conversión de tipo: convierte un tipo en otro tipo

  • Enlace de datos: vincula datos a las propiedades del objeto. La conversión de tipos está involucrada antes del enlace.

  • Procesamiento genérico: una clase de herramienta para operar genéricos, que se puede ver en todas partes en Spring

  • Internacionalización: encapsulación unificada de la internacionalización de Java

  • BeanFactory: contenedor COI

  • ApplicationContext: una potente interfaz que integra miles de funciones, también se puede decir que es un contenedor IOC.

  • Evento: una herramienta de desacoplamiento proporcionada por Spring basada en el patrón del observador

Por supuesto, además de lo anterior, Spring también tiene muchas otras funciones principales, como AOP, expresiones SpEL, etc. Dado que AOP involucra el ciclo de vida del Bean, este artículo no incluye la explicación del ciclo de vida del Bean, por lo que lo haré. No entraré en eso aquí. Hablaré de ello más tarde cuando tenga la oportunidad; en cuanto a SpEL, es un lenguaje de expresión proporcionado por Spring. Se trata principalmente de gramática y análisis de gramática, por lo que no entraré en eso aquí. .

El artículo anterior proviene de la cuenta pública de WeChat: Sanyou's Java Diary, autor Sanyou

Supongo que te gusta

Origin blog.csdn.net/weixin_43820024/article/details/132469709
Recomendado
Clasificación