SpringBoot --- Artículos prácticos

1. Implementación en caliente

1.1 Concepto

¿Qué es el despliegue en caliente? En pocas palabras, su programa ha cambiado y ahora tiene que reiniciar el servidor, ¿es problemático? Sin reiniciar, el servidor recargará silenciosamente el programa actualizado por sí mismo, que es una implementación en caliente.

¿Cómo se realiza la función de despliegue en caliente? Esto se divide en dos situaciones: los métodos de implementación de implementación en caliente de los proyectos que no son Springboot y los proyectos Springboot son completamente diferentes. Permítanme hablar primero sobre cómo el proyecto original que no es springboot implementa la implementación en caliente.

Principio de implementación de implementación en caliente de proyectos que no son springboot

Al desarrollar un proyecto que no sea springboot, debemos crear un proyecto web e iniciarlo a través de tomcat. Por lo general, primero debemos instalar el servidor tomcat en el disco y publicar la configuración del programa desarrollado en el servidor tomcat instalado. Si desea lograr el efecto de implementación en caliente, en realidad existen dos métodos en este caso. Uno es configurarlo en el archivo de configuración del servidor tomcat. Este método no tiene nada que ver con la herramienta IDE que usa, ya sea que use eclipse o idea. Otro método es configurar a través de herramientas IDE, como la configuración en la herramienta idea. Este formulario debe basarse en herramientas IDE. Cada herramienta IDE es diferente, y la configuración correspondiente también es diferente. Pero la idea central es la misma, que es usar el servidor para monitorear las aplicaciones cargadas en él y volver a cargarlo una vez si se encuentran cambios.

La implementación en caliente de los proyectos que no son springboot mencionados anteriormente parece ser un proceso muy simple, y casi todos los socios pequeños pueden escribirlo por sí mismos. Si no puede escribir, le daré la idea más simple, pero el diseño real es más complicado que esto. Por ejemplo, inicie una tarea programada, registre el tamaño de cada archivo cuando se inicie la tarea y luego compare cada 5 segundos para ver si el tamaño de cada archivo ha cambiado o si hay archivos nuevos. Si no hay cambios, libérelo. Si hay un cambio, actualice la información del archivo grabado actual y luego reinicie el servidor, que puede realizar una implementación en caliente. Por supuesto, este proceso no debe hacerse de esta manera, por ejemplo, si cambio una cadena impresa "abc" a "cba", el tamaño de comparación no cambiará, pero el contenido no cambiará, por lo que definitivamente no funcionará. Hagamos una analogía, y reiniciar el servidor es un inicio en frío, no una implementación en caliente, entendamos el espíritu.

​ Parece que este proceso no es demasiado complicado ¿Hay otros giros y vueltas en el proyecto springboot? Realmente los hay.

Principio de implementación del despliegue en caliente del proyecto springboot

​ El proyecto web desarrollado en base a springboot en realidad tiene una característica notable, es decir, el servidor tomcat está incorporado, ¿recuerdas el servidor integrado? El servidor se ejecuta en el contenedor de primavera como un objeto. Originalmente, esperábamos que después de que el servidor tomcat cargara el programa, el servidor tomcat mirará fijamente el programa. Después de cambiar, reiniciaré y volveré a cargar, pero ahora tomcat y nuestro programa están en el mismo nivel, y todos son componentes en el contenedor de primavera Problema, falta de un derecho de gestión directa, ¿qué debo hacer? Simple, solo cree otro programa X para mirar su programa A original desarrollado en el contenedor de primavera, ¿no sería suficiente? De hecho, solo haga un programa X que mire fijamente al programa A. Si el programa A que usted mismo desarrolló cambia, entonces el programa X ordenará al contenedor tomcat que vuelva a cargar el programa A y estará bien. Y hay una ventaja en hacer esto, no necesita recargar todas las cosas en el contenedor de primavera, solo necesita recargar la parte del programa que desarrolló, que es más eficiente ahora, lo cual es muy bueno.

Permítanme hablar sobre cómo crear un programa X de este tipo, no debe ser escrito a mano por nosotros mismos, Springboot ya se ha hecho, solo importe una coordenada en él.

1.2 Iniciar manualmente la implementación en caliente

Paso ① : Importe las coordenadas correspondientes a la herramienta de desarrollo

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

Paso ② : Cree el proyecto, puede usar la tecla de acceso directo para activar esta función

inserte la descripción de la imagen aquí

Activar despliegue en caliente: Ctrl + F9

​ El proceso anterior realiza la implementación en caliente del proyecto springboot, ¿es muy simple? Pero aquí necesitamos popularizar la ingeniería de trabajo subyacente.

reiniciar y recargar

Un proyecto springboot se divide en realidad en dos procesos en tiempo de ejecución. De acuerdo con las diferentes cosas cargadas, se divide en el cargador de clases base y el cargador de clases de reinicio.

  • Cargador de clases base: se usa para cargar las clases en el paquete jar. Dado que las clases y los archivos de configuración en el paquete jar no cambiarán, no importa cuántas veces se carguen, el contenido cargado no cambiará
  • reiniciar el cargador de clases: se utiliza para cargar clases, archivos de configuración, páginas y otra información desarrollada por los propios desarrolladores.. Este tipo de archivos se ven afectados por los desarrolladores.

Cuando se inicia el proyecto springboot, se ejecuta el cargador de clases base y, después de cargar la información en el paquete jar, se ejecuta el cargador de clases de reinicio para cargar el contenido creado por el desarrollador. Después de ejecutar el proyecto de compilación, dado que la información en el jar no cambiará, no es necesario volver a ejecutar el cargador de clases base, así que simplemente ejecute el cargador de clases de reinicio, es decir, vuelva a cargar el contenido creado por el desarrollador, y esto es complete También se puede decir que el proceso de implementación en caliente en realidad está recargando la información en el cargador de clases de reinicio.

Resumir

  1. Usar herramientas de desarrollador para habilitar la implementación en caliente para el proyecto actual
  2. Use la operación de compilación del proyecto para implementar el proyecto en caliente (Ctrl+F9)
  3. La implementación en caliente solo carga los recursos desarrollados de forma personalizada por el desarrollador actual y no carga los recursos jar.

1.3 Iniciar automáticamente la implementación en caliente

La implementación automática en caliente es en realidad para diseñar un conmutador. Después de encender el conmutador, la herramienta IDE se puede implementar automáticamente en caliente. Por lo tanto, esta operación está relacionada con las herramientas IDE. A continuación, se toma Idea como ejemplo para configurar la implementación en caliente en Idea.

Paso ① : configurar un proyecto de compilación automática

​ Abra [Archivo], seleccione [configuración...], busque la opción [Compilar] en el menú en el lado izquierdo del panel y luego marque [Crear proyecto automáticamente], lo que significa construir automáticamente el proyecto

inserte la descripción de la imagen aquí

Paso ② : permitir la construcción automática mientras se ejecuta el programa

Use las teclas de acceso directo [Ctrl] + [Alt] + [Mierda] + [/] para abrir el panel de mantenimiento y seleccione el elemento 1 [Registro...]

inserte la descripción de la imagen aquí

Busque comple en las opciones y luego marque el elemento correspondiente

inserte la descripción de la imagen aquí

De esta manera, el programa se puede construir automáticamente cuando se está ejecutando, dándose cuenta del efecto de implementación en caliente.

Nota : si reconstruye el servidor cada vez que escribe una letra, esto es demasiado frecuente, por lo que la idea está configurada para realizar una implementación activa cuando la herramienta de la idea pierde el foco durante 5 segundos. De hecho, la implementación en caliente se realiza cuando cambia de la herramienta idea a otras herramientas. Por ejemplo, después de cambiar el programa, debe ir al navegador para depurar. En este momento, la idea realizará automáticamente la implementación en caliente.

Resumir

  1. El despliegue automático en caliente necesita iniciar el proyecto de construcción automática
  2. La implementación en caliente automática debe estar activada para compilar automáticamente el proyecto cuando el programa se está ejecutando

1.4 Configuración del rango de despliegue en caliente

Al modificar los archivos en el proyecto, puede encontrar que no todas las modificaciones de archivos activarán la implementación en caliente. La razón es que hay un conjunto de configuraciones en la herramienta del desarrollador. Cuando se cumplan las condiciones en la configuración, se iniciará la implementación en caliente. Configuración La información del directorio que no participa en hot deployment por defecto es la siguiente

  • /META-INF/experto
  • /META-INF/recursos
  • /recursos
  • /estático
  • /público
  • /plantillas

​ Si los archivos en el directorio anterior cambian, no participarán en la implementación en caliente. Si desea modificar la configuración, puede establecer qué archivos no participan en la operación de implementación en caliente a través del archivo application.yml

spring:
  devtools:
    restart:
      # 设置不参与热部署的文件或文件夹
      exclude: static/**,public/**,config/application.yml

Resumir

  1. Los archivos o directorios que no participan en la implementación en caliente se pueden modificar a través de la configuración

1.5 Desactivar la implementación en caliente

Es imposible utilizar la función de implementación en caliente cuando se está ejecutando el entorno en línea, por lo que es necesario deshabilitar a la fuerza esta función, que se puede deshabilitar a través de la configuración.

spring:
  devtools:
    restart:
      enabled: false

​ Si tiene cuidado de que demasiadas capas de archivos de configuración den lugar a una cobertura uniforme y eventualmente provoquen un error de configuración, puede aumentar el nivel de configuración y configurar la implementación en caliente en un nivel superior. Por ejemplo, apague la función de implementación en caliente a través de la configuración de propiedades del sistema antes de iniciar el contenedor.

@SpringBootApplication
public class SSMPApplication {
    
    
    public static void main(String[] args) {
    
    
        System.setProperty("spring.devtools.restart.enabled","false");
        SpringApplication.run(SSMPApplication.class);
    }
}

De hecho, las preocupaciones anteriores son un poco redundantes, porque es imposible modificar el código en el mantenimiento del entorno en línea. El único efecto de hacerlo es reducir el consumo de recursos. Después de todo, solo necesita cerrar los ojos. mirando si su proyecto ha cambiado, no hay una función de implementación en caliente, y la función de este interruptor es deshabilitar la función correspondiente.

Resumir

  1. La función de implementación en caliente se puede desactivar mediante configuración para reducir el consumo de recursos de los programas en línea

Dos, configurar avanzado

2.1, @ConfiguraciónPropiedades

En el capítulo básico, aprendí la anotación @ConfigurationProperties, que se usa para enlazar propiedades para beans. Los desarrolladores pueden agregar varias propiedades en el formato de objeto en el archivo de configuración yml

servers:
  ip-address: 192.168.0.1 
  port: 2345
  timeout: -1

Luego desarrolle una clase de entidad para encapsular datos, preste atención para proporcionar el método de establecimiento correspondiente al atributo

@Component
@Data
public class ServerConfig {
    
    
    private String ipAddress;
    private int port;
    private long timeout;
}

​ Use la anotación @ConfigurationProperties para asociar los valores de propiedad en la configuración con la clase del modelo desarrollado

@Component
@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
    
    
    private String ipAddress;
    private int port;
    private long timeout;
}

De esta forma, cuando se carga el bean correspondiente, el valor de la propiedad de configuración se puede cargar directamente. Pero lo que hemos aprendido hasta ahora es usar este formulario para cargar valores de atributo para beans personalizados ¿Qué pasa si es un bean de terceros? ¿Se puede utilizar este formulario para cargar valores de atributos? ¿Por qué se plantea esta pregunta? El motivo es que la anotación @ConfigurationProperties actual está escrita sobre la definición de la clase, y el código fuente del bean desarrollado por el tercero no está escrito por usted mismo, y es imposible que agregue la anotación @ConfigurationProperties al código fuente. .¿Cómo solucionar este problema? Hablemos de este problema a continuación.

El uso de la anotación @ConfigurationProperties puede cargar propiedades para beans de terceros, con un formato especial.

Paso ① : Defina un bean de terceros usando la anotación @Bean

@Bean
public DruidDataSource datasource(){
    
    
    DruidDataSource ds = new DruidDataSource();
    return ds;
}

Paso ② : defina las propiedades que se vincularán en yml, tenga en cuenta que la fuente de datos está en minúsculas en este momento

datasource:
  driverClassName: com.mysql.jdbc.Driver

Paso ③ : use la anotación @ConfigurationProperties para vincular propiedades a beans de terceros. Tenga en cuenta que el prefijo es fuente de datos en minúsculas

@Bean
@ConfigurationProperties(prefix = "datasource")
public DruidDataSource datasource(){
    
    
    DruidDataSource ds = new DruidDataSource();
    return ds;
}

El método de operación es exactamente el mismo, excepto que la anotación @ConfigurationProperties se puede agregar no solo a la clase, sino también al método. Agregar a la clase es para las propiedades de enlace de objetos de la clase actual administrada por el contenedor de primavera, y agregar al método es para el contenedor de primavera. Administrar el valor de retorno de las propiedades de enlace del objeto del método actual, de hecho, son esencialmente las mismas.

De hecho, ha surgido un nuevo problema cuando hacemos esto. En la actualidad, definimos beans a través de anotaciones de clase o mediante definiciones @Bean. El uso de anotaciones @ConfigurationProperties puede vincular propiedades para beans. En un sistema empresarial, qué beans se definen a través de anotaciones @ConfigurationProperties para enlazar propiedades? Debido a que esta anotación se puede escribir no solo en la clase, sino también en el método, es más complicado encontrarla. Para resolver este problema, Spring nos proporciona una nueva anotación que marca específicamente qué beans están vinculados a las propiedades mediante la anotación @ConfigurationProperties. Esta anotación se llama @EnableConfigurationProperties. ¿Cómo usarlo?

Paso ① : abra la anotación @EnableConfigurationProperties en la clase de configuración y marque la clase para usar la anotación @ConfigurationProperties para vincular propiedades

@SpringBootApplication
@EnableConfigurationProperties(ServerConfig.class)
public class Springboot13ConfigurationApplication {
    
    
}

Paso ② : use @ConfigurationProperties directamente en la clase correspondiente para vincular propiedades

@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
    
    
    private String ipAddress;
    private int port;
    private long timeout;
}

¿Algunas personas sienten que no hay diferencia? Tenga en cuenta que la clase ServerConfig que ahora vincula propiedades no declara la anotación @Component. Al usar la anotación @EnableConfigurationProperties, Spring definirá su clase anotada como un bean de forma predeterminada, por lo que no es necesario volver a declarar la anotación @Component.

  • @EnableConfigurationProperties y @Component no se pueden usar al mismo tiempo

Al usar la anotación @ConfigurationProperties, aparecerá un mensaje de aviso

inserte la descripción de la imagen aquí

Después de que aparezca este mensaje, solo necesita agregar una coordenada y el recordatorio desaparecerá

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

Resumir

  1. Use @ConfigurationProperties para enlazar propiedades para beans de terceros declarados usando @Bean
  2. Después de usar @EnableConfigurationProperties para declarar el bean para el enlace de propiedades, no es necesario usar la anotación @Component para declarar el bean nuevamente.

2.2 Encuadernación suelta/encuadernación suelta

Al realizar la vinculación de atributos, es posible que se encuentre con las siguientes situaciones: para realizar la asignación de nombres estándar, los desarrolladores escribirán el nombre del atributo estrictamente de acuerdo con el método de asignación de nombres y cambiarán la fuente de datos a fuente de datos en el archivo de configuración yml, de la siguiente manera:

dataSource:
  driverClassName: com.mysql.jdbc.Driver

En este punto, el programa puede ejecutarse normalmente y luego cambiar el prefijo datasource en el código a dataSource, de la siguiente manera:

@Bean
@ConfigurationProperties(prefix = "dataSource")
public DruidDataSource datasource(){
    
    
    DruidDataSource ds = new DruidDataSource();
    return ds;
}

En este punto, ocurrió un error de compilación y no fue causado por la herramienta idea. Después de la ejecución, seguirá habiendo problemas. El nombre del atributo de configuración dataSource no es válido.

Configuration property name 'dataSource' is not valid:

    Invalid characters: 'S'
    Bean: datasource
    Reason: Canonical names should be kebab-case ('-' separated), lowercase alpha-numeric characters and must start with a letter

Action:
Modify 'dataSource' so that it conforms to the canonical names requirements.

Por qué ocurre este tipo de problema, este es un punto de conocimiento importante cuando Springboot enlaza propiedades.La vinculación flexible de los nombres de propiedad también se puede denominar vinculación flexible.

¿Qué es la encuadernación suelta? De hecho, es una manifestación del diseño humanizado de la programación springboot, es decir, el formato de nomenclatura en el archivo de configuración y el formato de nomenclatura del nombre de la variable se pueden maximizar en compatibilidad de formato. ¿Hasta qué punto es compatible? Casi todos los principales formatos de nombres son compatibles, por ejemplo:

nombre de la propiedad ipAddress en ServerConfig

@Component
@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
    
    
    private String ipAddress;
}

Puede ser totalmente compatible con las siguientes reglas de nombre de propiedad de configuración

servers:
  ipAddress: 192.168.0.2       # 驼峰模式
  ip_address: 192.168.0.2      # 下划线模式
  ip-address: 192.168.0.2      # 烤肉串模式
  IP_ADDRESS: 192.168.0.2      # 常量模式

También se puede decir que los cuatro patrones anteriores eventualmente pueden coincidir con el nombre de atributo ipAddress. ¿Por qué? La razón es que al hacer coincidir, los nombres en la configuración deben eliminar los guiones y guiones bajos, e ignorar el caso para hacer coincidir los nombres de propiedad en el código java ignorando los equivalentes de caso.Los cuatro nombres anteriores eliminan los guiones bajos y las líneas medias Después de ignorar caso, la línea es la palabra ipaddress, y el nombre del atributo en el código java también es ipaddress después de ignorar el caso, por lo que se puede realizar una coincidencia equivalente, razón por la cual los cuatro formatos pueden coincidir con éxito. Sin embargo, springboot recomienda oficialmente usar el modo kebab, que es el modo tablero.

Aquí hemos dominado un punto de conocimiento, que es el tema de la especificación de nombres. Veamos el mensaje de error de programación que comenzó a aparecer.

Configuration property name 'dataSource' is not valid:

    Invalid characters: 'S'
    Bean: datasource
    Reason: Canonical names should be kebab-case ('-' separated), lowercase alpha-numeric characters and must start with a letter

Action:
Modify 'dataSource' so that it conforms to the canonical names requirements.

​ Entre ellos, Reason describe el motivo del error, y el nombre de la especificación debe ser el modo kebab (case), es decir, separados por -, utilizando caracteres alfanuméricos en minúsculas como caracteres estándar, y debe comenzar con una letra. Luego mire el nombre dataSource que escribimos, que no cumple con los requisitos anteriores.

Finalmente, las reglas anteriores solo son válidas para el enlace de atributos de la anotación @ConfigurationProperties en springboot y no son válidas para el mapeo de atributos de la anotación @Value.

Resumir

  1. @ConfigurationProperties admite la vinculación suelta de nombres de propiedad al vincular propiedades, lo que se refleja en las reglas de denominación de los nombres de propiedad
  2. La anotación @Value no admite reglas de enlace flexible
  3. Se recomienda usar la regla de nomenclatura de kebab para el nombre del prefijo vinculante, es decir, usar un guión como separador
  4. Convención de nomenclatura de prefijos vinculantes: solo se pueden usar letras minúsculas, números y guiones bajos como caracteres válidos

2.3 Vinculación de unidades de medida de uso común

En la configuración anterior, escribimos los siguientes valores de configuración. El tercer timeout timeout describe el tiempo de espera de la operación del servidor. El valor actual es -1, lo que significa que nunca se agota el tiempo.

servers:
  ip-address: 192.168.0.1 
  port: 2345
  timeout: -1

​ Pero la comprensión de este valor por parte de todos será diferente. Por ejemplo, el servidor en línea completa una copia de seguridad maestro-esclavo y configura un período de tiempo de espera de 240. Si la unidad de 240 es segundos, el período de tiempo de espera es de 4 minutos, y si la unidad son minutos, el tiempo de espera es de 4 horas. En este momento, viene el problema, ¿cómo solucionar este malentendido?

​ Además de fortalecer el contrato, Springboot aprovecha al máximo los nuevos tipos de datos proporcionados en JDK8 para representar unidades de medida, resolviendo fundamentalmente este problema. Se agregan dos nuevas clases en JDK8 a las siguientes clases de modelo, a saber, Duration y DataSize

@Component
@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
    
    
    @DurationUnit(ChronoUnit.HOURS)
    private Duration serverTimeOut;
    @DataSizeUnit(DataUnit.MEGABYTES)
    private DataSize dataSize;
}

Duración : indica el intervalo de tiempo, y la unidad de tiempo se puede describir a través de la anotación @DurationUnit, por ejemplo, la unidad descrita en el ejemplo anterior es horas (ChronoUnit.HOURS)

DataSize : indica el espacio de almacenamiento, y la unidad de espacio de almacenamiento se puede describir a través de la anotación @DataSizeUnit, por ejemplo, la unidad descrita en el ejemplo anterior es MB (DataUnit.MEGABYTES)

El uso de las dos unidades anteriores puede evitar de manera efectiva el problema de la asimetría de la información causada por una comunicación desincronizada o documentos incompletos, resolver el problema de manera fundamental y evitar lecturas incorrectas.

Las unidades comunes de Druation son las siguientes:

inserte la descripción de la imagen aquí

Las unidades comunes de DataSize son las siguientes:

inserte la descripción de la imagen aquí

2.4 Verificación de datos

En la actualidad, podemos dejarnos llevar cuando escribimos a través de reglas de vinculación flexible cuando vinculamos atributos, pero debido a que no podemos percibir el tipo de datos en la clase del modelo al escribir, habrá problemas de discrepancia de tipos, como el tipo int requerido en el código , se da un valor ilegal en la configuración, como escribir una "a", este tipo de datos definitivamente no se vincularán de manera efectiva y se generará un error. SpringBoot proporciona una poderosa función de verificación de datos, que puede evitar tales problemas de manera efectiva. Los estándares de verificación de datos específicos se proporcionan en la especificación JSR303 de JAVAEE. Los desarrolladores pueden elegir el marco de verificación correspondiente según sus propias necesidades. Aquí, el marco de verificación proporcionado por Hibernate se utiliza como implementación para la verificación de datos. El formato de solicitud de escritura es muy fijo.

Paso ① : Abra el marco de verificación

<!--1.导入JSR303规范-->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
</dependency>
<!--使用hibernate框架提供的校验器做实现-->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

Paso ② : use la anotación @Validated para habilitar la función de validación en la clase que necesita habilitar la función de validación

@Component
@Data
@ConfigurationProperties(prefix = "servers")
//开启对当前bean的属性注入校验
@Validated
public class ServerConfig {
    
    
}

Paso ③ : Establecer reglas de validación para campos específicos

@Component
@Data
@ConfigurationProperties(prefix = "servers")
//开启对当前bean的属性注入校验
@Validated
public class ServerConfig {
    
    
    //设置具体的规则
    @Max(value = 8888,message = "最大值不能超过8888")
    @Min(value = 202,message = "最小值不能低于202")
    private int port;
}

Al configurar la verificación del formato de datos, puede evitar de manera efectiva la carga ilegal de datos. De hecho, es bastante fácil de usar, básicamente es un formato.

Resumir

  1. Hay 3 pasos para habilitar la función de validación de propiedades de Bean: importar JSR303 y las coordenadas del marco de validación de Hibernate, usar la anotación @Validated para habilitar la función de validación y usar reglas de validación específicas para estandarizar el formato de validación de datos

2.5 Conversión de tipos de datos

Primero describa el problema, la operación normal de conectarse a la base de datos, pero el mensaje que aparece después de ejecutar el programa es que la contraseña es incorrecta.

java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)

​ De hecho, al ver este error, casi todos los alumnos pueden decir que el nombre de usuario y la contraseña no coinciden, es decir, la contraseña se ingresa incorrectamente, pero el problema es que la contraseña no se ingresa incorrectamente. Echemos un vistazo a la configuración. del nombre de usuario y contraseña Cómo se escribe:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
    username: root
    password: 0127

Cuando hablé sobre la inyección de atributos en el capítulo básico, mencioné el conocimiento relacionado con el tipo. En el conocimiento relacionado con los enteros, existe una oración que admite binarios, octales y hexadecimales.

inserte la descripción de la imagen aquí

Este problema radica aquí, porque 0127 es una cadena "0127" a los ojos de los desarrolladores, pero desde el punto de vista de springboot, este es un número y es un número octal. Cuando el fondo usa el tipo Cadena para recibir datos, si se configura un valor entero en el archivo de configuración, primero se procesará como un entero y luego se convertirá en una cadena después de la lectura. Coincidentemente, 0127 se topó con el formato octal, por lo que terminó como el número decimal 87.

Aquí hay dos puntos de atención. Primero, la escritura estándar de cadenas debe estar entre comillas para que sea un hábito. Segundo, preste más atención a los datos que comienzan con 0.

Resumir

  1. La definición de números en el archivo yaml admite el formato de escritura base. Si necesita usar una cadena, use comillas para marcarla claramente.

3. prueba

3.1, propiedades especiales de prueba de carga

El proceso de prueba en sí no es un proceso complicado, pero en muchos casos es necesario simular algunas situaciones en línea o simular algunas situaciones especiales durante la prueba. Si el entorno actual se ha configurado de acuerdo con el entorno en línea, por ejemplo, la siguiente configuración

env:
  maxMemory: 32GB
  minMemory: 16GB

Pero si desea probar la compatibilidad correspondiente ahora, debe probar la siguiente configuración

env:
  maxMemory: 16GB
  minMemory: 8GB

En este momento, ¿podemos modificar la configuración en el código fuente application.yml para probar cada vez que hacemos una prueba? Obviamente no. Es demasiado problemático cambiarlo antes de cada prueba y volver a cambiarlo después de cada prueba. Entonces pensamos que necesitamos crear un conjunto de propiedades temporales en el entorno de prueba para cubrir las propiedades establecidas en nuestro código fuente, de modo que el caso de prueba sea equivalente a un entorno independiente y se pueda probar de forma independiente, lo cual es mucho más conveniente.

propiedades temporales

springboot ya pensó en cómo resolver este tipo de problema para nuestros desarrolladores y proporcionó la entrada de función correspondiente. En el programa de caso de prueba, puede simular propiedades temporales agregando propiedades a la anotación @SpringBootTest, de la siguiente manera:

//properties属性可以为当前测试用例添加临时的属性配置
@SpringBootTest(properties = {
    
    "test.prop=testValue1"})
public class PropertiesAndArgsTest {
    
    

    @Value("${test.prop}")
    private String msg;
    
    @Test
    void testProperties(){
    
    
        System.out.println(msg);
    }
}

Use el atributo de propiedades de la anotación @SpringBootTest para agregar propiedades temporales al caso de prueba actual, cubriendo los valores de propiedad correspondientes en el archivo de configuración del código fuente para la prueba.

Además de la situación anterior, como se mencionó anteriormente al usar la línea de comandos para iniciar el programa springboot, los valores de propiedad también se pueden establecer a través de los parámetros de la línea de comandos. Y al iniciar el programa en línea, generalmente se agrega alguna información de configuración especial. Como personal de operación y mantenimiento, no conocen Java y no saben cómo escribir el formato específico de esta información de configuración. Si nosotros, como desarrolladores, proporcionamos el contenido de escritura correspondiente, ¿podemos probar si la información de configuración es válido de antemano? Era posible en ese momento, o se configuró anotando otra propiedad de @SpringBootTest.

//args属性可以为当前测试用例添加临时的命令行参数
@SpringBootTest(args={
    
    "--test.prop=testValue2"})
public class PropertiesAndArgsTest {
    
    
    
    @Value("${test.prop}")
    private String msg;
    
    @Test
    void testProperties(){
    
    
        System.out.println(msg);
    }
}

Utilice el atributo args de la anotación @SpringBootTest para simular los parámetros de la línea de comandos y probar el caso de prueba actual.

¿Qué pasa si ambos coexisten? De hecho, si piensa en la prioridad de carga de las propiedades de configuración y los parámetros de la línea de comandos, este resultado es evidente. En la configuración de prioridad de la carga de atributos, hay un orden claro de configuración de prioridad. ¿Recuerdas el siguiente orden?

inserte la descripción de la imagen aquí

En el orden de prioridad de la carga de atributos, se estipula claramente que la prioridad de los parámetros de la línea de comandos es 11, mientras que la prioridad de los atributos de configuración es 3. El resultado es evidente que la configuración del atributo args se carga antes que el atributo de propiedades. configuración.

Resumir

  1. Las propiedades temporales de la prueba de carga se pueden establecer anotando las propiedades y las propiedades de los argumentos de @SpringBootTest. El alcance de la aplicación de esta configuración solo se aplica al caso de prueba actual.

3.2 Configuración especial para prueba de carga

Después de estudiar Spring, todos sabemos que, de hecho, se pueden establecer varios archivos de configuración o clases de configuración en un entorno de Spring, y varias informaciones de configuración pueden tener efecto al mismo tiempo. Ahora, nuestro requisito es agregar otra clase de configuración en el entorno de prueba y luego, cuando se inicie el entorno de prueba, esta configuración surtirá efecto. De hecho, el método es exactamente el mismo que la forma de cargar información de configuración múltiple en el entorno Spring. Los pasos específicos de la operación son los siguientes:

Paso ① : Cree una clase de configuración de entorno de prueba dedicada en la prueba del paquete de prueba

@Configuration
public class MsgConfig {
    
    
    @Bean
    public String msg(){
    
    
        return "bean msg";
    }
}

La configuración anterior solo se usa para demostrar el efecto experimental actual, y el desarrollo real no puede inyectar datos de tipo String como este

Paso ② : al iniciar el entorno de prueba, importe la clase de configuración dedicada al entorno de prueba y use la anotación @Import para lograr

@SpringBootTest
@Import({
    
    MsgConfig.class})
public class ConfigurationTest {
    
    

    @Autowired
    private String msg;

    @Test
    void testConfiguration(){
    
    
        System.out.println(msg);
    }
}

En este punto, sobre la base de la configuración basada en el entorno de desarrollo, la operación adicional del entorno de prueba se implementa a través del atributo @Import y se realiza el efecto del entorno de configuración 1+1. De esta forma, podemos lograr el efecto de cargar diferentes beans para cada caso de prueba diferente, enriquecer la escritura de casos de prueba y no afectar la configuración del entorno de desarrollo.

Resumir

  1. Defina una clase de configuración dedicada al entorno de prueba y luego importe configuraciones temporales, como casos de prueba, en pruebas específicas a través de anotaciones @Import para facilitar el proceso de prueba, y las configuraciones anteriores no afectan otros entornos de clase de prueba

3.3, prueba de simulación de entorno web

Probar la funcionalidad de la capa de presentación en las pruebas requiere una base y una funcionalidad. La supuesta base es que cuando se ejecuta el programa de prueba, se debe iniciar el entorno web, de lo contrario, la función web no se puede probar. Una función es que el programa de prueba debe tener la capacidad de enviar solicitudes web, de lo contrario no se puede realizar la prueba de funciones web. Entonces, el trabajo de probar la interfaz de la capa de presentación en el caso de prueba se transforma en dos cosas, una, cómo iniciar la prueba web en la clase de prueba y dos, cómo enviar la solicitud web en la clase de prueba.

Inicie el entorno web en la clase de prueba.

Cada clase de prueba Springboot tendrá una anotación @SpringBootTest estándar, y la anotación tiene un atributo llamado webEnvironment. A través de esta propiedad, se puede configurar el entorno web para que se inicie en el caso de prueba, de la siguiente manera:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WebTest {
    
    	
}

Al iniciar el entorno web en la clase de prueba, puede especificar el puerto correspondiente al entorno web de inicio. Springboot proporciona 4 valores de configuración, que son los siguientes:

  • MOCK: confirme si desea iniciar el entorno web de acuerdo con la configuración actual. Por ejemplo, si se utiliza la API de Servlet para iniciar el entorno web, es una configuración adaptativa
  • DEFINED_PORT: utilice un puerto personalizado como puerto del servidor web
  • RANDOM_PORT: use un puerto aleatorio como puerto del servidor web
  • NONE: no iniciar el entorno web

A través de la configuración anterior, el entorno web se puede habilitar normalmente al iniciar el programa de prueba. Se recomienda que use RANDOM_PORT al realizar pruebas para evitar fenómenos inesperados debido a conflictos de puertos durante las pruebas de paquetes de funciones en línea causados ​​por configuraciones codificadas en el código. Significa que el puerto 8080 está escrito en su programa, pero el puerto 8080 en el entorno en línea está ocupado y todo lo que está escrito en su código tendrá que cambiarse. Este es el precio de escribir código muerto. Ahora puede usar un puerto aleatorio para probar si tiene algún peligro oculto de este tipo de problema.

测试环境中的web环境已经搭建好了,下面就可以来解决第二个问题了,如何在程序代码中发送web请求。

测试类中发送请求

​ 对于测试类中发送请求,其实java的API就提供对应的功能,只不过平时各位小伙伴接触的比较少,所以较为陌生。springboot为了便于开发者进行对应的功能开发,对其又进行了包装,简化了开发步骤,具体操作如下:

步骤①:在测试类中开启web虚拟调用功能,通过注解@AutoConfigureMockMvc实现此功能的开启

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//开启虚拟MVC调用
@AutoConfigureMockMvc
public class WebTest {
    
    
}

步骤②:定义发起虚拟调用的对象MockMVC,通过自动装配的形式初始化对象

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//开启虚拟MVC调用
@AutoConfigureMockMvc
public class WebTest {
    
    

    @Test
    void testWeb(@Autowired MockMvc mvc) {
    
    
    }
}

步骤③:创建一个虚拟请求对象,封装请求的路径,并使用MockMVC对象发送对应请求

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//开启虚拟MVC调用
@AutoConfigureMockMvc
public class WebTest {
    
    

    @Test
    void testWeb(@Autowired MockMvc mvc) throws Exception {
    
    
        //http://localhost:8080/books
        //创建虚拟请求,当前访问/books
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        //执行对应的请求
        mvc.perform(builder);
    }
}

​ 执行测试程序,现在就可以正常的发送/books对应的请求了,注意访问路径不要写http://localhost:8080/books,因为前面的服务器IP地址和端口使用的是当前虚拟的web环境,无需指定,仅指定请求的具体路径即可。

总结

  1. 在测试类中测试web层接口要保障测试类启动时启动web容器,使用@SpringBootTest注解的webEnvironment属性可以虚拟web环境用于测试
  2. 为测试方法注入MockMvc对象,通过MockMvc对象可以发送虚拟请求,模拟web请求调用过程

web环境请求结果比对

​我们已经在测试用例中成功的模拟出了web环境,并成功的发送了web请求,现在就来解决发送请求后如何比对发送结果的问题。其实发完请求得到的信息只有一种,就是响应对象。至于响应对象中包含什么,就可以比对什么。常见的比对内容如下:

  • 响应状态匹配

    @Test
    void testStatus(@Autowired MockMvc mvc) throws Exception {
          
          
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        ResultActions action = mvc.perform(builder);
        //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
        //定义本次调用的预期值
        StatusResultMatchers status = MockMvcResultMatchers.status();
        //预计本次调用时成功的:状态200
        ResultMatcher ok = status.isOk();
        //添加预计值到本次调用过程中进行匹配
        action.andExpect(ok);
    }
    
  • 响应体匹配(非json数据格式)

    @Test
    void testBody(@Autowired MockMvc mvc) throws Exception {
          
          
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        ResultActions action = mvc.perform(builder);
        //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
        //定义本次调用的预期值
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.string("springboot2");
        //添加预计值到本次调用过程中进行匹配
        action.andExpect(result);
    }
    
  • 响应体匹配(json数据格式,开发中的主流使用方式)

    @Test
    void testJson(@Autowired MockMvc mvc) throws Exception {
          
          
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        ResultActions action = mvc.perform(builder);
        //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
        //定义本次调用的预期值
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot2\",\"type\":\"springboot\"}");
        //添加预计值到本次调用过程中进行匹配
        action.andExpect(result);
    }
    
  • 响应头信息匹配

    @Test
    void testContentType(@Autowired MockMvc mvc) throws Exception {
          
          
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        ResultActions action = mvc.perform(builder);
        //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
        //定义本次调用的预期值
        HeaderResultMatchers header = MockMvcResultMatchers.header();
        ResultMatcher contentType = header.string("Content-Type", "application/json");
        //添加预计值到本次调用过程中进行匹配
        action.andExpect(contentType);
    }
    

Básicamente, todo está completo, la información del encabezado, la información del cuerpo y la información de estado están todas allí, y se puede combinar un resultado de comparación de resultados de respuesta perfecto. El siguiente ejemplo es que se cotejan y verifican tres tipos de información al mismo tiempo, lo que también es un proceso completo de cotejo de información.

@Test
void testGetById(@Autowired MockMvc mvc) throws Exception {
    
    
    MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
    ResultActions action = mvc.perform(builder);

    StatusResultMatchers status = MockMvcResultMatchers.status();
    ResultMatcher ok = status.isOk();
    action.andExpect(ok);

    HeaderResultMatchers header = MockMvcResultMatchers.header();
    ResultMatcher contentType = header.string("Content-Type", "application/json");
    action.andExpect(contentType);

    ContentResultMatchers content = MockMvcResultMatchers.content();
    ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot\",\"type\":\"springboot\"}");
    action.andExpect(result);
}

Resumir

  1. La llamada virtual web puede comparar la información de respuesta devuelta por la solicitud virtual local, que se divide en comparación de información de encabezado de respuesta, comparación de información de cuerpo de respuesta y comparación de información de estado de respuesta.

3.4 Reversión de la prueba de la capa de datos

En la actualidad, nuestro programa de prueba puede realizar perfectamente las pruebas funcionales correspondientes a la interfaz de la capa de presentación, la capa comercial y la capa de datos, sin embargo, una vez que se completa el desarrollo del caso de prueba, en la etapa de empaque, debido a que el ciclo de vida de la prueba pertenece a la ciclo de vida que se debe ejecutar, si se salta traerá un riesgo de seguridad muy alto al sistema, por lo que se debe ejecutar el caso de prueba. Pero surge un nuevo problema: si el caso de prueba genera una confirmación de transacción durante la prueba, tendrá un impacto en los datos de la base de datos durante la prueba y luego generará datos basura. Este proceso no es lo que queremos que suceda. Debe ejecutarse como un caso de prueba del desarrollador, pero los datos generados durante el proceso no deben dejar rastros en mi sistema. ¿Cómo debo manejarlo?

​springboot ha pensado durante mucho tiempo en este problema para los desarrolladores y ha dado la solución más simple a este problema. Agregar la anotación @Transactional al caso de prueba original puede darse cuenta de que la transacción del caso de prueba actual no está confirmada. Cuando el programa se está ejecutando, siempre que haya una anotación @SpringBootTest donde aparezca la anotación @Transactional, Springboot pensará que se trata de un programa de prueba y que no es necesario confirmar la transacción, por lo que se puede evitar el envío de la transacción.

@SpringBootTest
@Transactional
@Rollback(true)
public class DaoTest {
    
    
    @Autowired
    private BookService bookService;

    @Test
    void testSave(){
    
    
        Book book = new Book();
        book.setName("springboot3");
        book.setType("springboot3");
        book.setDescription("springboot3");

        bookService.save(book);
    }
}

Si el desarrollador desea enviar la transacción, también puede agregar una anotación @RollBack y establecer el estado de reversión en falso para enviar la transacción normalmente.

Resumir

  1. Evite que el caso de prueba envíe la transacción agregando la anotación @Transactional en la clase de prueba springboot
  2. Use la anotación @Rollback para controlar si el resultado de la ejecución de la clase de prueba springboot envía la transacción, que debe usarse con la anotación @Transactional

3.5 Configuración de datos del caso de prueba

Definitivamente no es razonable escribir datos fijos para casos de prueba, springboot proporciona un mecanismo para usar valores aleatorios en la configuración para garantizar que los datos cargados cada vez que se ejecuta el programa sean aleatorios. detalles de la siguiente manera:

testcase:
  book:
    id: ${
    
    random.int}
    id2: ${
    
    random.int(10)}
    type: ${
    
    random.int!5,10!}
    name: ${
    
    random.value}
    uuid: ${
    
    random.uuid}
    publishTime: ${
    
    random.long}

​ La configuración actual puede crear un conjunto de datos aleatorios cada vez que se ejecuta el programa, evitando el embarazoso fenómeno de que los datos son un valor fijo cada vez que se ejecuta, y es útil para probar funciones. La carga de datos es en forma de los datos previamente cargados, utilizando la anotación @ConfigurationProperties

@Component
@Data
@ConfigurationProperties(prefix = "testcase.book")
public class BookCase {
    
    
    private int id;
    private int id2;
    private int type;
    private String name;
    private String uuid;
    private long publishTime;
}

Para la generación de valores aleatorios, existen algunas pequeñas restricciones, como que se puede establecer el rango de los datos numéricos generados, etc., de la siguiente manera:

inserte la descripción de la imagen aquí

  • ${random.int} representa un entero aleatorio
  • ${random.int(10)} significa un número aleatorio dentro de 10
  • ${random.int(10,20)} representa un número aleatorio del 10 al 20
  • Donde () puede ser cualquier carácter, como [],!!

Resumir

  1. El uso de datos aleatorios puede reemplazar los datos fijos escritos en el caso de prueba para mejorar la validez de los datos de prueba en el caso de prueba

4. Solución de capa de datos

4.1, SQL

Se puede decir que la solución de capa de datos es Mysql+Druid+MyBatisPlus. Las tres tecnologías corresponden a los tres niveles de operaciones de la capa de datos:

  • Tecnología de fuente de datos: Druid
  • Tecnología de persistencia: MyBatisPlus
  • Tecnología de base de datos: MySQL

La siguiente investigación se divide en tres niveles de investigación, correspondientes a los tres aspectos enumerados anteriormente, comencemos con la primera tecnología de fuente de datos.

4.1.1 Tecnología de la fuente de datos

Actualmente, la tecnología de fuente de datos que utilizamos es Druid, y la información de inicialización de la fuente de datos correspondiente se puede ver en el registro durante el tiempo de ejecución, de la siguiente manera:

INFO 28600 --- [           main] c.a.d.s.b.a.DruidDataSourceAutoConfigure : Init DruidDataSource
INFO 28600 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {
    
    dataSource-1} inited

​ Si no se utiliza la fuente de datos de Druid, ¿cómo se ve el programa después de ejecutarse? ¿Es un objeto de conexión de base de datos independiente o hay otro soporte técnico de grupo de conexiones? Retire el arrancador correspondiente a la tecnología Druid y vuelva a ejecutar el programa para encontrar la siguiente información de inicialización en el registro:

INFO 31820 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
INFO 31820 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.

​ Aunque no hay información relacionada con DruidDataSource, encontramos que hay información sobre HikariDataSource en el registro. Incluso si no sabe qué tecnología es, puede saberlo por el nombre. El nombre que termina con DataSource debe ser un tecnología de fuente de datos. No agregamos manualmente esta tecnología, ¿de dónde vino esta tecnología? Este es el conocimiento que se discutirá en esta sección, la fuente de datos incrustada Springboot.

La tecnología de capa de datos es utilizada por todas las aplicaciones de nivel empresarial, y la administración de la conexión de la base de datos debe realizarse entre ellas. Springboot parte de los hábitos del desarrollador. Si el desarrollador proporciona la tecnología de fuente de datos, use lo que usted proporciona. Si el desarrollador no la proporciona, entonces no puede administrar manualmente los objetos de conexión de la base de datos uno por uno. ¿Qué debo hacer? Solo te daré uno predeterminado.

springboot proporciona 3 tecnologías de fuentes de datos integradas, que son las siguientes:

  • HikariCP
  • Tomcat proporciona fuente de datos
  • Comunes DBCP

La primera, HikartCP, es la tecnología de fuente de datos recomendada oficialmente por springboot y se utiliza como fuente de datos integrada predeterminada. ¿Qué quieres decir? Si no configura la fuente de datos, entonces use esto.

​ El segundo es el DataSource proporcionado por Tomcat.Si no desea utilizar HikartCP y utilizar Tomcat como servidor web para el desarrollo de programas web, utilícelo. ¿Por qué Tomcat y no otros servidores web? Porque después de que la tecnología web se importa al iniciador, el tomcat incorporado se usa de forma predeterminada. Dado que es la tecnología utilizada de forma predeterminada, se usará como fuente de datos una vez que se use hasta el final. ¿Alguien propuso cómo usar el objeto de fuente de datos predeterminado proporcionado por tomcat sin usar HikartCP? Está bien excluir las coordenadas de la tecnología HikartCP.

​ El tercer tipo, DBCP, tiene condiciones de uso más estrictas. Cuando no se usa HikartCP ni el DataSource de Tomcat, se usa por defecto para usted.

​ El corazón de springboot también está fragmentado. Me temo que no puede administrar los objetos de conexión usted mismo. Le daré una recomendación. Es realmente la ayuda más fuerte en el mundo del desarrollo. Como se te ha dado todo, usémoslo. ¿Cómo configurar y usar estas cosas? Cuando configuramos druid antes, la configuración correspondiente al starter de druid es la siguiente:

spring:
  datasource:
    druid:	
   	  url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: root

Después de cambiar a la fuente de datos predeterminada HikariCP, simplemente elimine el druida directamente, de la siguiente manera:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root

Por supuesto, también se puede escribir como una configuración para hikari, pero la dirección URL debe configurarse por separado, de la siguiente manera:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
    hikari:
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: root

Así es como se configura la fuente de datos hikari. Si desea seguir configurando hikari, puede continuar configurando sus propiedades independientes. Por ejemplo:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
    hikari:
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: root
      maximum-pool-size: 50

​ 如果不想使用hikari数据源,使用tomcat的数据源或者DBCP配置格式也是一样的。学习到这里,以后我们做数据层时,数据源对象的选择就不再是单一的使用druid数据源技术了,可以根据需要自行选择。

总结

  1. springboot技术提供了3种内置的数据源技术,分别是Hikari、tomcat内置数据源、DBCP

4.1.2、持久化技术

说完数据源解决方案,再来说一下持久化解决方案。springboot充分发挥其最强辅助的特征,给开发者提供了一套现成的数据层技术,叫做JdbcTemplate。其实这个技术不能说是springboot提供的,因为不使用springboot技术,一样能使用它,谁提供的呢?spring技术提供的,所以在springboot技术范畴中,这个技术也是存在的,毕竟springboot技术是加速spring程序开发而创建的。

​ 这个技术其实就是回归到jdbc最原始的编程形式来进行数据层的开发,下面直接上操作步骤:

步骤①:导入jdbc对应的坐标,记得是starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency

步骤②:自动装配JdbcTemplate对象

@SpringBootTest
class Springboot15SqlApplicationTests {
    
    
    @Test
    void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){
    
    
    }
}

步骤③:使用JdbcTemplate实现查询操作(非实体类封装数据的查询操作)

@Test
void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){
    
    
    String sql = "select * from tbl_book";
    List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
    System.out.println(maps);
}

步骤④:使用JdbcTemplate实现查询操作(实体类封装数据的查询操作)

@Test
void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){
    
    

    String sql = "select * from tbl_book";
    RowMapper<Book> rm = new RowMapper<Book>() {
    
    
        @Override
        public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
    
    
            Book temp = new Book();
            temp.setId(rs.getInt("id"));
            temp.setName(rs.getString("name"));
            temp.setType(rs.getString("type"));
            temp.setDescription(rs.getString("description"));
            return temp;
        }
    };
    List<Book> list = jdbcTemplate.query(sql, rm);
    System.out.println(list);
}

步骤⑤:使用JdbcTemplate实现增删改操作

@Test
void testJdbcTemplateSave(@Autowired JdbcTemplate jdbcTemplate){
    
    
    String sql = "insert into tbl_book values(3,'springboot1','springboot2','springboot3')";
    jdbcTemplate.update(sql);
}

​ 如果想对JdbcTemplate对象进行相关配置,可以在yml文件中进行设定,具体如下:

spring:
  jdbc:
    template:
      query-timeout: -1   # 查询超时时间
      max-rows: 500       # 最大行数
      fetch-size: -1      # 缓存行数

总结

  1. SpringBoot内置JdbcTemplate持久化解决方案
  2. 使用JdbcTemplate需要导入spring-boot-starter-jdbc的坐标

4.1.3、数据库技术

Hasta ahora, Springboot ha brindado a los desarrolladores soluciones integradas de fuente de datos y soluciones de persistencia. Solo queda una base de datos en la solución de capa de datos de tres piezas. ¿Springboot también brinda soluciones integradas? Realmente no hay una, sino 3. Esta sección hablará sobre las soluciones de bases de datos integradas.

springboot proporciona 3 bases de datos integradas, a saber

  • H2
  • HSQL
  • derby

​ Además de instalarse de forma independiente, las tres bases de datos anteriores también pueden ejecutarse en el contenedor spirngboot de forma integrada como un servidor tomcat. Para ejecutarse incrustado en el contenedor, debe ser un objeto Java. Sí, las capas inferiores de estas tres bases de datos se desarrollan utilizando el lenguaje Java.

Hemos estado usando muy bien la base de datos MySQL, ¿por qué es necesario usar esto? La razón es que estas tres bases de datos pueden ejecutarse en forma de contenedores incrustados.Después de que la aplicación se ejecuta, si realizamos un trabajo de prueba, los datos de prueba no necesitan almacenarse en el disco en este momento, pero la base de datos incrustada es usado para pruebas Es conveniente, se ejecuta en la memoria, es hora de probar, es hora de ejecutar, y cuando el servidor se apaga, todo desaparece, lo cual es excelente, lo que le ahorra el mantenimiento de una base de datos externa. Esta es también la mayor ventaja de la base de datos integrada, que es conveniente para las pruebas funcionales.

Tomemos la base de datos H2 como ejemplo para explicar cómo usar estas bases de datos integradas. Los pasos de operación también son muy simples. Simple es fácil de usar.

Paso ① : Importe las coordenadas correspondientes a la base de datos H2, un total de 2

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Paso ② : configure el proyecto como un proyecto web e inicie la base de datos H2 al iniciar el proyecto

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Paso ③ : Abra el programa de acceso a la consola de la base de datos H2 a través de la configuración, o use otro software de conexión a la base de datos para operar

spring:
  h2:
    console:
      enabled: true
      path: /h2

​ Ruta de acceso web /h2, contraseña de acceso 123456, si el acceso falla, primero configure las siguientes fuentes de datos, inicie el programa y luego acceda a la ruta /h2 nuevamente para acceder normalmente

datasource:
  url: jdbc:h2:~/test
  hikari:
    driver-class-name: org.h2.Driver
    username: sa
    password: 123456

Paso ④ : Use la tecnología JdbcTemplate o MyBatisPlus para operar la base de datos

@Test
void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){
    
    
    String sql = "select * from tbl_book";
    List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
    System.out.println(maps);
}

De hecho, acabamos de cambiar una base de datos y otras cosas no se ven afectadas. Un recordatorio importante, no olvide, cuando se conecte, cierre la base de datos de nivel de memoria y use la base de datos MySQL como la solución de persistencia de datos. La forma de cerrar es establecer la propiedad habilitada en falso.

Resumir

  1. Método de inicio de la base de datos integrada H2, agregar coordenadas, agregar configuración
  2. Asegúrese de cerrar la base de datos H2 cuando se ejecuta en línea

Este es el final de las soluciones de capa de datos relacionadas con SQL y ahora las tecnologías opcionales son mucho más ricas.

  • Tecnología de fuente de datos: Druid, Hikari, tomcat DataSource, DBCP
  • Tecnología de persistencia: MyBatisPlus, MyBatis, JdbcTemplate
  • Tecnología de base de datos: MySQL, H2, HSQL, Derby

Ahora, al desarrollar un programa, puede elegir una de las tecnologías anteriores para organizar un conjunto de soluciones de base de datos.

4.2, No SQL

Ahora que la solución de la capa de datos SQL está terminada, hablemos de la solución de la capa de datos NoSQL. ¿Qué significa este NoSQL? Literalmente, No significa negativo. NoSQL es una solución de base de datos no relacional, lo que significa que los datos deben almacenarse y recuperarse, pero estos datos no se almacenan en una base de datos relacional. ¿Dónde deben colocarse? Naturalmente, se encuentra en algunas otras tecnologías relacionadas que pueden almacenar datos, como Redis. El contenido de esta sección es cómo Springboot integra estas tecnologías. Los documentos oficiales de Springboot brindan 10 soluciones de integración para tecnologías relacionadas. Explicaremos las soluciones de integración de bases de datos NoSQL más populares en el mercado nacional, a saber, Redis, MongoDB, ES.

4.2.1, SpringBoot integra Redis

Redis es una base de datos NoSQL en memoria que utiliza el formato de almacenamiento de datos clave-valor Se centra en el formato de almacenamiento de datos, que es el formato clave-valor, que es la forma de almacenamiento de pares clave-valor. A diferencia de la base de datos MySQL, la base de datos MySQL tiene tablas, campos y registros, Redis no tiene estas cosas, es decir, un nombre corresponde a un valor, y los datos se almacenan principalmente en la memoria. ¿Qué se almacena principalmente en la memoria? De hecho, Redis tiene sus soluciones de persistencia de datos, que son RDB y AOF, pero Redis en sí no nació para la persistencia de datos, principalmente guarda datos en la memoria y acelera el acceso a los datos, por lo que es una base de datos a nivel de memoria.

Redis es compatible con una variedad de formatos de almacenamiento de datos. Por ejemplo, puede almacenar cadenas directamente, o puede almacenar una colección de mapas, una colección de listas y algunas operaciones de datos en diferentes formatos estarán involucradas más adelante. Esto debe aprenderse antes de que pueda ser integrado, por lo que en la operación básica Algunas operaciones relacionadas se introducirán en . Primero instalemos, luego operemos y finalmente integremos

4.2.1.1 Instalación

Dirección de descarga del paquete de instalación de la versión de Windows: https://github.com/tporadowski/redis/releases

Hay dos formas del paquete de instalación descargado, una es un archivo msi para la instalación con un solo clic y la otra es un archivo zip que se puede usar después de la descompresión, cualquiera de las formas está bien, aquí está el archivo msi para la instalación con un solo clic instalación de msi para la instalación.

​ ¿Qué es msi? De hecho, es un paquete de instalación de archivos. No solo instala el software, sino que también lo ayuda a asociar las funciones necesarias para instalar el software y empaquetarlas para su funcionamiento. Los ejemplos incluyen secuencias de instalación, creación y configuración de rutas de instalación, configuración de dependencias del sistema, configuración de opciones de instalación predeterminadas y propiedades que controlan el proceso de instalación. En pocas palabras, es un servicio integral y el proceso de instalación se completa de una sola vez.

Una vez completada la instalación, obtendrá los siguientes archivos: hay dos archivos que corresponden a dos comandos, que son los comandos principales para iniciar Redis y deben ejecutarse en el modo de línea de comandos de CMD.

servidor de inicio

redis-server.exe redis.windows.conf

iniciar el cliente

redis-cli.exe

Si no puede iniciar el servidor Redis, puede iniciar el cliente primero, luego ejecutar la operación de apagado y salir, y luego el servidor Redis se puede ejecutar normalmente.

4.2.1.2 Funcionamiento básico

Una vez que se inicia el servidor, puede conectarse al servidor utilizando el cliente, similar a iniciar la base de datos MySQL y luego iniciar la línea de comando SQL para operar la base de datos.

​ Ponga una cadena de datos en redis, primero defina un nombre para los datos, como nombre, edad, etc., y luego use el conjunto de comandos para configurar los datos en el servidor redis

set name test
set age 12

Saque los datos que se han ingresado desde redis y obtenga los datos correspondientes según el nombre. Si no hay datos correspondientes, obtendrá (nulo)

get name
get age

El almacenamiento de datos utilizado anteriormente es un nombre que corresponde a un valor. Si hay demasiados datos para mantener, se pueden utilizar otras estructuras de almacenamiento de datos. Por ejemplo, hash es un modelo de almacenamiento que puede almacenar varios datos con un solo nombre y cada dato también puede tener su propio nombre de almacenamiento secundario. El formato de almacenamiento de datos en la estructura hash es el siguiente:

hset a a1 aa1		#对外key名称是a,在名称为a的存储模型中,a1这个key中保存了数据aa1
hset a a2 aa2

El comando para obtener los datos en la estructura hash es el siguiente

hget a a1			#得到aa1
hget a a2			#得到aa2

4.2.1.3 Integración

Antes de la integración, aclaremos la idea de la integración. Integrar cualquier tecnología con springboot es en realidad usar la API de la tecnología correspondiente en springboot. Si dos tecnologías no se cruzan, no hay concepto de integración. La llamada integración en realidad utiliza la tecnología springboot para administrar otras tecnologías, y no se pueden evitar varios problemas.

Primero, primero debe importar las coordenadas de la tecnología correspondiente, y después de la integración, estas coordenadas tienen algunos cambios.

En segundo lugar, cualquier tecnología suele tener alguna información de configuración relacionada.Después de la integración, cómo escribir esta información y dónde escribirla es un problema.

En tercer lugar, si la operación es el modo A antes de la integración, si la integración no brinda algunas operaciones convenientes para los desarrolladores, entonces la integración no tendrá sentido, por lo que la operación después de la integración debe simplificarse y los métodos de operación correspondientes son naturalmente disponible diferente

De acuerdo con las tres preguntas anteriores, es una idea general pensar en la integración de todas las tecnologías en springboot.Durante el proceso de integración, la rutina de integración se explorará gradualmente y la aplicabilidad es muy fuerte.Después de la integración de varias tecnologías , básicamente se puede concluir que Una mentalidad fija.

Comencemos springboot para integrar redis, los pasos de la operación son los siguientes:

Paso ① : importe springboot para integrar las coordenadas iniciales de redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Las coordenadas anteriores se pueden seleccionar comprobando al crear un módulo y pertenecen a la categoría NoSQL

Paso ② : Realice la configuración básica

spring:
  redis:
    host: localhost
    port: 6379

​ Para operar redis, la información más básica es qué servidor redis operar, por lo que la dirección del servidor pertenece a la información de configuración básica y es indispensable. Pero incluso si no lo configura, aún se puede usar en la actualidad. Porque los dos conjuntos de información anteriores tienen configuraciones predeterminadas, que resultan ser los valores de configuración anteriores.

Paso ③ : use springboot para integrar la operación de interfaz de cliente dedicada de redis, aquí está RedisTemplate

@SpringBootTest
class Springboot16RedisApplicationTests {
    
    
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void set() {
    
    
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("age",41);
    }
    @Test
    void get() {
    
    
        ValueOperations ops = redisTemplate.opsForValue();
        Object age = ops.get("name");
        System.out.println(age);
    }
    @Test
    void hset() {
    
    
        HashOperations ops = redisTemplate.opsForHash();
        ops.put("info","b","bb");
    }
    @Test
    void hget() {
    
    
        HashOperations ops = redisTemplate.opsForHash();
        Object val = ops.get("info", "b");
        System.out.println(val);
    }
}

Al operar redis, debe confirmar qué tipo de datos operar y obtener la interfaz de operación de acuerdo con el tipo de datos. Por ejemplo, use opsForValue() para obtener la interfaz de operación de datos de tipo cadena, use opsForHash() para obtener la interfaz de operación de datos de tipo hash, y el resto es llamar a la operación api correspondiente. Varios tipos de interfaces de operación de datos son los siguientes:

inserte la descripción de la imagen aquí

Resumir

  1. pasos de redis de integración springboot
    1. Importe springboot para integrar las coordenadas iniciales de redis
    2. Realizar configuración básica
    3. Use springboot para integrar la operación RedisTemplate de la interfaz de cliente dedicada de redis

StringRedisTemplate

Dado que redis no proporciona el formato de almacenamiento de los objetos java internamente, cuando los datos operados existen en forma de objetos, se transcodificarán y convertirán en formato de cadena para la operación. Para facilitar a los desarrolladores el uso de operaciones de datos basadas en cadenas, springboot proporciona una interfaz de API dedicada StringRedisTemplate al integrar redis. Puede comprender que se trata de una API de operación específica de datos genéricos de RedisTemplate.

@SpringBootTest
public class StringRedisTemplateTest {
    
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Test
    void get(){
    
    
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String name = ops.get("name");
        System.out.println(name);
    }
}

selección de cliente redis

Springboot integra la tecnología redis para proporcionar una variedad de modos de compatibilidad de clientes. La tecnología de cliente lettucs se proporciona de forma predeterminada y también se puede cambiar a una tecnología de cliente específica según sea necesario, como la tecnología de cliente jedis. Los pasos de operación para cambiar a cliente jedis tecnología son las siguientes:

Paso ① : Importar coordenadas jedis

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

Las coordenadas de Jedis son administradas por springboot, no es necesario proporcionar el número de versión

Paso ② : configure el tipo de tecnología del cliente y configúrelo en jedis

spring:
  redis:
    host: localhost
    port: 6379
    client-type: jedis

Paso ③ : establezca la configuración correspondiente según sea necesario

spring:
  redis:
    host: localhost
    port: 6379
    client-type: jedis
    lettuce:
      pool:
        max-active: 16
    jedis:
      pool:
        max-active: 16

La diferencia entre letcus y jedis.

  • Jedis se conecta al servidor de Redis en modo de conexión directa. Al usar jedis en modo de subprocesos múltiples, habrá problemas de seguridad de subprocesos. La solución puede ser configurar el conjunto de conexiones para que cada conexión sea dedicada, de modo que el rendimiento general sea mucho mayor. afectado.
  • Lettcus está conectado al servidor Redis basado en el marco Netty y StatefulRedisConnection se usa en el diseño subyacente. StatefulRedisConnection en sí mismo es seguro para subprocesos y puede garantizar problemas de seguridad de acceso simultáneos, por lo que varios subprocesos pueden reutilizar una conexión. Por supuesto, lettcus también admite múltiples instancias de conexión para trabajar juntas

Resumir

  1. Springboot integra redis para proporcionar un objeto StringRedisTemplate para operar redis en el formato de datos de cadena
  2. Si necesita cambiar la tecnología de implementación del cliente redis, puede hacerlo en forma de configuración

4.2.2 SpringBoot integra MongoDB

MongoDB es una base de datos de documentos de código abierto, de alto rendimiento y sin esquemas. Es uno de los productos de base de datos NoSQL y es la base de datos no relacional más similar a las bases de datos relacionales.

Varias palabras en la descripción anterior, las palabras más desconocidas para nosotros no tienen patrón. ¿Qué es sin modelo? En pocas palabras, como base de datos, no existe una estructura fija de almacenamiento de datos. El primer dato puede tener 3 campos A, B y C, el segundo dato puede tener D, E y F también son 3 campos, y el tercer dato puede tener tres campos: Los datos pueden ser campos A, C, E3, es decir, la estructura de los datos no es fija, que es amodal. Algunas personas dirán ¿cuál es el uso de esto? Flexible, cambia en cualquier momento, sin restricciones. Según las características anteriores, el lado de la aplicación de MongoDB también sufrirá algunos cambios. A continuación, se enumeran algunos escenarios en los que se puede usar MongoDB como almacenamiento de datos, pero no es necesario usar MongoDB:

  • Datos de usuario de Taobao
    • Ubicación de almacenamiento: base de datos
    • Características: almacenamiento permanente, frecuencia de modificación muy baja
  • Datos del equipo del juego, datos de accesorios del juego
    • Ubicación de almacenamiento: base de datos, Mongodb
    • Características: combinación de almacenamiento permanente y almacenamiento temporal, alta frecuencia de modificación
  • Datos de transmisión en vivo, datos de recompensa, datos de fanáticos
    • Ubicación de almacenamiento: base de datos, Mongodb
    • Características: combinación de almacenamiento permanente y almacenamiento temporal, alta frecuencia de modificación
  • datos IoT
    • Ubicación de almacenamiento: Mongodb
    • Características: almacenamiento temporal, frecuencia de modificación rápida

4.2.2.1 Instalación

Dirección de descarga del paquete de instalación de la versión de Windows: https://www.mongodb.com/try/download

​ El paquete de instalación descargado también tiene dos formas, una es el archivo msi para la instalación con un solo clic y la otra es el archivo zip que se puede usar después de la descompresión. Cualquier forma está bien. Aquí, el archivo zip de descompresión se usa para la instalación .

Después de la descompresión, obtendrá los siguientes archivos, en los que el directorio bin contiene todos los comandos ejecutables de mongodb

inserte la descripción de la imagen aquí

mongodb necesita especificar un directorio de almacenamiento de datos cuando se ejecuta, así que cree un directorio de almacenamiento de datos, generalmente ubicado en el directorio de instalación, y cree un directorio de datos aquí para almacenar datos, de la siguiente manera

inserte la descripción de la imagen aquí

Si aparece el siguiente mensaje de advertencia durante el proceso de instalación, le indica que a su sistema operativo actual le faltan algunos archivos del sistema, así que no se preocupe.

inserte la descripción de la imagen aquí

De acuerdo con las siguientes soluciones, puede buscar el archivo correspondiente al nombre que falta en el navegador y descargarlo. Copie el archivo descargado en el directorio system32 del directorio de instalación de Windows y luego ejecute el comando regsvr32 en la línea de comando. para registrar este archivo. Dependiendo del nombre del archivo descargado, cambie el nombre correspondiente antes de ejecutar el comando.

regsvr32 vcruntime140_1.dll

servidor de inicio

mongod --dbpath=..\data\db

Al iniciar el servidor, debe especificar la ubicación de almacenamiento de datos, establecerla a través del parámetro –dbpath, y puede configurar la ruta de almacenamiento de datos según sus necesidades. El puerto de servicio predeterminado es 27017.

iniciar el cliente

mongo --host=127.0.0.1 --port=27017

4.2.2.2 Funcionamiento básico

​ Aunque MongoDB es una base de datos, su operación no utiliza instrucciones SQL, por lo que es posible que no esté familiarizado con el método de operación. Afortunadamente, existen algunos software de cliente de base de datos similares a Navicat, que pueden operar MongoDB fácilmente. Instale un cliente y luego opere Mongo DB.

​ Hay muchos programas del mismo tipo. El software instalado esta vez es Robo3t. Robot3t es un software ecológico, no es necesario instalarlo, solo descomprímalo. Después de la descompresión, ingrese al directorio de instalación y haga doble clic en robot3t.exe para usarlo.

inserte la descripción de la imagen aquí

Para abrir el software, primero debe conectarse al servidor MongoDB, seleccionar el menú [Archivo] y seleccionar [Conectar…]

inserte la descripción de la imagen aquí

Después de ingresar a la interfaz de administración de conexión, seleccione el enlace [Crear] en la esquina superior izquierda para crear una nueva configuración de conexión

inserte la descripción de la imagen aquí

Si ingresa el valor de configuración, puede conectarse (de manera predeterminada, puede conectarse al puerto 27017 de la máquina sin modificaciones)

inserte la descripción de la imagen aquí

Después de que la conexión sea exitosa, ingrese los comandos en el área de entrada de comandos para operar MongoDB.

​ Crear una base de datos: use el botón derecho para crear en el menú de la izquierda, ingrese el nombre de la base de datos

​Crear una colección: haga clic derecho en Colecciones para crear, solo ingrese el nombre de la colección, la colección es equivalente al rol de la tabla en la base de datos

Nuevo documento: (el documento es un tipo de datos similar al formato json)

db.集合名称.insert/save/insertOne(文档)

Eliminar documento:

db.集合名称.remove(条件)

Modificar el documento:

db.集合名称.update(条件,{操作种类:{文档}})

Consulta de documentos:

基础查询
查询全部:		   db.集合.find();
查第一条:		   db.集合.findOne()
查询指定数量文档:	db.集合.find().limit(10)					//查10条文档
跳过指定数量文档:	db.集合.find().skip(20)					//跳过20条文档
统计:			  	db.集合.count()
排序:				db.集合.sort({age:1})						//按age升序排序
投影:				db.集合名称.find(条件,{name:1,age:1})		 //仅保留name与age域

条件查询
基本格式:			db.集合.find({条件})
模糊查询:			db.集合.find({域名:/正则表达式/})		  //等同SQL中的like,比like强大,可以执行正则所有规则
条件比较运算:		   db.集合.find({域名:{$gt:值}})				//等同SQL中的数值比较操作,例如:name>18
包含查询:			db.集合.find({域名:{$in:[值1,值2]}})		//等同于SQL中的in
条件连接查询:		   db.集合.find({$and:[{条件1},{条件2}]})	   //等同于SQL中的and、or

4.2.2.3 Integración

¿Cómo integrar MongDB con springboot? De hecho, la razón por la cual springboot usa tantos desarrolladores es porque sus rutinas son casi exactamente las mismas. Importe coordenadas, configure y use la interfaz API para operar. Lo mismo es cierto para la integración de Redis y lo mismo es cierto para la integración de MongoDB.

Primero, primero importe las coordenadas de inicio integradas de la tecnología correspondiente

En segundo lugar, configure la información necesaria

En tercer lugar, use la API provista para operar

Empecemos Springboot para integrar MongoDB, los pasos son los siguientes:

Paso ① : importe springboot para integrar las coordenadas iniciales de MongoDB

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

​ Las coordenadas anteriores también se pueden seleccionar comprobando al crear un módulo, y también pertenecen a la categoría NoSQL

Paso ② : Realice la configuración básica

spring:
  data:
    mongodb:
      uri: mongodb://localhost/angyan

​ La configuración requerida para operar MongoDB es la misma que se requiere para operar redis. La información más básica es qué servidor operar. La diferencia es que la dirección IP y el puerto del servidor conectado son diferentes, y el formato de escritura es diferente.

Paso ③ : use springboot para integrar la interfaz de cliente dedicada de MongoDB MongoTemplate para operar

@SpringBootTest
class Springboot17MongodbApplicationTests {
    
    
    @Autowired
    private MongoTemplate mongoTemplate;
    @Test
    void contextLoads() {
    
    
        Book book = new Book();
        book.setId(2);
        book.setName("springboot2");
        book.setType("springboot2");
        book.setDescription("springboot2");
        mongoTemplate.save(book);
    }
    @Test
    void find(){
    
    
        List<Book> all = mongoTemplate.findAll(Book.class);
        System.out.println(all);
    }
}

Resumir

  1. Springboot integra los pasos de MongoDB
    1. Importe springboot para integrar las coordenadas iniciales de MongoDB
    2. Realizar configuración básica
    3. Use springboot para integrar la operación MongoTemplate de la interfaz de cliente dedicada de MongoDB

4.2.3 SpringBoot integra ES

ES (Elasticsearch) es un motor de búsqueda de texto completo distribuido que se centra en la búsqueda de texto completo.

Entonces, ¿qué es la búsqueda de texto completo? Por ejemplo, si un usuario quiere comprar un libro, busca con Java como palabra clave, ya sea en el título del libro, en la introducción del libro o incluso en el nombre del autor del libro, siempre y cuando como contiene Java, se devolverá al usuario como resultado de la consulta. El proceso anterior utiliza tecnología de búsqueda de texto completo. La condición de búsqueda ya no se usa solo para comparar un campo determinado, sino para usar la condición de búsqueda para comparar más campos en un dato. Siempre que se pueda hacer coincidir, se incluirá en los resultados de la consulta. Este es el propósito de la búsqueda de texto completo. La tecnología ES es una tecnología que puede lograr los efectos anteriores.

​ Para lograr el efecto de la búsqueda de texto completo, es imposible usar la operación similar en la base de datos para comparar, lo cual es demasiado ineficiente. ES ha diseñado una nueva idea para realizar búsquedas de texto completo. El proceso de operación específico es el siguiente:

  1. Verifique y divida toda la información de texto de los datos en el campo consultado en varias palabras

    • Por ejemplo, "República Popular de China" se dividirá en tres palabras, a saber, "China", "Pueblo" y "República". Hay un término profesional para este proceso llamado segmentación de palabras. Diferentes estrategias de segmentación de palabras tienen diferentes efectos de separación.Diferentes estrategias de segmentación de palabras se denominan dispositivos de segmentación de palabras.
  2. 将分词得到的结果存储起来,对应每条数据的id

    • 例如id为1的数据中名称这一项的值是“中华人民共和国”,那么分词结束后,就会出现“中华”对应id为1,“人民”对应id为1,“共和国”对应id为1

    • 例如id为2的数据中名称这一项的值是“人民代表大会“,那么分词结束后,就会出现“人民”对应id为2,“代表”对应id为2,“大会”对应id为2

    • 此时就会出现如下对应结果,按照上述形式可以对所有文档进行分词。需要注意分词的过程不是仅对一个字段进行,而是对每一个参与查询的字段都执行,最终结果汇总到一个表格中

      分词结果关键字 对应id
      中华 1
      人民 1,2
      共和国 1
      代表 2
      大会 2
  3. 当进行查询时,如果输入“人民”作为查询条件,可以通过上述表格数据进行比对,得到id值1,2,然后根据id值就可以得到查询的结果数据了。

​ 上述过程中分词结果关键字内容每一个都不相同,作用有点类似于数据库中的索引,是用来加速数据查询的。但是数据库中的索引是对某一个字段进行添加索引,而这里的分词结果关键字不是一个完整的字段值,只是一个字段中的其中的一部分内容。并且索引使用时是根据索引内容查找整条数据,全文搜索中的分词结果关键字查询后得到的并不是整条的数据,而是数据的id,要想获得具体数据还要再次查询,因此这里为这种分词结果关键字起了一个全新的名称,叫做倒排索引

4.2.3.1、安装

windows版安装包下载地址:https://www.elastic.co/cn/downloads/elasticsearch

​ 下载的安装包是解压缩就能使用的zip文件,解压缩完毕后会得到如下文件

inserte la descripción de la imagen aquí

  • bin目录:包含所有的可执行命令
  • config目录:包含ES服务器使用的配置文件
  • jdk目录:此目录中包含了一个完整的jdk工具包,版本17,当ES升级时,使用最新版本的jdk确保不会出现版本支持性不足的问题
  • lib目录:包含ES运行的依赖jar文件
  • directorio de registros: contiene todos los archivos de registro generados después de la ejecución de ES
  • directorio de módulos: Contiene todos los módulos funcionales en el software ES, y también es un paquete jar uno por uno. A diferencia del directorio jar, el directorio jar es el paquete jar del que depende ES durante la operación, y los módulos son los paquetes jar funcionales del propio software ES.
  • directorio de complementos: contiene complementos instalados por el software ES, el valor predeterminado está vacío

servidor de inicio

elasticsearch.bat

Haga doble clic en el archivo elasticsearch.bat para iniciar el servidor ES. El puerto de servicio predeterminado es 9200. Visite http://localhost:9200 a través del navegador y vea la siguiente información, que se considera como el inicio normal del servidor ES

{
    
    
  "name" : "CZBK-**********",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "j137DSswTPG8U4Yb-0T1Mg",
  "version" : {
    
    
    "number" : "7.16.2",
    "build_flavor" : "default",
    "build_type" : "zip",
    "build_hash" : "2b937c44140b6559905130a8650c64dbd0879cfb",
    "build_date" : "2021-12-18T19:42:46.604893745Z",
    "build_snapshot" : false,
    "lucene_version" : "8.10.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

4.2.3.2 Funcionamiento básico

​ Los datos que queremos consultar se almacenan en ES, pero el formato es diferente del formato de datos de almacenamiento de la base de datos. En ES, primero debemos crear un índice invertido. La función de este índice es similar a la de una tabla de base de datos, y luego agregar datos al índice invertido. Los datos agregados se denominan documento. Por lo tanto, para realizar operaciones de ES, primero debe crear un índice y luego agregar documentos, para que se puedan realizar las operaciones de consulta posteriores.

​ ES se puede operar a través de solicitudes estilo Rest, es decir, se puede realizar una operación mediante el envío de una solicitud. Por ejemplo, las operaciones como crear un índice y eliminar un índice se pueden realizar en forma de envío de una solicitud.

  • Cree un índice, libros es el nombre del índice, lo mismo a continuación

    PUT请求		http://localhost:9200/books
    

    Después de enviar la solicitud, si ve la siguiente información, el índice se crea correctamente

    {
          
          
        "acknowledged": true,
        "shards_acknowledged": true,
        "index": "books"
    }
    

    La creación repetida de un índice existente generará un mensaje de error y el motivo del error se describe en el atributo de motivo.

    {
          
          
        "error": {
          
          
            "root_cause": [
                {
          
          
                    "type": "resource_already_exists_exception",
                    "reason": "index [books/VgC_XMVAQmedaiBNSgO2-w] already exists",
                    "index_uuid": "VgC_XMVAQmedaiBNSgO2-w",
                    "index": "books"
                }
            ],
            "type": "resource_already_exists_exception",
            "reason": "index [books/VgC_XMVAQmedaiBNSgO2-w] already exists",	# books索引已经存在
            "index_uuid": "VgC_XMVAQmedaiBNSgO2-w",
            "index": "book"
        },
        "status": 400
    }
    
  • índice de consulta

    GET请求		http://localhost:9200/books
    

    Consulta el índice para obtener la información relevante del índice, de la siguiente manera

    {
          
          
        "book": {
          
          
            "aliases": {
          
          },
            "mappings": {
          
          },
            "settings": {
          
          
                "index": {
          
          
                    "routing": {
          
          
                        "allocation": {
          
          
                            "include": {
          
          
                                "_tier_preference": "data_content"
                            }
                        }
                    },
                    "number_of_shards": "1",
                    "provided_name": "books",
                    "creation_date": "1645768584849",
                    "number_of_replicas": "1",
                    "uuid": "VgC_XMVAQmedaiBNSgO2-w",
                    "version": {
          
          
                        "created": "7160299"
                    }
                }
            }
        }
    }
    

    Si consulta un índice que no existe, se devolverá un mensaje de error. Por ejemplo, la información después de consultar el índice llamado libro es la siguiente

    {
          
          
        "error": {
          
          
            "root_cause": [
                {
          
          
                    "type": "index_not_found_exception",
                    "reason": "no such index [book]",
                    "resource.type": "index_or_alias",
                    "resource.id": "book",
                    "index_uuid": "_na_",
                    "index": "book"
                }
            ],
            "type": "index_not_found_exception",
            "reason": "no such index [book]",		# 没有book索引
            "resource.type": "index_or_alias",
            "resource.id": "book",
            "index_uuid": "_na_",
            "index": "book"
        },
        "status": 404
    }
    
  • eliminar índice

    DELETE请求	http://localhost:9200/books
    

    Después de eliminar todo, proporcione el resultado de eliminación

    {
          
          
        "acknowledged": true
    }
    

    Si se elimina repetidamente, se dará un mensaje de error y la causa específica del error también se describirá en el atributo de motivo.

    {
          
          
        "error": {
          
          
            "root_cause": [
                {
          
          
                    "type": "index_not_found_exception",
                    "reason": "no such index [books]",
                    "resource.type": "index_or_alias",
                    "resource.id": "book",
                    "index_uuid": "_na_",
                    "index": "book"
                }
            ],
            "type": "index_not_found_exception",
            "reason": "no such index [books]",		# 没有books索引
            "resource.type": "index_or_alias",
            "resource.id": "book",
            "index_uuid": "_na_",
            "index": "book"
        },
        "status": 404
    }
    
  • Crear un índice y especificar un tokenizador

    ​ El índice creado anteriormente no especifica un tokenizador. Puede agregar parámetros de solicitud y configurar el tokenizador al crear el índice. En la actualidad, el separador de palabras más popular en China es el separador de palabras IK. Antes de usarlo, descargue el separador de palabras correspondiente y utilícelo. Dirección de descarga del tokenizador IK: https://github.com/medcl/elasticsearch-analysis-ik/releases

    Descargue el separador de palabras y extráigalo al directorio de complementos del directorio de instalación de ES. Después de instalar el separador de palabras, debe reiniciar el servidor ES. Use el tokenizador IK para crear un formato de índice:

    PUT请求		http://localhost:9200/books
    
    请求参数如下(注意是json格式的参数)
    {
          
          
        "mappings":{
          
          							#定义mappings属性,替换创建索引时对应的mappings属性		
            "properties":{
          
          						#定义索引中包含的属性设置
                "id":{
          
          							#设置索引中包含id属性
                    "type":"keyword"			#当前属性可以被直接搜索
                },
                "name":{
          
          						#设置索引中包含name属性
                    "type":"text",              #当前属性是文本信息,参与分词  
                    "analyzer":"ik_max_word",   #使用IK分词器进行分词             
                    "copy_to":"all"				#分词结果拷贝到all属性中
                },
                "type":{
          
          
                    "type":"keyword"
                },
                "description":{
          
          
                    "type":"text",	                
                    "analyzer":"ik_max_word",                
                    "copy_to":"all"
                },
                "all":{
          
          							#定义属性,用来描述多个字段的分词结果集合,当前属性可以参与查询
                    "type":"text",	                
                    "analyzer":"ik_max_word"
                }
            }
        }
    }
    

    Una vez completada la creación, el resultado devuelto es el mismo que el resultado de crear el índice sin usar el tokenizador. En este momento, puede observar las asignaciones de parámetros de solicitud agregadas al ver la información del índice y haber ingresado el atributo del índice.

    {
          
          
        "books": {
          
          
            "aliases": {
          
          },
            "mappings": {
          
          						#mappings属性已经被替换
                "properties": {
          
          
                    "all": {
          
          
                        "type": "text",
                        "analyzer": "ik_max_word"
                    },
                    "description": {
          
          
                        "type": "text",
                        "copy_to": [
                            "all"
                        ],
                        "analyzer": "ik_max_word"
                    },
                    "id": {
          
          
                        "type": "keyword"
                    },
                    "name": {
          
          
                        "type": "text",
                        "copy_to": [
                            "all"
                        ],
                        "analyzer": "ik_max_word"
                    },
                    "type": {
          
          
                        "type": "keyword"
                    }
                }
            },
            "settings": {
          
          
                "index": {
          
          
                    "routing": {
          
          
                        "allocation": {
          
          
                            "include": {
          
          
                                "_tier_preference": "data_content"
                            }
                        }
                    },
                    "number_of_shards": "1",
                    "provided_name": "books",
                    "creation_date": "1645769809521",
                    "number_of_replicas": "1",
                    "uuid": "DohYKvr_SZO4KRGmbZYmTQ",
                    "version": {
          
          
                        "created": "7160299"
                    }
                }
            }
        }
    }
    

En la actualidad, ya tenemos un índice, pero no hay datos en el índice, por lo que debemos agregar datos primero. Los datos se denominan documento en ES, y la operación del documento se realizará a continuación.

  • Hay tres formas de agregar documentos

    POST请求	http://localhost:9200/books/_doc		#使用系统生成id
    POST请求	http://localhost:9200/books/_create/1	#使用指定id
    POST请求	http://localhost:9200/books/_doc/1		#使用指定id,不存在创建,存在更新(版本递增)
    
    文档通过请求参数传递,数据格式json
    {
          
          
        "name":"springboot",
        "type":"springboot",
        "description":"springboot"
    }  
    
  • documento de consulta

    GET请求	http://localhost:9200/books/_doc/1		 #查询单个文档 		
    GET请求	http://localhost:9200/books/_search		 #查询全部文档
    
  • consulta condicional

    GET请求	http://localhost:9200/books/_search?q=name:springboot	# q=查询属性名:查询属性值
    
  • eliminar documento

    DELETE请求	http://localhost:9200/books/_doc/1
    
  • Modificar el documento (actualización completa)

    PUT请求	http://localhost:9200/books/_doc/1
    
    文档通过请求参数传递,数据格式json
    {
          
          
        "name":"springboot",
        "type":"springboot",
        "description":"springboot"
    }
    
  • Documentos revisados ​​(parcialmente actualizados)

    POST请求	http://localhost:9200/books/_update/1
    
    文档通过请求参数传递,数据格式json
    {
          
          			
        "doc":{
          
          						#部分更新并不是对原始文档进行更新,而是对原始文档对象中的doc属性中的指定属性更新
            "name":"springboot"		#仅更新提供的属性值,未提供的属性值不参与更新操作
        }
    }
    

4.2.3.3 Integración

¿Cómo integrar ES con springboot? Las reglas antiguas, importar coordenadas, configurar, usar la interfaz API para operar. La integración de Redis es la misma, la integración de MongoDB es la misma y la integración de ES sigue siendo la misma.

Empecemos Springboot a integrar ES, los pasos son los siguientes:

Paso ① : Importe springboot para integrar las coordenadas de inicio de ES

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

Paso ② : Realice la configuración básica

spring:
  elasticsearch:
    rest:
      uris: http://localhost:9200

Configurar la dirección del servidor ES, puerto 9200

Paso ③ : use Springboot para integrar la interfaz de cliente dedicada de ES, ElasticsearchRestTemplate, para operar

@SpringBootTest
class Springboot18EsApplicationTests {
    
    
    @Autowired
    private ElasticsearchRestTemplate template;
}

​ El modo de operación anterior es el modo de operación inicial de ES. El cliente utilizado se llama Cliente de bajo nivel. El rendimiento de este modo de operación de cliente es ligeramente insuficiente, por lo que ES ha desarrollado un nuevo modo de operación de cliente llamado Cliente de alto nivel. . El cliente de alto nivel se actualiza sincrónicamente con la versión de ES, pero cuando Springboot integró ES por primera vez, utilizó un cliente de bajo nivel, por lo que el desarrollo empresarial debe reemplazarse con un modelo de cliente de alto nivel.

A continuación se utiliza el método de cliente de alto nivel para integrar ES con springboot. Los pasos de la operación son los siguientes:

Paso ① : Importe Springboot para integrar las coordenadas del cliente de alto nivel ES Actualmente no hay un iniciador correspondiente para este formulario

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

Paso ② : use la programación para configurar el servidor ES conectado y obtener el objeto del cliente

@SpringBootTest
class Springboot18EsApplicationTests {
    
    
    private RestHighLevelClient client;
      @Test
      void testCreateClient() throws IOException {
    
    
          HttpHost host = HttpHost.create("http://localhost:9200");
          RestClientBuilder builder = RestClient.builder(host);
          client = new RestHighLevelClient(builder);
  
          client.close();
      }
}

Configure la dirección del servidor ES y el puerto 9200. Recuerde que el cliente debe cerrarse manualmente después de su uso. Dado que el cliente actual se mantiene manualmente, los objetos no se pueden cargar a través del cableado automático.

Paso ③ : use el objeto de cliente para operar ES, como crear un índice

@SpringBootTest
class Springboot18EsApplicationTests {
    
    
    private RestHighLevelClient client;
      @Test
      void testCreateIndex() throws IOException {
    
    
          HttpHost host = HttpHost.create("http://localhost:9200");
          RestClientBuilder builder = RestClient.builder(host);
          client = new RestHighLevelClient(builder);
          
          CreateIndexRequest request = new CreateIndexRequest("books");
          client.indices().create(request, RequestOptions.DEFAULT); 
          
          client.close();
      }
}

Las operaciones de cliente de alto nivel completan todas las operaciones mediante el envío de solicitudes. ES configura varios objetos de solicitud para varias operaciones. En el ejemplo anterior, el objeto para crear el índice es CreateIndexRequest, y otras operaciones también tendrán su propio objeto de solicitud dedicado.

En la operación actual, encontramos que no importa qué operación de ES se realice, el primer paso siempre es obtener el objeto RestHighLevelClient y el último paso siempre es cerrar la conexión del objeto. En la prueba, puede usar las características de la clase de prueba para ayudar al desarrollador a completar las operaciones anteriores al mismo tiempo, pero al escribir el negocio, debe administrarlo usted mismo. Convierta el formato de código anterior para usar el método de inicialización y el método de destrucción de la clase de prueba para mantener el objeto del cliente.

@SpringBootTest
class Springboot18EsApplicationTests {
    
    
    @BeforeEach		//在测试类中每个操作运行前运行的方法
    void setUp() {
    
    
        HttpHost host = HttpHost.create("http://localhost:9200");
        RestClientBuilder builder = RestClient.builder(host);
        client = new RestHighLevelClient(builder);
    }

    @AfterEach		//在测试类中每个操作运行后运行的方法
    void tearDown() throws IOException {
    
    
        client.close();
    }

    private RestHighLevelClient client;

    @Test
    void testCreateIndex() throws IOException {
    
    
        CreateIndexRequest request = new CreateIndexRequest("books");
        client.indices().create(request, RequestOptions.DEFAULT);
    }
}

La escritura actual es mucho más simplificada y más razonable. A continuación, utilice el modo anterior para ejecutar todas las operaciones de ES nuevamente y pruebe los resultados

Cree un índice (tokenizador IK) :

@Test
void testCreateIndexByIK() throws IOException {
    
    
    CreateIndexRequest request = new CreateIndexRequest("books");
    String json = "{\n" +
            "    \"mappings\":{\n" +
            "        \"properties\":{\n" +
            "            \"id\":{\n" +
            "                \"type\":\"keyword\"\n" +
            "            },\n" +
            "            \"name\":{\n" +
            "                \"type\":\"text\",\n" +
            "                \"analyzer\":\"ik_max_word\",\n" +
            "                \"copy_to\":\"all\"\n" +
            "            },\n" +
            "            \"type\":{\n" +
            "                \"type\":\"keyword\"\n" +
            "            },\n" +
            "            \"description\":{\n" +
            "                \"type\":\"text\",\n" +
            "                \"analyzer\":\"ik_max_word\",\n" +
            "                \"copy_to\":\"all\"\n" +
            "            },\n" +
            "            \"all\":{\n" +
            "                \"type\":\"text\",\n" +
            "                \"analyzer\":\"ik_max_word\"\n" +
            "            }\n" +
            "        }\n" +
            "    }\n" +
            "}";
    //设置请求中的参数
    request.source(json, XContentType.JSON);
    client.indices().create(request, RequestOptions.DEFAULT);
}

El tokenizador IK se establece en forma de parámetros de solicitud. Para establecer los parámetros de solicitud, utilice el método de origen en el objeto de solicitud para establecerlos. En cuanto a los parámetros, depende de su tipo de operación. Cuando se requieren parámetros en la solicitud, se puede usar el formulario actual para la configuración de parámetros.

Añadir documentación :

@Test
//添加文档
void testCreateDoc() throws IOException {
    
    
    Book book = bookDao.selectById(1);
    IndexRequest request = new IndexRequest("books").id(book.getId().toString());
    String json = JSON.toJSONString(book);
    request.source(json,XContentType.JSON);
    client.index(request,RequestOptions.DEFAULT);
}

El objeto de solicitud que se usa para agregar un documento es IndexRequest, que es diferente del objeto de solicitud que se usa para crear un índice.

Para agregar documentos en lotes :

@Test
//批量添加文档
void testCreateDocAll() throws IOException {
    
    
    List<Book> bookList = bookDao.selectList(null);
    BulkRequest bulk = new BulkRequest();
    for (Book book : bookList) {
    
    
        IndexRequest request = new IndexRequest("books").id(book.getId().toString());
        String json = JSON.toJSONString(book);
        request.source(json,XContentType.JSON);
        bulk.add(request);
    }
    client.bulk(bulk,RequestOptions.DEFAULT);
}

Al realizar el procesamiento por lotes, primero cree un objeto BulkRequest, que puede entenderse como un contenedor para almacenar objetos de solicitud. Después de inicializar todas las solicitudes, agréguelas al objeto BulkRequest y luego use el método masivo del objeto BulkRequest, una vez Sex está hecho.

Consultar documentos por id :

@Test
//按id查询
void testGet() throws IOException {
    
    
    GetRequest request = new GetRequest("books","1");
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    String json = response.getSourceAsString();
    System.out.println(json);
}

El objeto de solicitud utilizado para consultar documentos en función de la identificación es GetRequest.

Consultar documentos por estado :

@Test
//按条件查询
void testSearch() throws IOException {
    
    
    SearchRequest request = new SearchRequest("books");

    SearchSourceBuilder builder = new SearchSourceBuilder();
    builder.query(QueryBuilders.termQuery("all","spring"));
    request.source(builder);

    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    SearchHits hits = response.getHits();
    for (SearchHit hit : hits) {
    
    
        String source = hit.getSourceAsString();
        //System.out.println(source);
        Book book = JSON.parseObject(source, Book.class);
        System.out.println(book);
    }
}

​ 按条件查询文档使用的请求对象是SearchRequest,查询时调用SearchRequest对象的termQuery方法,需要给出查询属性名,此处支持使用合并字段,也就是前面定义索引属性时添加的all属性。

总结

  1. springboot整合ES步骤
    1. 导入springboot整合ES的High Level Client坐标
    2. 手工管理客户端对象,包括初始化和关闭操作
    3. 使用High Level Client根据操作的种类不同,选择不同的Request对象完成对应操作

五、整合第三方技术

5.1、缓存

企业级应用主要作用是信息处理,当需要读取数据时,由于受限于数据库的访问效率,导致整体系统性能偏低。

应用程序直接与数据库打交道,访问效率低

​为了改善上述现象,开发者通常会在应用程序与数据库之间建立一种临时的数据存储机制,该区域中的数据在内存中保存,读写速度较快,可以有效解决数据库访问效率低下的问题。这一块临时存储数据的区域就是缓存。

  • 缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质

  • 使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高系统性能

  • 缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间

5.1.1、SpringBoot内置缓存解决方案

springboot技术提供有内置的缓存解决方案,可以帮助开发者快速开启缓存技术,并使用缓存技术进行数据的快速操作,例如读取缓存数据和写入数据到缓存。

步骤①:导入springboot提供的缓存技术对应的starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

步骤②:启用缓存,在引导类上方标注注解@EnableCaching配置springboot程序中可以使用缓存

@SpringBootApplication
//开启缓存功能
@EnableCaching
public class Springboot19CacheApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(Springboot19CacheApplication.class, args);
    }
}

步骤③:设置操作的数据是否使用缓存

@Service
public class BookServiceImpl implements BookService {
    
    
    @Autowired
    private BookDao bookDao;

    @Cacheable(value="cacheSpace",key="#id")
    public Book getById(Integer id) {
    
    
        return bookDao.selectById(id);
    }
}

​ Utilice la anotación @Cacheable en el método comercial para declarar que el valor de retorno del método actual se coloca en la memoria caché, donde se especifica la ubicación de almacenamiento de la memoria caché y se guarda el nombre correspondiente al valor de retorno del método actual. en el caché. En el ejemplo anterior, el atributo de valor describe la ubicación de almacenamiento de la memoria caché, que puede entenderse como un nombre de espacio de almacenamiento, y el atributo clave describe el nombre de los datos almacenados en la memoria caché. Use #id para leer el valor de id en el parámetro formal como el nombre de caché.

Después de usar la anotación @Cacheable, ejecute la operación actual. Si encuentra que el nombre correspondiente no tiene datos en el caché, lea los datos normalmente y colóquelos en el caché; si el nombre correspondiente tiene datos en el caché, finalice la ejecución del método comercial actual y devolver directamente los datos en el caché.

5.1.2 Caso del código de verificación del teléfono móvil

Para facilitar la demostración de varias tecnologías de almacenamiento en caché a continuación, creamos un entorno de casos de códigos de verificación de teléfonos móviles para simular el proceso de uso de caché para guardar códigos de verificación de teléfonos móviles.

Los requisitos del caso del código de verificación del teléfono móvil son los siguientes:

  • Ingrese el número de teléfono móvil para obtener el código de verificación y organice el documento que se enviará al usuario en forma de mensaje de texto (simulación de página)
  • Ingrese el número de teléfono móvil y el código de verificación para verificar el resultado

Para describir las operaciones anteriores, creamos dos interfaces de capa de presentación, una se usa para simular el proceso de envío de SMS, de hecho, se genera un código de verificación de acuerdo con el número de teléfono móvil proporcionado por el usuario y luego se pone en el caché, y el otro se usa para simular la verificación del código de verificación. El proceso es en realidad usar el número de teléfono móvil entrante y el código de verificación para hacer coincidir, y devolver el resultado final de la coincidencia. El código de simulación de este caso se produce directamente a continuación, y la tecnología de almacenamiento en caché integrada proporcionada por springboot en el ejemplo anterior se usa para completar la producción del caso actual.

Paso ① : Importe el iniciador correspondiente a la tecnología de almacenamiento en caché proporcionada por springboot

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

Paso ② : habilite el almacenamiento en caché, anote @EnableCaching arriba de la clase de arranque para configurar el programa springboot para usar el caché

@SpringBootApplication
//开启缓存功能
@EnableCaching
public class Springboot19CacheApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(Springboot19CacheApplication.class, args);
    }
}

Paso ③ : defina la clase de entidad correspondiente al código de verificación y encapsule los dos atributos del número de teléfono móvil y el código de verificación

@Data
public class SMSCode {
    
    
    private String tele;
    private String code;
}

Paso ④ : defina la interfaz de la capa empresarial y la clase de implementación de la función del código de verificación

public interface SMSCodeService {
    
    
    public String sendCodeToSMS(String tele);
    public boolean checkCode(SMSCode smsCode);
}

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    
    
    @Autowired
    private CodeUtils codeUtils;

    @CachePut(value = "smsCode", key = "#tele")
    public String sendCodeToSMS(String tele) {
    
    
        String code = codeUtils.generator(tele);
        return code;
    }

    public boolean checkCode(SMSCode smsCode) {
    
    
        //取出内存中的验证码与传递过来的验证码比对,如果相同,返回true
        String code = smsCode.getCode();
        String cacheCode = codeUtils.get(smsCode.getTele());
        return code.equals(cacheCode);
    }
}

​ Después de obtener el código de verificación, el código de verificación debe obtenerse nuevamente cuando el código de verificación deja de ser válido. Por lo tanto, la anotación @Cacheable no se puede usar en la función de obtener el código de verificación. La anotación @Cacheable significa que si no hay ningún valor en la cache se pone el valor, y si hay un valor en la cache se toma el valor. La función aquí es solo generar el código de verificación y ponerlo en el caché, y no tiene la función de obtener valores del caché, por lo que la anotación @Cacheable no se puede usar, y solo debe tener la función de guardar datos en el caché, usando la anotación @CachePut.

Para la función de verificar el código de verificación, se recomienda ponerlo en la clase de herramienta.

Paso ⑤ : defina la estrategia de generación del código de verificación y la función de lectura del código de verificación de acuerdo con el número de teléfono móvil

@Component
public class CodeUtils {
    
    
    private String [] patch = {
    
    "000000","00000","0000","000","00","0",""};

    public String generator(String tele){
    
    
        int hash = tele.hashCode();
        int encryption = 20206666;
        long result = hash ^ encryption;
        long nowTime = System.currentTimeMillis();
        result = result ^ nowTime;
        long code = result % 1000000;
        code = code < 0 ? -code : code;
        String codeStr = code + "";
        int len = codeStr.length();
        return patch[len] + codeStr;
    }

    @Cacheable(value = "smsCode",key="#tele")
    public String get(String tele){
    
    
        return null;
    }
}

Paso ⑥ : defina la interfaz de la capa web de la función del código de verificación, se usa un método para proporcionar el número de teléfono móvil para obtener el código de verificación y el otro método se usa para proporcionar el número de teléfono móvil y el código de verificación para la verificación

@RestController
@RequestMapping("/sms")
public class SMSCodeController {
    
    
    @Autowired
    private SMSCodeService smsCodeService;
    
    @GetMapping
    public String getCode(String tele){
    
    
        String code = smsCodeService.sendCodeToSMS(tele);
        return code;
    }
    
    @PostMapping
    public boolean checkCode(SMSCode smsCode){
    
    
        return smsCodeService.checkCode(smsCode);
    }
}

5.1.3, SpringBoot integra la memoria caché Ehcache

El caso del código de verificación del teléfono móvil se completó y Springboot comenzará a integrar varias tecnologías de almacenamiento en caché, el primero en integrar la tecnología Ehcache. Ehcache es una tecnología de almacenamiento en caché. Usar Springboot para integrar Ehcache en realidad está cambiando la implementación de la tecnología de almacenamiento en caché.

Paso ① : Importar las coordenadas de Ehcache

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>

​ ¿Por qué no importa aquí el arrancador de Ehcache, sino las coordenadas técnicas? De hecho, la tecnología de almacenamiento en caché integrada de Springboot tiene un formato general. No importa qué tecnología de almacenamiento en caché integre, solo cambia la implementación y el método de operación es el mismo. Esto también refleja las ventajas de la tecnología springboot y unifica el método de integración de tecnologías similares.

Paso ② : Configure la tecnología de almacenamiento en caché para usar Ehcache

spring:
  cache:
    type: ehcache
    ehcache:
      config: ehcache.xml

​ El tipo de caché de configuración es ehcache.Es necesario explicar aquí que la tecnología de almacenamiento en caché actual que Springboot puede integrar incluye ehcach, por lo que se puede escribir así. De hecho, este tipo no se puede escribir de manera casual, y no se puede integrar simplemente escribiendo un nombre.

​ Dado que la configuración de ehcache tiene un formato de archivo de configuración independiente, también es necesario especificar el archivo de configuración de ehcache para poder leer la configuración correspondiente

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <diskStore path="D:\ehcache" />

    <!--默认缓存策略 -->
    <!-- external:是否永久存在,设置为true则不会被清除,此时与timeout冲突,通常设置为false-->
    <!-- diskPersistent:是否启用磁盘持久化-->
    <!-- maxElementsInMemory:最大缓存数量-->
    <!-- overflowToDisk:超过最大缓存数量是否持久化到磁盘-->
    <!-- timeToIdleSeconds:最大不活动间隔,设置过长缓存容易溢出,设置过短无效果,可用于记录时效性数据,例如验证码-->
    <!-- timeToLiveSeconds:最大存活时间-->
    <!-- memoryStoreEvictionPolicy:缓存清除策略-->
    <defaultCache
        eternal="false"
        diskPersistent="false"
        maxElementsInMemory="1000"
        overflowToDisk="false"
        timeToIdleSeconds="60"
        timeToLiveSeconds="60"
        memoryStoreEvictionPolicy="LRU" />

    <cache
        name="smsCode"
        eternal="false"
        diskPersistent="false"
        maxElementsInMemory="1000"
        overflowToDisk="false"
        timeToIdleSeconds="10"
        timeToLiveSeconds="10"
        memoryStoreEvictionPolicy="LRU" />
</ehcache>

Tenga en cuenta que en el caso anterior, la ubicación donde se guardan los datos se establece en smsCode

@CachePut(value = "smsCode", key = "#tele")
public String sendCodeToSMS(String tele) {
    
    
    String code = codeUtils.generator(tele);
    return code;
}	

Esta configuración debe garantizar que haya un nombre de espacio de caché llamado configuración de smsCode en ehcache, que debe unificarse antes y después. En el proceso de desarrollo empresarial, se establecen diferentes estrategias de almacenamiento en caché mediante la configuración de cachés con diferentes nombres, que se aplican a diferentes datos almacenados en caché.

​ En este punto, la integración de springboot de Ehcache ha terminado. Se puede encontrar que el código original no ha sido modificado. Solo agregar un conjunto de configuraciones puede cambiar el proveedor de caché. Esta es también la ventaja de Springboot que proporciona una operación de caché unificada. interfaz Cambio de implementación No afecta la escritura del código original.

Resumir

  1. Springboot usa Ehcache como caché para implementar las coordenadas que deben importarse a Ehcache
  2. Modifique la configuración, configure el proveedor de caché como ehcache y proporcione el archivo de configuración de caché correspondiente

5.1.4, SpringBoot integra caché Redis

En la sección anterior, se usó Ehcache para reemplazar la tecnología de almacenamiento en caché integrada de springboot. De hecho, Springboot admite muchas tecnologías de almacenamiento en caché. A continuación, se usa la tecnología redis como una solución de almacenamiento en caché para realizar el caso del código de verificación del teléfono móvil.

Compare el proceso de uso de Ehcache, agregue coordenadas, cambie el tipo de implementación de caché a ehcache y configure Ehcache. ¿Qué pasa si también se usa como redis para el almacenamiento en caché? Es exactamente lo mismo, agregue coordenadas, cambie el tipo de implementación de caché a redis y configure redis. Solo hay una diferencia, la configuración de redis se puede configurar directamente en el archivo yml, sin crear un archivo de configuración por separado.

Paso ① : Importa las coordenadas de redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Paso ② : Configure la tecnología de almacenamiento en caché para usar redis

spring:
  redis:
    host: localhost
    port: 6379
  cache:
    type: redis

​ Si necesita configurar redis como caché, tenga cuidado de no configurar el redis original, pero configure redis como caché para usar configuraciones relacionadas, que pertenecen al nodo spring.cache.redis, y tenga cuidado de no escribir el lugar incorrecto.

spring:
  redis:
    host: localhost
    port: 6379
  cache:
    type: redis
    redis:
      use-key-prefix: false
      key-prefix: sms_
      cache-null-values: false
      time-to-live: 10s

Resumir

  1. Springboot usa redis como caché para implementar las coordenadas que deben importarse a redis
  2. Modifique la configuración, configure el proveedor de caché como redis y proporcione la configuración de caché correspondiente

5.1.5, SpringBoot integra la caché de Memcached

​ 目前我们已经掌握了3种缓存解决方案的配置形式,分别是springboot内置缓存,ehcache和redis,现在研究一下国内比较流行的一款缓存memcached。

​ 按照之前的套路,其实变更缓存并不繁琐,但是springboot并没有支持使用memcached作为其缓存解决方案,也就是说在type属性中没有memcached的配置选项,这里就需要更变一下处理方式了。在整合之前先安装memcached。

安装

​ windows版安装包下载地址:https://www.runoob.com/memcached/window-install-memcached.html

​ 下载的安装包是解压缩就能使用的zip文件,解压缩完毕后会得到如下文件

inserte la descripción de la imagen aquí

​ 可执行文件只有一个memcached.exe,使用该文件可以将memcached作为系统服务启动,执行此文件时会出现报错信息,如下:

inserte la descripción de la imagen aquí

​ 此处出现问题的原因是注册系统服务时需要使用管理员权限,当前账号权限不足导致安装服务失败,切换管理员账号权限启动命令行

​ 然后再次执行安装服务的命令即可,如下:

memcached.exe -d install

​ 服务安装完毕后可以使用命令启动和停止服务,如下:

memcached.exe -d start		# 启动服务
memcached.exe -d stop		# 停止服务

​ 也可以在任务管理器中进行服务状态的切换

变更缓存为Memcached

​ 由于memcached未被springboot收录为缓存解决方案,因此使用memcached需要通过手工硬编码的方式来使用,于是前面的套路都不适用了,需要自己写了。

​ memcached目前提供有三种客户端技术,分别是Memcached Client for Java、SpyMemcached和Xmemcached,其中性能指标各方面最好的客户端是Xmemcached,本次整合就使用这个作为客户端实现技术了。下面开始使用Xmemcached

步骤①:导入xmemcached的坐标

<dependency>
    <groupId>com.googlecode.xmemcached</groupId>
    <artifactId>xmemcached</artifactId>
    <version>2.4.7</version>
</dependency>

步骤②:配置memcached,制作memcached的配置类

@Configuration
public class XMemcachedConfig {
    
    
    @Bean
    public MemcachedClient getMemcachedClient() throws IOException {
    
    
        MemcachedClientBuilder memcachedClientBuilder = new XMemcachedClientBuilder("localhost:11211");
        MemcachedClient memcachedClient = memcachedClientBuilder.build();
        return memcachedClient;
    }
}

El puerto de servicio externo predeterminado de memcached es 11211.

Paso ③ : use el cliente xmemcached para operar el caché e inyectar el objeto MemcachedClient

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    
    
    @Autowired
    private CodeUtils codeUtils;
    @Autowired
    private MemcachedClient memcachedClient;

    public String sendCodeToSMS(String tele) {
    
    
        String code = codeUtils.generator(tele);
        try {
    
    
            memcachedClient.set(tele,10,code);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return code;
    }

    public boolean checkCode(SMSCode smsCode) {
    
    
        String code = null;
        try {
    
    
            code = memcachedClient.get(smsCode.getTele()).toString();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return smsCode.getCode().equals(code);
    }
}

Use la operación set para establecer el valor de la memoria caché y use la operación get para obtener el valor, que en realidad está más en línea con los hábitos de nuestros desarrolladores.

En el código anterior, la configuración del servidor está codificada en el código, y los datos se extraen y se convierten en un atributo de configuración independiente.

Definir propiedades de configuración

​ El siguiente proceso se lleva a cabo utilizando el método de configuración de atributos aprendido en el período anterior. La operación actual es útil para comprender muchos conocimientos en los principios.

  • Defina la clase de configuración, cargue los atributos de configuración necesarios y lea la información del nodo memcached en el archivo de configuración

    @Component
    @ConfigurationProperties(prefix = "memcached")
    @Data
    public class XMemcachedProperties {
          
          
        private String servers;
        private int poolSize;
        private long opTimeout;
    }
    
  • Definir la información del nodo memcached

    memcached:
      servers: localhost:11211
      poolSize: 10
      opTimeout: 3000
    
  • Cargar información en la clase de configuración memcached

@Configuration
public class XMemcachedConfig {
    
    
    @Autowired
    private XMemcachedProperties props;
    @Bean
    public MemcachedClient getMemcachedClient() throws IOException {
    
    
        MemcachedClientBuilder memcachedClientBuilder = new XMemcachedClientBuilder(props.getServers());
        memcachedClientBuilder.setConnectionPoolSize(props.getPoolSize());
        memcachedClientBuilder.setOpTimeout(props.getOpTimeout());
        MemcachedClient memcachedClient = memcachedClientBuilder.build();
        return memcachedClient;
    }
}

Resumir

  1. Después de instalar Memcached, debe iniciar el servicio correspondiente para proporcionar la función de almacenamiento en caché de forma externa. La instalación del servicio Memcached requiere permisos de administrador del sistema de Windows.
  2. Dado que Springboot no proporciona una solución de integración de caché para Memcached, es necesario crear la caché de operaciones del cliente xmemcached en forma de codificación manual.
  3. Después de importar las coordenadas de xmemcached, cree una clase de configuración de memcached y registre el bean correspondiente a MemcachedClient para operar el caché
  4. Las propiedades necesarias para inicializar el objeto MemcachedClient se pueden cargar en forma de una clase de propiedad de configuración personalizada

5.1.6, caché jetcache integrada SpringBoot

En la actualidad, los cachés que usamos son A o B. ¿Se pueden usar AB juntos? Arregla eso ahora. La integración de springboot para el caché solo permanece en el caché. Si el caché en sí no admite el uso de AB al mismo tiempo, Springboot no puede hacer nada al respecto. Por lo tanto, si desea resolver el problema de usar AB caché juntos, debe encontrar un caché que admita tanto AB como AB. Usado junto con el caché, ¿existe tal caché? Realmente hay, producido por Ali, jetcache.

Estrictamente hablando, jetcache no es una solución de almacenamiento en caché. Solo se puede decir que es un marco de almacenamiento en caché y luego poner otros cachés en jetcache para su administración, de modo que pueda admitir el uso de caché AB. Y jetcache se refiere a la idea de la caché integrada de springboot, el uso general de la tecnología es muy similar a la idea de la solución de caché de springboot. Primero usemos jetcache y luego hablemos de algunas funciones pequeñas en él.

​ Antes de hacerlo, aclaremos que jetcache no puede simplemente tomar dos cachés y juntarlos. Actualmente, jetcache admite dos tipos de esquemas de almacenamiento en caché: almacenamiento en caché local y almacenamiento en caché remoto, que son los siguientes:

  • Caché local (Local)
    • LinkedHashMap
    • Cafeína
  • Caché remota (remota)
    • redis
    • Tres

De hecho, algunas personas me preguntaron, ¿por qué jetcache solo admite 2+2 cachés? Ali desarrolló esta tecnología principalmente para satisfacer sus propias necesidades. Debe haber solo 1+1 al principio, y cambiar gradualmente a 2+2. Lo siguiente utiliza la solución LinkedHashMap+Redis para realizar el uso simultáneo de soluciones de caché locales y remotas.

5.1.6.1, solución remota pura

Paso ① : Importe springboot para integrar el iniciador de coordenadas correspondiente a jetcache. La solución remota predeterminada para las coordenadas actuales es redis

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>2.6.2</version>
</dependency>

Paso ② : Configuración básica de la solución remota

jetcache:
  remote:
    default:
      type: redis
      host: localhost
      port: 6379
      poolConfig:
        maxTotal: 50

Entre ellos, poolConfig es un elemento obligatorio, de lo contrario se informará un error

Paso 3 : habilite el caché y marque la anotación @EnableCreateCacheAnnotation arriba de la clase de arranque para configurar el programa springboot para crear un caché en forma de anotaciones

@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
public class Springboot20JetCacheApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(Springboot20JetCacheApplication.class, args);
    }
}

Paso ④ : Cree un Caché de objeto de caché y use la anotación @CreateCache para marcar la información de caché actual y luego use la API del objeto Caché para operar el caché, poner caché de escritura y obtener caché de lectura.

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    
    
    @Autowired
    private CodeUtils codeUtils;
    
    @CreateCache(name="jetCache_",expire = 10,timeUnit = TimeUnit.SECONDS)
    private Cache<String ,String> jetCache;

    public String sendCodeToSMS(String tele) {
    
    
        String code = codeUtils.generator(tele);
        jetCache.put(tele,code);
        return code;
    }

    public boolean checkCode(SMSCode smsCode) {
    
    
        String code = jetCache.get(smsCode.getTele());
        return smsCode.getCode().equals(code);
    }
}

A través del jetcache mencionado anteriormente que usa la solución remota para conectarse a redis, se puede ver que la operación de la interfaz de jetcache cuando se opera el caché está más en línea con los hábitos del desarrollador. Al usar el caché, primero obtenga el objeto de caché Caché , poner los datos en él y sacar los datos, que es más simple y más fácil de entender. Y cuando jetcache opera el caché, puede establecer un tiempo de caducidad para un determinado objeto de caché y colocar el mismo tipo de datos en el caché para facilitar la gestión del período efectivo.

​ El esquema anterior utiliza el caché predeterminado definido en la configuración. De hecho, este valor predeterminado es un nombre, que se puede escribir o agregar a voluntad. Por ejemplo, para agregar otra solución de almacenamiento en caché, consulte la siguiente configuración:

jetcache:
  remote:
    default:
      type: redis
      host: localhost
      port: 6379
      poolConfig:
        maxTotal: 50
    sms:
      type: redis
      host: localhost
      port: 6379
      poolConfig:
        maxTotal: 50

​ Si desea usar un caché cuyo nombre es sms, debe especificar el área de parámetros al crear el caché y declarar que usará el caché correspondiente

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    
    
    @Autowired
    private CodeUtils codeUtils;
    
    @CreateCache(area="sms",name="jetCache_",expire = 10,timeUnit = TimeUnit.SECONDS)
    private Cache<String ,String> jetCache;

    public String sendCodeToSMS(String tele) {
    
    
        String code = codeUtils.generator(tele);
        jetCache.put(tele,code);
        return code;
    }

    public boolean checkCode(SMSCode smsCode) {
    
    
        String code = jetCache.get(smsCode.getTele());
        return smsCode.getCode().equals(code);
    }
}

5.1.6.2, solución local pura

En la solución remota, remoto se usa en la configuración para indicar remoto y local es local, pero el tipo es diferente.

Paso ① : Importe springboot para integrar las coordenadas correspondientes al iniciador de jetcache

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>2.6.2</version>
</dependency>

Paso ② : Configuración básica de caché local

jetcache:
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson

Para acelerar la velocidad de coincidencia de claves durante la adquisición de datos, jetcache requiere el convertidor de tipo de clave especificado. En pocas palabras, si proporciona un objeto como clave, primero usaré el convertidor de tipo de clave para convertirlo en una cadena y luego guardarlo. Cuando se obtienen los datos, aún se convierten en una cadena utilizando primero el objeto dado y luego se comparan de acuerdo con la cadena. Dado que jetcache es la tecnología de Ali, se recomienda utilizar fastjson de Ali para el convertidor de tipo de clave.

Paso ③ : habilitar el almacenamiento en caché

@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
public class Springboot20JetCacheApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(Springboot20JetCacheApplication.class, args);
    }
}

Paso ④ : al crear el objeto de caché Caché, marque el uso actual del caché local

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    
    
    @CreateCache(name="jetCache_",expire = 1000,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.LOCAL)
    private Cache<String ,String> jetCache;

    public String sendCodeToSMS(String tele) {
    
    
        String code = codeUtils.generator(tele);
        jetCache.put(tele,code);
        return code;
    }

    public boolean checkCode(SMSCode smsCode) {
    
    
        String code = jetCache.get(smsCode.getTele());
        return smsCode.getCode().equals(code);
    }
}

​ cacheType controla si el caché actual usa un caché local o un caché remoto.Configurar cacheType=CacheType.LOCAL significa usar un caché local.

5.1.6.3, solución local + remota

​ Existen métodos locales y remotos, ¿cómo configurar las dos soluciones juntas? De hecho, es suficiente combinar las dos configuraciones juntas.

jetcache:
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson
  remote:
    default:
      type: redis
      host: localhost
      port: 6379
      poolConfig:
        maxTotal: 50
    sms:
      type: redis
      host: localhost
      port: 6379
      poolConfig:
        maxTotal: 50

Al crear un caché, configure cacheType en AMBOS, es decir, el caché local y el caché remoto se utilizarán al mismo tiempo.

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    
    
    @CreateCache(name="jetCache_",expire = 1000,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.BOTH)
    private Cache<String ,String> jetCache;
}

Si cacheType no está configurado, el valor predeterminado es REMOTE, es decir, solo se usa la solución de caché remota. Para la configuración de jetcache, consulte la siguiente información

Atributos valores predeterminados ilustrar
jetcache.statIntervalMinutes 0 Intervalo de estadísticas, 0 significa que no hay estadísticas
jetcache.hiddenPackages ninguno Al generar automáticamente el nombre, oculta el prefijo del nombre del paquete especificado
jetcache.[local|remoto].${área}.type ninguno Tipo de caché, soporte local linkedhashmap, cafeína, soporte remoto redis, tair
jetcache.[local|remoto].${área}.keyConvertor ninguno convertidor de claves, actualmente solo es compatible con fastjson
jetcache.[local|remoto].${área}.valueEncoder Java Solo se debe especificar el caché de tipo remoto, java y kryo son opcionales
jetcache.[local|remoto].${área}.valueDecoder Java Solo se debe especificar el caché de tipo remoto, java y kryo son opcionales
jetcache.[local|remoto].${área}.limit 100 Solo se necesita especificar el caché de tipo local y el número máximo de elementos de la instancia de caché
jetcache.[local|remoto].${área}.expireAfterWriteInMillis Sin precedentes Tiempo de caducidad predeterminado, en milisegundos
jetcache.local.${área}.expireAfterAccessInMillis 0 Solo las cachés locales son válidas, en milisegundos, el intervalo máximo de inactividad

El esquema anterior solo admite el control manual del almacenamiento en caché, pero el método de almacenamiento en caché en el esquema springcache es particularmente útil. Agregue una anotación a un método y el método usará automáticamente el caché. jetcache también proporciona la función correspondiente, es decir, el método de almacenamiento en caché.

caché de métodos

jetcache proporciona una solución de almacenamiento en caché de métodos, pero el nombre ha cambiado. Simplemente use la anotación @Cached arriba de la interfaz de operación correspondiente

Paso ① : Importe springboot para integrar las coordenadas correspondientes al iniciador de jetcache

<dependency>
    <groupId>com.alicp.jetcache</groupId>
    <artifactId>jetcache-starter-redis</artifactId>
    <version>2.6.2</version>
</dependency>

Paso ② : Configurar el caché

jetcache:
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson
  remote:
    default:
      type: redis
      host: localhost
      port: 6379
      keyConvertor: fastjson
      valueEncode: java
      valueDecode: java
      poolConfig:
        maxTotal: 50
    sms:
      type: redis
      host: localhost
      port: 6379
      poolConfig:
        maxTotal: 50

Dado que la memoria caché de redis no admite guardar objetos, es necesario configurar redis para realizar la conversión de tipo cuando los datos de tipo de objeto ingresan en redis. Es necesario configurar keyConvertor para indicar el método de conversión de tipo de la clave y marcar el tipo de conversión del valor al mismo tiempo. Cuando el valor ingresa a redis, es de tipo java, marque valueEncode como java, convierta el valor a java cuando lea de redis y marque valueDecode como java.

Tenga en cuenta que para obtener el valor del tipo de objeto que ingresa y sale de redis, es necesario asegurarse de que los datos del tipo de objeto que ingresan y salen de redis deben implementar la interfaz de serialización.

@Data
public class Book implements Serializable {
    
    
    private Integer id;
    private String type;
    private String name;
    private String description;
}

Paso ③ : habilite la función de almacenamiento en caché de métodos cuando el almacenamiento en caché esté habilitado y configure los paquetes base para indicar en qué paquetes está habilitado el almacenamiento en caché de métodos

@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
//开启方法注解缓存
@EnableMethodCache(basePackages = "com.test")
public class Springboot20JetCacheApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(Springboot20JetCacheApplication.class, args);
    }
}

Paso ④ : use la anotación @Cached para marcar el método actual usando el caché

@Service
public class BookServiceImpl implements BookService {
    
    
    @Autowired
    private BookDao bookDao;
    
    @Override
    @Cached(name="book_",key="#id",expire = 3600,cacheType = CacheType.REMOTE)
    public Book getById(Integer id) {
    
    
        return bookDao.selectById(id);
    }
}

5.1.6.4 Sincronización de datos de soluciones remotas

Dado que los datos guardados por redis en la solución remota pueden ser compartidos por varios clientes, existe un problema de sincronización de datos. Jetcache proporciona 3 anotaciones para resolver este problema, sincronizando respectivamente los datos de la caché durante las operaciones de actualización y eliminación, y actualizando periódicamente los datos al leer la caché.

actualizar caché

@CacheUpdate(name="book_",key="#book.id",value="#book")
public boolean update(Book book) {
    
    
    return bookDao.updateById(book) > 0;
}

borrar caché

@CacheInvalidate(name="book_",key = "#id")
public boolean delete(Integer id) {
    
    
    return bookDao.deleteById(id) > 0;
}

Actualizar el caché regularmente

@Cached(name="book_",key="#id",expire = 3600,cacheType = CacheType.REMOTE)
@CacheRefresh(refresh = 5)
public Book getById(Integer id) {
    
    
    return bookDao.selectById(id);
}

5.1.6.5, Informe de datos

jetcache también proporciona una función de informe de datos simple para ayudar a los desarrolladores a ver rápidamente la información de aciertos de caché, solo es necesario agregar una configuración

jetcache:
  statIntervalMinutes: 1

Después de la configuración, la información de aciertos de datos de caché se mostrará en la consola cada 1 minuto

[DefaultExecutor] c.alicp.jetcache.support.StatInfoLogger  : jetcache stat from 2022-02-28 09:32:15,892 to 2022-02-28 09:33:00,003
cache    |    qps|   rate|   get|    hit|   fail|   expire|   avgLoadTime|   maxLoadTime
---------+-------+-------+------+-------+-------+---------+--------------+--------------
book_    |   0.66| 75.86%|    29|     22|      0|        0|          28.0|           188
---------+-------+-------+------+-------+-------+---------+--------------+--------------

Resumir

  1. Jetcache es una solución de almacenamiento en caché similar a Springcache. No tiene una función de almacenamiento en caché en sí misma. Proporciona una solución de almacenamiento en caché que utiliza tanto el almacenamiento en caché local como el almacenamiento en caché remoto en múltiples niveles.
  2. La solución de almacenamiento en caché proporcionada por jetcache está limitada por los esquemas admitidos actualmente. El caché local admite dos tipos y el caché remoto admite dos tipos.
  3. Preste atención al problema de conversión de tipo cuando los datos ingresan al caché remoto
  4. Jetcache proporciona almacenamiento en caché de métodos y proporciona funciones de actualización y actualización de caché correspondientes
  5. Jetcache proporciona un informe de aciertos de información de caché simple para facilitar a los desarrolladores el seguimiento de aciertos de datos de caché en tiempo real.

5.1.7, caché j2cache integrada SpringBoot

jetcache puede construir un caché de varios niveles dentro de un rango limitado, pero no es lo suficientemente flexible para coincidir con el caché a voluntad. Ahora presentamos un marco de integración de caché que puede coincidir con la solución de caché a voluntad, j2cache. Expliquemos cómo usar este marco de almacenamiento en caché, tomando como ejemplo la integración de Ehcache y redis:

Paso ① : Importar coordenadas j2cache, redis, ehcache

<dependency>
    <groupId>net.oschina.j2cache</groupId>
    <artifactId>j2cache-core</artifactId>
    <version>2.8.4-release</version>
</dependency>
<dependency>
    <groupId>net.oschina.j2cache</groupId>
    <artifactId>j2cache-spring-boot2-starter</artifactId>
    <version>2.8.0-release</version>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>

El iniciador de j2cache contiene coordenadas redis de forma predeterminada, y se recomienda oficialmente usar redis como caché de segundo nivel, por lo que no es necesario importar coordenadas redis aquí.

Paso ② : Configure los cachés de primer y segundo nivel, y configure el método de transferencia de datos entre los cachés de primer y segundo nivel. La configuración se escribe en el archivo denominado j2cache.properties. Si usa ehcache, debe agregar el archivo de configuración de ehcache por separado

# 1级缓存
j2cache.L1.provider_class = ehcache
ehcache.configXml = ehcache.xml

# 2级缓存
j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
j2cache.L2.config_section = redis
redis.hosts = localhost:6379

# 1级缓存中的数据如何到达二级缓存
j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy

La configuración aquí no se puede configurar indiscriminadamente, y debe consultar las instrucciones de configuración oficiales. Por ejemplo, el proveedor de primer nivel elige ehcache, y el nombre del proveedor es solo un ehcache, pero cuando el proveedor de segundo nivel elige redis, necesita escribir un nombre de clase de proveedor de Redis integrado en Spring dedicado SpringRedisProvider, y este nombre no es disponible en todos los paquetes redis No se proporciona en el paquete de primavera. Por lo tanto, para configurar j2cache, debe consultar la configuración del documento oficial, y debe encontrar un paquete de integración dedicado e importar las coordenadas correspondientes antes de que pueda usarse.

​ Una de las configuraciones más importantes de los cachés de primer y segundo nivel es el método de comunicación de datos entre los dos. Este tipo de configuración no es arbitrario, y los métodos de comunicación de datos proporcionados por diferentes soluciones de caché son muy diferentes. Necesita para comprobar el documento oficial para configurarlo.

Paso ③ : Usar caché

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    
    
    @Autowired
    private CodeUtils codeUtils;

    @Autowired
    private CacheChannel cacheChannel;

    public String sendCodeToSMS(String tele) {
    
    
        String code = codeUtils.generator(tele);
        cacheChannel.set("sms",tele,code);
        return code;
    }

    public boolean checkCode(SMSCode smsCode) {
    
    
        String code = cacheChannel.get("sms",smsCode.getTele()).asString();
        return smsCode.getCode().equals(code);
    }
}

​ El uso de j2cache es similar al de jetcache, pero no es necesario activar el interruptor para usarlo, y se puede usar definiendo directamente el objeto de caché. El nombre del objeto de caché es CacheChannel.

El uso de j2cache no es complicado, la configuración es el núcleo de j2cache, después de todo, es un marco de almacenamiento en caché integrado. Hay demasiadas configuraciones relacionadas con la memoria caché, puede consultar la descripción en el archivo j2cache.properties en el paquete principal de j2cache-core. como sigue:

#J2Cache configuration
#########################################
# Cache Broadcast Method
# values:
# jgroups -> use jgroups's multicast
# redis -> use redis publish/subscribe mechanism (using jedis)
# lettuce -> use redis publish/subscribe mechanism (using lettuce, Recommend)
# rabbitmq -> use RabbitMQ publisher/consumer mechanism
# rocketmq -> use RocketMQ publisher/consumer mechanism
# none -> don't notify the other nodes in cluster
# xx.xxxx.xxxx.Xxxxx your own cache broadcast policy classname that implement net.oschina.j2cache.cluster.ClusterPolicy
#########################################
j2cache.broadcast = redis

# jgroups properties
jgroups.channel.name = j2cache
jgroups.configXml = /network.xml

# RabbitMQ properties
rabbitmq.exchange = j2cache
rabbitmq.host = localhost
rabbitmq.port = 5672
rabbitmq.username = guest
rabbitmq.password = guest

# RocketMQ properties
rocketmq.name = j2cache
rocketmq.topic = j2cache
# use ; to split multi hosts
rocketmq.hosts = 127.0.0.1:9876

#########################################
# Level 1&2 provider
# values:
# none -> disable this level cache
# ehcache -> use ehcache2 as level 1 cache
# ehcache3 -> use ehcache3 as level 1 cache
# caffeine -> use caffeine as level 1 cache(only in memory)
# redis -> use redis as level 2 cache (using jedis)
# lettuce -> use redis as level 2 cache (using lettuce)
# readonly-redis -> use redis as level 2 cache ,but never write data to it. if use this provider, you must uncomment `j2cache.L2.config_section` to make the redis configurations available.
# memcached -> use memcached as level 2 cache (xmemcached),
# [classname] -> use custom provider
#########################################

j2cache.L1.provider_class = caffeine
j2cache.L2.provider_class = redis

# When L2 provider isn't `redis`, using `L2.config_section = redis` to read redis configurations
# j2cache.L2.config_section = redis

# Enable/Disable ttl in redis cache data (if disabled, the object in redis will never expire, default:true)
# NOTICE: redis hash mode (redis.storage = hash) do not support this feature)
j2cache.sync_ttl_to_redis = true

# Whether to cache null objects by default (default false)
j2cache.default_cache_null_object = true

#########################################
# Cache Serialization Provider
# values:
# fst -> using fast-serialization (recommend)
# kryo -> using kryo serialization
# json -> using fst's json serialization (testing)
# fastjson -> using fastjson serialization (embed non-static class not support)
# java -> java standard
# fse -> using fse serialization
# [classname implements Serializer]
#########################################

j2cache.serialization = json
#json.map.person = net.oschina.j2cache.demo.Person

#########################################
# Ehcache configuration
#########################################

# ehcache.configXml = /ehcache.xml

# ehcache3.configXml = /ehcache3.xml
# ehcache3.defaultHeapSize = 1000

#########################################
# Caffeine configuration
# caffeine.region.[name] = size, xxxx[s|m|h|d]
#
#########################################
caffeine.properties = /caffeine.properties

#########################################
# Redis connection configuration
#########################################

#########################################
# Redis Cluster Mode
#
# single -> single redis server
# sentinel -> master-slaves servers
# cluster -> cluster servers (数据库配置无效,使用 database = 0)
# sharded -> sharded servers  (密码、数据库必须在 hosts 中指定,且连接池配置无效 ; redis://user:[email protected]:6379/0)
#
#########################################

redis.mode = single

#redis storage mode (generic|hash)
redis.storage = generic

## redis pub/sub channel name
redis.channel = j2cache
## redis pub/sub server (using redis.hosts when empty)
redis.channel.host =

#cluster name just for sharded
redis.cluster_name = j2cache

## redis cache namespace optional, default[empty]
redis.namespace =

## redis command scan parameter count, default[1000]
#redis.scanCount = 1000

## connection
# Separate multiple redis nodes with commas, such as 192.168.0.10:6379,192.168.0.11:6379,192.168.0.12:6379

redis.hosts = 127.0.0.1:6379
redis.timeout = 2000
redis.password =
redis.database = 0
redis.ssl = false

## redis pool properties
redis.maxTotal = 100
redis.maxIdle = 10
redis.maxWaitMillis = 5000
redis.minEvictableIdleTimeMillis = 60000
redis.minIdle = 1
redis.numTestsPerEvictionRun = 10
redis.lifo = false
redis.softMinEvictableIdleTimeMillis = 10
redis.testOnBorrow = true
redis.testOnReturn = false
redis.testWhileIdle = true
redis.timeBetweenEvictionRunsMillis = 300000
redis.blockWhenExhausted = false
redis.jmxEnabled = false

#########################################
# Lettuce scheme
#
# redis -> single redis server
# rediss -> single redis server with ssl
# redis-sentinel -> redis sentinel
# redis-cluster -> cluster servers
#
#########################################

#########################################
# Lettuce Mode
#
# single -> single redis server
# sentinel -> master-slaves servers
# cluster -> cluster servers (数据库配置无效,使用 database = 0)
# sharded -> sharded servers  (密码、数据库必须在 hosts 中指定,且连接池配置无效 ; redis://user:[email protected]:6379/0)
#
#########################################

## redis command scan parameter count, default[1000]
#lettuce.scanCount = 1000
lettuce.mode = single
lettuce.namespace =
lettuce.storage = hash
lettuce.channel = j2cache
lettuce.scheme = redis
lettuce.hosts = 127.0.0.1:6379
lettuce.password =
lettuce.database = 0
lettuce.sentinelMasterId =
lettuce.maxTotal = 100
lettuce.maxIdle = 10
lettuce.minIdle = 10
# timeout in milliseconds
lettuce.timeout = 10000
# redis cluster topology refresh interval in milliseconds
lettuce.clusterTopologyRefresh = 3000

#########################################
# memcached server configurations
# refer to https://gitee.com/mirrors/XMemcached
#########################################

memcached.servers = 127.0.0.1:11211
memcached.username =
memcached.password =
memcached.connectionPoolSize = 10
memcached.connectTimeout = 1000
memcached.failureMode = false
memcached.healSessionInterval = 1000
memcached.maxQueuedNoReplyOperations = 100
memcached.opTimeout = 100
memcached.sanitizeKeys = false

Resumir

  1. j2cache es un marco de almacenamiento en caché, no tiene una función de almacenamiento en caché en sí mismo, proporciona una variedad de soluciones de integración de almacenamiento en caché
  2. j2cache necesita configurar cachés en todos los niveles a través de configuraciones complejas, así como la forma de intercambio de datos entre cachés
  3. La interfaz de operación j2cache se implementa a través de CacheChannel

5.1.8, SpringBoot integra caché de cafeína

Tres estrategias de reciclaje para el caché de cafeína

  • Basado en tamaño: tamaño máximo

  • Basado en el tiempo:

    • expireAfterAccess El temporizador de caducidad del último acceso
    • expireAfterWrite expira cuando la última escritura
  • Basado en referencias: solo caducan las referencias blandas y las referencias débiles

La clase Caffeine es equivalente a la clase Builder del modo builder. El Cache se configura a través de la clase Caffeine. La configuración de un Cache tiene los siguientes parámetros:

  • expireAfterWrite: comience a cronometrar después de la última escritura en el caché y caduque después del tiempo especificado;

  • expireAfterAccess: comience a cronometrar después del último acceso o escritura y caduque después del tiempo especificado. Si siempre hay solicitudes para acceder a la clave, el caché nunca caducará.

  • refreshAfterWrite: cuánto tiempo para actualizar después de escribir. Esta actualización se activa de forma pasiva en función del acceso. Admite actualización asíncrona y actualización síncrona. Si se usa en combinación con expireAfterWrite, puede garantizar que incluso si no se puede acceder al caché, se eliminará después un intervalo de tiempo fijo De lo contrario, es fácil causar OOM si se usa solo;

  • expireAfter: estrategia de eliminación personalizada, bajo esta estrategia, Caffeine implementa diferentes tiempos de caducidad para diferentes claves a través del algoritmo de rueda de tiempo;

  • tamaño máximo: desaloje la cantidad de datos almacenados en caché por la misma estrategia de caché de acuerdo con el recuento de caché, en orden de acceso, tomando como ejemplo el tamaño máximo de 100, y si el valor excede 100, se desalojará el último caché de datos accedido.

  • maximumWeight:根据缓存的权重来进行驱逐(权重只是用于确定缓存大小,不会用于决定该缓存是否被驱逐)。maximumWeight与maximumSize不可以同时使用。

  • weakKeys:key设置为弱引用,在 GC 时可以直接淘汰;

  • weakValues:value设置为弱引用,在 GC 时可以直接淘汰;

  • softValues:value设置为软引用,在内存溢出前可以直接淘汰;

  • executor:选择自定义的线程池,默认的线程池实现是 ForkJoinPool.commonPool();

  • weigher:设置具体key权重;

  • recordStats:缓存的统计数据,比如命中率等;

  • removalListener:缓存淘汰监听器;

  • writer:缓存写入、更新、淘汰的监听器。

主要基于Spring缓存注解@Cacheable、@CacheEvict、@CachePut的方式使用

  • @Cacheable :触发缓存读取操作,用于查询方法上,如果缓存中找到则直接取出缓存并返回,否则执行目标方法并将结果缓存。

  • @CachePut :触发缓存更新的方法上,与 Cacheable 相比,该注解的方法始终都会被执行,并且使用方法返回的结果去更新缓存,适用于 insert 和 update 行为的方法上。

  • @CacheEvict :触发缓存失效,删除缓存项或者清空缓存,适用于 delete 方法上。

  • @Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)

  • @CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)

5.1.8.1、方式一:直接引入Caffeine依赖,然后使用Caffeine的函数实现缓存

5.1.8.1.1、引入maven相关依赖
<dependency>  
  <groupId>com.github.ben-manes.caffeine</groupId>  
    <artifactId>caffeine</artifactId>  
</dependency>
5.1.8.1.2、设置缓存的配置选项
@EnableCaching
@Configuration
public class CacheConfig {
    
    
 
    @Bean
    public Cache<String, Object> caffeineCache() {
    
    
        return Caffeine.newBuilder()
                // 设置最后一次写入或访问后经过固定时间过期
                .expireAfterWrite(60, TimeUnit.SECONDS)
                // 初始的缓存空间大小
                .initialCapacity(100)
                // 缓存的最大条数
                .maximumSize(1000)
                .build();
    }
}
5.1.8.1.3、给服务添加缓存功能
 
@Slf4j
@Service
public class UserInfoServiceImpl {
    
    
 
    /**
     * 模拟数据库存储数据
     */
    private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();
 
    @Autowired
    Cache<String, Object> caffeineCache;
 
    public void addUserInfo(UserInfo userInfo) {
    
    
        userInfoMap.put(userInfo.getId(), userInfo);
        // 加入缓存
        caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);
    }
 
    public UserInfo getByName(Integer id) {
    
    
        // 先从缓存读取
        caffeineCache.getIfPresent(id);
        UserInfo userInfo = (UserInfo) caffeineCache.asMap().get(String.valueOf(id));
        if (userInfo != null){
    
    
            return userInfo;
        }
        // 如果缓存中不存在,则从库中查找
        userInfo = userInfoMap.get(id);
        // 如果用户信息不为空,则加入缓存
        if (userInfo != null){
    
    
            caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);
        }
        return userInfo;
    }
 
    public UserInfo updateUserInfo(UserInfo userInfo) {
    
    
        if (!userInfoMap.containsKey(userInfo.getId())) {
    
    
            return null;
        }
        // 取旧的值
        UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
        // 替换内容
        if (!StringUtils.isEmpty(oldUserInfo.getAge())) {
    
    
            oldUserInfo.setAge(userInfo.getAge());
        }
        if (!StringUtils.isEmpty(oldUserInfo.getName())) {
    
    
            oldUserInfo.setName(userInfo.getName());
        }
        if (!StringUtils.isEmpty(oldUserInfo.getSex())) {
    
    
            oldUserInfo.setSex(userInfo.getSex());
        }
        // 将新的对象存储,更新旧对象信息
        userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
        // 替换缓存中的值
        caffeineCache.put(String.valueOf(oldUserInfo.getId()),oldUserInfo);
        return oldUserInfo;
    }
 
    @Override
    public void deleteById(Integer id) {
    
    
        userInfoMap.remove(id);
        // 从缓存中删除
        caffeineCache.asMap().remove(String.valueOf(id));
    }
}

5.1.8.2、方式二:引入Caffeine和Spring Cache依赖,使用SpringCache注解方法实现缓存

5.1.8.2.1 Introducir dependencias relacionadas con maven
 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>
5.1.8.2.2, configurar la clase de gestión de caché
@EnableCaching
@Configuration  
public class CacheConfig {
    
      
  
    /**  
     * 配置缓存管理器  
     *  
     * @return 缓存管理器  
     */  
    @Bean("caffeineCacheManager")  
    public CacheManager cacheManager() {
    
      
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();  
        cacheManager.setCaffeine(Caffeine.newBuilder()  
                // 设置最后一次写入或访问后经过固定时间过期  
                .expireAfterAccess(60, TimeUnit.SECONDS)  
                // 初始的缓存空间大小  
                .initialCapacity(100)  
                // 缓存的最大条数  
                .maximumSize(1000));  
        return cacheManager;  
    }  
}
5.1.8.2.3 Agregar función de caché al servicio
@Slf4j
@Service
@CacheConfig(cacheNames = "caffeineCacheManager")
public class UserInfoServiceImpl {
    
    
 
    /**
     * 模拟数据库存储数据
     */
    private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();
 
    @CachePut(key = "#userInfo.id")
    public void addUserInfo(UserInfo userInfo) {
    
    
        userInfoMap.put(userInfo.getId(), userInfo);
    }
 
    @Cacheable(key = "#id")
    public UserInfo getByName(Integer id) {
    
    
        return userInfoMap.get(id);
    }
 
    @CachePut(key = "#userInfo.id")
    public UserInfo updateUserInfo(UserInfo userInfo) {
    
    
        if (!userInfoMap.containsKey(userInfo.getId())) {
    
    
            return null;
        }
        // 取旧的值
        UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
        // 替换内容
        if (!StringUtils.isEmpty(oldUserInfo.getAge())) {
    
    
            oldUserInfo.setAge(userInfo.getAge());
        }
        if (!StringUtils.isEmpty(oldUserInfo.getName())) {
    
    
            oldUserInfo.setName(userInfo.getName());
        }
        if (!StringUtils.isEmpty(oldUserInfo.getSex())) {
    
    
            oldUserInfo.setSex(userInfo.getSex());
        }
        // 将新的对象存储,更新旧对象信息
        userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
        // 返回新对象信息
        return oldUserInfo;
    }
 
    @CacheEvict(key = "#id")
    public void deleteById(Integer id) {
    
    
        userInfoMap.remove(id);
    }
}

5.2 Tareas

5.2.1, Cuarzo

La tecnología de cuarzo es un marco de tareas cronometradas relativamente maduro, ¿cómo decirlo? Es un poco engorroso, y todos los que lo han usado saben que la configuración es un poco complicada. Después de que springboot lo integra, simplifica una serie de configuraciones y adopta configuraciones predeterminadas para muchas configuraciones, lo que simplifica mucho la etapa de desarrollo. Antes de aprender springboot a integrar Quartz, popularice varios conceptos de Quartz.

  • Obra (Job): se utiliza para definir la ejecución específica de la obra
  • Detalles del trabajo (JobDetail): se utiliza para describir información relacionada con el trabajo regular
  • Trigger (Disparador): describe la correspondencia entre los detalles del trabajo y el programador
  • Programador (Scheduler): se utiliza para describir las reglas de ejecución que activan el trabajo, generalmente usando expresiones cron para definir reglas

​ En pocas palabras, es lo que haces de forma regular, que es trabajo.Es imposible trabajar de una manera simple, y necesitas establecer alguna información detallada. Cuándo ejecutar el trabajo, establecer un programador puede entenderse simplemente como establecer el tiempo para ejecutar un trabajo. Tanto el trabajo como la programación se definen de forma independiente, ¿cómo encajan? Usa disparadores. Eso es todo. Comencemos springboot para integrar Quartz.

Paso ① : importe springboot para integrar el iniciador de cuarzo

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

Paso ② : defina el bean de tarea, hágalo de acuerdo con la especificación de desarrollo de Quartz, herede QuartzJobBean

public class MyQuartz extends QuartzJobBean {
    
    
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    
    
        System.out.println("quartz task run...");
    }
}

Paso ③ : Cree una clase de configuración de Quartz, defina los detalles del trabajo (JobDetail) y el bean desencadenante (Trigger)

@Configuration
public class QuartzConfig {
    
    
    @Bean
    public JobDetail printJobDetail(){
    
    
        //绑定具体的工作
        return JobBuilder.newJob(MyQuartz.class).storeDurably().build();
    }
    @Bean
    public Trigger printJobTrigger(){
    
    
        ScheduleBuilder schedBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
        //绑定对应的工作明细
        return TriggerBuilder.newTrigger().forJob(printJobDetail()).withSchedule(schedBuilder).build();
    }
}

Para establecer el trabajo específico correspondiente en los detalles del trabajo, utilice la operación newJob() para pasar el tipo de tarea de trabajo correspondiente.

​ El activador debe vincularse a la tarea, use la operación forJob() para pasar el objeto de detalles del trabajo vinculado. Aquí puede establecer un nombre para los detalles del trabajo y usar el enlace de nombre, o puede llamar directamente al enlace de método correspondiente. La regla central en el activador es el tiempo de ejecución. Aquí, el programador se usa para definir el tiempo de ejecución, y el método de descripción del tiempo de ejecución usa expresiones cron. Para conocer las reglas de las expresiones cron, puede consultar los cursos relevantes para aprender. Es un poco complicado y el formato no se puede configurar al azar. No se puede usar simplemente escribiendo un formato.

Resumir

  1. La integración de Springboot de Quartz consiste en entregar los objetos principales correspondientes a Quartz a la gestión de contenedores Spring, incluidos dos objetos, JobDetail y Trigger.
  2. El objeto JobDetail describe la información de ejecución del trabajo y debe vincularse a un objeto de tipo QuartzJobBean.
  3. El objeto Trigger define un disparador, es necesario especificar qué JobDetail está vinculado a él y configurar el programador del ciclo de ejecución al mismo tiempo.

5.2.2, Tarea

De acuerdo con las características de las tareas de cronometraje, Spring simplifica al extremo el desarrollo de las tareas de cronometraje. ¿cómo decir? Para hacer una tarea programada, siempre debes decirle al contenedor que tiene esta función, y luego decirle al bean correspondiente cuándo ejecutar la tarea a intervalos regulares, es así de simple, veamos cómo hacerlo juntos.

Paso ① : active la función de tareas programadas, active el interruptor de la función de tareas programadas en la clase de arranque y use la anotación @EnableScheduling

@SpringBootApplication
//开启定时任务功能
@EnableScheduling
public class SpringbootTaskApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(SpringbootTaskApplication.class, args);
    }
}

Paso ② : Defina el Bean. Sobre la operación correspondiente que se ejecutará regularmente, use la anotación @Scheduled para definir el tiempo de ejecución. El método de descripción del tiempo de ejecución sigue siendo una expresión cron

@Component
public class MyBean {
    
    
    @Scheduled(cron = "0/1 * * * * ?")
    public void print(){
    
    
        System.out.println(Thread.currentThread().getName()+" :spring task run...");
    }
}

​ Terminado, esto completa la configuración de la tarea programada. La sensación general es que no falta nada, pero en lugar de extraer toda la información en beans, utiliza directamente el enlace de anotaciones para ejecutar tareas a intervalos regulares.

​ La forma de configurar las tareas de temporización se puede realizar a través del archivo de configuración

spring:
  task:
   	scheduling:
      pool:
       	size: 1							# 任务调度线程池大小 默认 1
      thread-name-prefix: ssm_      	# 调度线程名称前缀 默认 scheduling-      
        shutdown:
          await-termination: false		# 线程池关闭时等待所有任务完成
          await-termination-period: 10s	# 调度线程关闭前最大等待时间,确保最后一定关闭

Resumir

  1. La tarea de primavera debe usar la anotación @EnableScheduling para habilitar la función de tarea programada

  2. Establezca el período de ejecución para la tarea de ejecución programada, descrita por la expresión cron

5.3 Correo

Antes de aprender a enviar correos electrónicos, comprenda 3 conceptos, que regulan los estándares en el proceso de operación del correo electrónico.

  • SMTP (Protocolo simple de transferencia de correo): Protocolo simple de transferencia de correo, un protocolo de transferencia para enviar correo electrónico
  • POP3 (Protocolo de oficina de correos - Versión 3): un protocolo estándar para recibir correo electrónico
  • IMAP (Protocolo de acceso al correo de Internet): Protocolo de mensajes de Internet, un protocolo alternativo a POP3

En pocas palabras, SMPT es el estándar para enviar correos electrónicos, POP3 es el estándar para recibir correos electrónicos e IMAP es una actualización de POP3. El funcionamiento de los correos electrónicos en nuestros programas de producción suele ser el envío de correos electrónicos, por lo que SMTP es el foco de uso, y la mayoría de la recepción de correos electrónicos se realiza a través de clientes de correo electrónico, por lo que hay muy pocos códigos para desarrollar correos electrónicos. A menos que desee leer el contenido del correo, luego analizarlo y realizar un procesamiento unificado de la función de correo. Por ejemplo, los buzones de recursos humanos reciben currículos de los solicitantes de empleo, que se pueden leer y procesar de manera unificada. Pero, ¿por qué no crear un sistema de entrega de currículums independiente? Entonces, qué requisito tan extraño, porque si quieres recibir correos electrónicos, debes estandarizar el formato de escritura del remitente. Esto es un poco difícil y es muy fácil recibir ataques externos. No puedes usar una lista blanca para recibir correos electrónicos. . Si puede usar la lista blanca para recibir correos electrónicos y luego analizarlos, es mejor desarrollar un sistema dedicado a las personas en la lista blanca, que es más seguro. Veamos cómo springboot integra javamail para enviar correos electrónicos.

5.3.1 Envío de correos electrónicos simples

Paso ① : importe springboot para integrar javamail starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

Paso ② : configure la información de inicio de sesión del buzón

spring:
  mail:
    host: smtp.126.com
    username: [email protected]
    password: test

El programa Java solo se usa para enviar correos electrónicos, y el proveedor de correo electrónico proporciona la función de correo electrónico, por lo que aquí se debe usar el servicio de correo electrónico de otra persona y se debe configurar la información correspondiente.

​ La configuración de host es el protocolo de host que proporciona servicios de correo. El programa actual solo se usa para enviar correo, por lo que la configuración es el protocolo smtp.

La contraseña no es la contraseña de inicio de sesión de la cuenta de correo electrónico, sino una contraseña cifrada proporcionada por el proveedor de correo electrónico para garantizar la seguridad del sistema. De lo contrario, si personal externo accede y descarga el archivo de configuración a través de la dirección y obtiene directamente la contraseña del correo electrónico, existirá un gran riesgo de seguridad. El método para obtener la contraseña es diferente para cada proveedor de correo, por lo que se omite aquí. Puede encontrar palabras clave como POP3 o IMAP en la página de configuración del proveedor de correo para encontrar la ubicación de adquisición correspondiente. El siguiente ejemplo es solo para referencia:

inserte la descripción de la imagen aquí

Paso ③ : use la interfaz JavaMailSender para enviar correo

@Service
public class SendMailServiceImpl implements SendMailService {
    
    
    @Autowired
    private JavaMailSender javaMailSender;

    //发送人
    private String from = "[email protected]";
    //接收人
    private String to = "[email protected]";
    //标题
    private String subject = "测试邮件";
    //正文
    private String context = "测试邮件正文内容";

    @Override
    public void sendMail() {
    
    
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from+"(小甜甜)");
        message.setTo(to);
        message.setSubject(subject);
        message.setText(context);
        javaMailSender.send(message);
    }
}

​ Encapsule la información necesaria (remitente, destinatario, cargo, cuerpo) para enviar correos electrónicos en un objeto SimpleMailMessage y podrá configurar el apodo del remitente de acuerdo con las reglas.

5.3.2 Envío de correos electrónicos de varios componentes (archivos adjuntos, texto complejo)

Para enviar un correo electrónico simple, solo necesita proporcionar la información básica correspondiente de 4. Si desea enviar un correo electrónico complejo, debe cambiar el objeto del correo electrónico. Se pueden enviar correos electrónicos especiales usando MimeMessage.

Enviar correo del cuerpo de la página web

@Service
public class SendMailServiceImpl2 implements SendMailService {
    
    
    @Autowired
    private JavaMailSender javaMailSender;

    //发送人
    private String from = "[email protected]";
    //接收人
    private String to = "[email protected]";
    //标题
    private String subject = "测试邮件";
    //正文
    private String context = "<img src='ABC.JPG'/><a href='https://www.angyan.cn'>点开有惊喜</a>";

    public void sendMail() {
    
    
        try {
    
    
            MimeMessage message = javaMailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message);
            helper.setFrom(to+"(小甜甜)");
            helper.setTo(from);
            helper.setSubject(subject);
            helper.setText(context,true);		//此处设置正文支持html解析

            javaMailSender.send(message);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

Enviar correo electrónico con archivo adjunto

@Service
public class SendMailServiceImpl2 implements SendMailService {
    
    
    @Autowired
    private JavaMailSender javaMailSender;

    //发送人
    private String from = "[email protected]";
    //接收人
    private String to = "[email protected]";
    //标题
    private String subject = "测试邮件";
    //正文
    private String context = "测试邮件正文";

    public void sendMail() {
    
    
        try {
    
    
            MimeMessage message = javaMailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message,true);		//此处设置支持附件
            helper.setFrom(to+"(小甜甜)");
            helper.setTo(from);
            helper.setSubject(subject);
            helper.setText(context);

            //添加附件
            File f1 = new File("springboot_23_mail-0.0.1-SNAPSHOT.jar");
            File f2 = new File("resources\\logo.png");

            helper.addAttachment(f1.getName(),f1);
            helper.addAttachment("test.png",f2);

            javaMailSender.send(message);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

Resumir

  1. La integración springboot de javamail en realidad simplifica el proceso de inicialización del objeto de cliente JavaMailSender para enviar correos electrónicos y simplifica el proceso de desarrollo al cargar información en forma de configuración.

5.4 Mensaje

5.4.1 El concepto de mensaje

En un sentido amplio, las noticias son en realidad información, pero es diferente de la información. La información suele definirse como un conjunto de datos, mientras que un mensaje no solo tiene las características de datos, sino que también tiene el concepto de fuente y recepción del mensaje. Por lo general, la parte que envía el mensaje se denomina productor del mensaje y la parte que recibe el mensaje se denomina consumidor del mensaje. Después de tal comparación, se encuentra que en realidad hay una gran diferencia entre noticias e información.

¿Por qué establecer productores y consumidores? Esto es lo que significa hablar de noticias. La información suele ser un conjunto de datos, pero como hay productores y consumidores en el mensaje, la información contenida en el mensaje puede interpretarse dos veces, cuando el productor envía un mensaje, puede entenderse como el productor enviando un mensaje, o puede entenderse que el productor ha enviado un comando, cuando el consumidor recibe el mensaje, puede entenderse que el consumidor ha obtenido un mensaje, y también puede entenderse que el consumidor ha obtenido un comando. Por comparación, encontraremos que la información es un dato básico, y los comandos se pueden asociar con la siguiente acción de comportamiento, por lo que se puede entender que, según el mensaje recibido, es equivalente a obtener una acción de comportamiento y utilizar estas acciones de comportamiento. se puede organizar en una lógica de negocios para operaciones posteriores. En términos generales, las noticias son en realidad un conjunto de información, pero solo le dan un nuevo significado, porque hay un flujo de noticias y es un flujo direccional, lo que trae una nueva interpretación basada en el flujo de comportamiento. Según esta solución especial del mensaje, los desarrolladores pueden reemplazarlo con instrucciones en el código.

En cuanto a la comprensión de los mensajes, los principiantes siempre piensan que los datos dentro del mensaje son muy complicados, lo cual es un malentendido. Por ejemplo, envié un mensaje pidiéndole al destinatario que tradujera lo que se envió. Los principiantes pensarán que el mensaje contendrá el texto traducido, ya que esta operación debería realizar una operación de traducción en lugar de una operación de impresión. De hecho, este fenómeno está un poco sobreinterpretado, el mensaje enviado solo contiene el texto traducido, pero puedes confirmar qué hacer controlando a diferentes personas para que reciban este mensaje. Por ejemplo, el texto a traducir solo se envía al programa A, y el programa A solo puede realizar operaciones de traducción, por lo que se puede enviar información simple para completar servicios complejos, y se realizan diferentes operaciones al recibir diferentes asuntos del mensaje. , en lugar de Definir el comportamiento de la operación de los datos dentro del mensaje. Por supuesto, si el desarrollador desea incluir la información del tipo de operación en el mensaje, también es posible, pero el contenido del mensaje puede ser más simple y singular.

En cuanto al modo de trabajo del productor y consumidor del mensaje, el mensaje también se puede dividir en dos modos, consumo sincrónico y mensaje asincrónico.

​ El llamado mensaje síncrono significa que el productor envía el mensaje y espera a que el consumidor lo procese, después de que el consumidor termina de procesar, informa al productor del resultado, y luego el productor continúa ejecutando el negocio hacia abajo. Este modo es demasiado para la continuidad de la ejecución comercial del productor de la tarjeta En el desarrollo actual a nivel empresarial, los escenarios comerciales mencionados anteriormente generalmente no se procesan en forma de mensajes.

​ El llamado mensaje asíncrono significa que después de que el productor envía el mensaje, el productor continúa realizando otras acciones sin esperar a que el consumidor termine de procesar. Por ejemplo, el productor envía un mensaje de registro al sistema de registro. Después de enviarlo, el productor puede hacer otras cosas, sin prestar atención al resultado de la ejecución del sistema de registro. El sistema de registro continúa llevando a cabo la ejecución comercial en función de la información de registro recibida. Ya sea que simplemente registre el registro o registre el registro y envíe una alarma, no tiene nada que ver con el productor, por lo que la eficiencia de ejecución comercial del productor mejorará en gran medida. Y al agregar múltiples consumidores para procesar los mensajes enviados por el mismo productor, se puede mejorar la alta concurrencia del sistema, se puede mejorar la eficiencia del trabajo del sistema y se puede mejorar la experiencia del usuario. Una vez que un determinado consumidor se cae debido a varios problemas, no afectará el negocio, lo que mejora la alta disponibilidad del sistema.

5.4.2, especificación estándar de Java para procesar mensajes

En la actualidad, existen tres tipos de tecnologías de procesamiento de mensajes ampliamente utilizadas en el desarrollo a nivel empresarial, a saber:

  • JMS
  • AMQP
  • MQTT

¿Por qué hay tres categorías en lugar de tres tecnologías? Debido a que todas estas son especificaciones, creo que la tecnología JDBC es una especificación, y el desarrollo se basa en el desarrollo de la especificación, y la operación depende de la clase de implementación. Por ejemplo, MySQL proporciona la implementación de JDBC, y la operación final depende de la implementación. Y estos tres tipos de especificaciones son para procesar mensajes asincrónicos, que también se ajustan a la esencia del diseño de los mensajes y manejan servicios asincrónicos. Popularizar las tres especificaciones de mensajes anteriores

5.4.2.1, JMS

​ JMS (Java Message Service), que es una especificación, es equivalente a la especificación JDBC y proporciona interfaces API relacionadas con los servicios de mensajes.

modelo de mensaje JMS

Hay dos modelos de mensajes especificados en la especificación JMS. Son el modelo punto a punto y el modelo de publicación-suscripción .

Modelo punto a punto : punto a punto, el productor enviará el mensaje a un contenedor que contiene el mensaje, generalmente usando el modelo de cola, y usará la cola para almacenar el mensaje. Los mensajes de una cola solo pueden ser consumidos por un consumidor, o el mensaje no se consume a tiempo, lo que genera un tiempo de espera. En este modelo, los productores y los consumidores están vinculados uno a uno.

Modelo de publicación : publicación-suscripción, el productor envía el mensaje a un contenedor que contiene el mensaje, que también se almacena utilizando el modelo de cola. Sin embargo, los mensajes pueden ser consumidos por múltiples consumidores, los productores y los consumidores son completamente independientes y no necesitan percibir la existencia del otro.

​ La clasificación anterior se distingue del proceso de producción y consumo del mensaje, de acuerdo a la información contenida en el mensaje, también se pueden dividir diferentes categorías.

Tipos de mensajes JMS

Según los tipos de datos contenidos en el mensaje, el mensaje se puede dividir en 6 tipos de mensajes.

  • Mensaje de texto
  • MapaMensaje
  • BytesMensaje
  • StreamMessage
  • ObjetoMensaje
  • Mensaje (solo encabezados y atributos de mensajes)

JMS aboga por diferentes tipos de mensajes y diferentes métodos de consumo, y se pueden seleccionar diferentes tipos de mensajes según las necesidades de uso. Pero esto también se ha convertido en su crítica, de la que hablaremos más adelante. En general, JMS es un conservador típico. Todo se basa en especificaciones J2EE, se define un conjunto de especificaciones, se definen varios estándares y se proporciona una gran cantidad de API bajo cada estándar. En la actualidad, hay bastantes tecnologías de middleware de mensajes implementadas por la especificación JMS.Después de todo, son utilizados por la familia real, y algunas personas deben lamerlos, como ActiveMQ, Redis y HornetMQ. Sin embargo, también existen algunas implementaciones no estándar, que hacen referencia al diseño estándar de JMS, pero que no cumplen completamente con sus especificaciones, tales como: RabbitMQ, RocketMQ.

5.4.2.2, AMQP

​ El advenimiento de JMS brinda un fuerte soporte normativo para el middleware de mensajes, pero ha sido criticado en el proceso de uso, como el extremadamente complicado mecanismo de procesamiento de mensajes de varios tipos establecido por JMS. Originalmente, era bueno clasificarlo y tratarlo, entonces, ¿por qué fue criticado? La razón es que el diseño de JMS es la especificación J2EE y lo pensamos desde la perspectiva del desarrollo de Java. Pero la realidad suele ser muy compleja. Por ejemplo, tengo un sistema A desarrollado por .NET y un sistema B desarrollado por Java. Ahora quiero enviar mensajes comerciales del sistema A al sistema B. Como resultado, los formatos de datos en ambos lados son inconsistentes y no se pueden operado. ¿No es JMS un formato de datos unificado? Proporciona 6 tipos de datos, siempre hay uno adecuado para usted. NO, ninguno de ellos se puede utilizar. Debido a que el lenguaje subyacente del sistema A no está desarrollado por el lenguaje Java, esos objetos no son compatibles en absoluto. Esto significa que si desea utilizar el sistema empresarial A existente para continuar con el desarrollo, ya no es posible, y debe derrocar y rehacer el sistema A desarrollado en el lenguaje Java.

En este momento, alguien sugirió que eres tan complicado, ¿por qué tienes tantos tipos? ¿Encontrar un tipo de datos de mensaje que todos admitan no resuelve este problema multiplataforma? Todo el mundo piensa, sí, así nació AMQP.

Solo de la descripción anterior, se puede percibir claramente que la aparición de AMQP resuelve el problema del tipo de mensajes utilizados en la entrega de mensajes, simplificando la complejidad, pero no derroca por completo la operación API de JMS, por lo que AMQP es solo Un protocolo que regula el formato de transmisión de datos.

​ AMQP (protocolo de cola de mensajes avanzado): un protocolo (protocolo de cola de mensajes avanzado, que también es una especificación de intermediario de mensajes), estandariza el formato de datos para el intercambio de red y es compatible con las operaciones JMS.

ventaja

Multiplataforma, proveedores de servidores, productores y consumidores pueden usar diferentes lenguajes para implementar

Tipos de mensajes JMS

​ Tipo de mensaje AMQP: byte[]

AMQP se ha expandido aún más sobre la base del modelo de mensajes JMS Además de los modelos punto a punto y publicación-suscripción, se han desarrollado varios modelos de mensajes nuevos para adaptarse a varios envíos de mensajes.

Modelo de mensaje AMQP

  • intercambio directo
  • intercambio de abanico
  • intercambio de temas
  • intercambio de encabezados
  • intercambio de sistema

En la actualidad, existen muchas tecnologías de middleware de mensajes que implementan el protocolo AMQP, y todas son tecnologías relativamente populares, como: RabbitMQ, StormMQ, RocketMQ

5.4.2.3, MQTT

La transmisión de telemetría de cola de mensajes MQTT (Message Queuing Telemetry Transport), diseñada para dispositivos pequeños, es uno de los componentes principales del ecosistema de Internet de las cosas (IOT). Dado que no hay intersección con el desarrollo de nivel empresarial de JavaEE, no se darán demasiadas explicaciones aquí.

Además de las tres tecnologías de mensajería asincrónica ampliamente utilizadas en las tres aplicaciones empresariales J2EE anteriores, existe otra tecnología que no se puede ignorar, Kafka.

5.4.2.4, KafKa

​ Kafka, un sistema de mensajería de publicación y suscripción distribuida de alto rendimiento, proporciona capacidades de mensajería en tiempo real. La tecnología Kafka no es un producto con la función principal de middleware de mensajes, pero tiene un modo de trabajo de publicación-suscripción y también se puede usar como middleware de mensajes, y no es raro en el desarrollo de nivel empresarial en la actualidad.

Esta sección explicará la integración de Springboot de varios middleware de mensajes en torno a varios esquemas de implementación en el contenido anterior. Dado que se deben instalar varios middleware de mensajes antes de su uso, el siguiente contenido se instala en el sistema Windows para reducir la dificultad de aprendizaje para todos los alumnos. La rutina básica es la misma que la solución NoSQL anterior, instale primero y luego integre.

5.4.3 El caso del envío de mensajes de texto al móvil para pedidos de compra

Para facilitar la siguiente demostración de varias tecnologías de middleware de mensajes, creamos un entorno de casos en el que se envían SMS a los usuarios cuando se genera un pedido durante el proceso de compra, y simulamos el proceso de uso de middleware de mensajes para enviar mensajes de texto a teléfonos móviles. .

Los requisitos del caso del código de verificación del teléfono móvil son los siguientes:

  • Al ejecutar el negocio de colocación de pedidos (simulando este proceso), llame al servicio de mensajes y pase la identificación del pedido para que se envíe al middleware de mensajes.

  • Después de que el servicio de procesamiento de mensajes recibe la identificación del pedido que se enviará, genera la identificación del pedido (simulando el envío de un mensaje de texto)

    Dado que no implica la lectura y escritura de datos, solo se desarrollan la capa comercial y la capa de presentación, y el código comercial para el procesamiento de SMS se desarrolla de forma independiente.El código es el siguiente:

ordenar negocios

​Interfaz de capa empresarial

public interface OrderService {
    
    
    void order(String id);
}

Simule la identificación del pedido entrante y ejecute el negocio de colocación del pedido. Los parámetros son configuraciones virtuales, que en realidad deberían ser la clase de entidad correspondiente al pedido.

​Implementación de la capa empresarial

@Service
public class OrderServiceImpl implements OrderService {
    
    
    @Autowired
    private MessageService messageService;
    
    @Override
    public void order(String id) {
    
    
        //一系列操作,包含各种服务调用,处理各种业务
        System.out.println("订单处理开始");
        //短信消息处理
        messageService.sendMessage(id);
        System.out.println("订单处理结束");
        System.out.println();
    }
}

​ La capa empresarial transfiere el servicio de procesamiento de mensajes MessageService

Servicios de la capa de presentación

@RestController
@RequestMapping("/orders")
public class OrderController {
    
    

    @Autowired
    private OrderService orderService;

    @PostMapping("{id}")
    public void order(@PathVariable String id){
    
    
        orderService.order(id);
    }
}

La interfaz de desarrollo externa de la capa de presentación, simplemente ingrese la identificación del pedido (simulación)

negocio de procesamiento de SMS

​Interfaz de capa empresarial

public interface MessageService {
    
    
    void sendMessage(String id);
    String doMessage();
}

La interfaz de la capa empresarial de procesamiento de SMS proporciona dos operaciones: envía el ID del pedido para que se procese al middleware de mensajes. La otra operación está diseñada actualmente para procesar mensajes. El proceso de procesamiento de mensajes real no debe realizarse manualmente, sino que debe realizarse automáticamente. Al diseño específico después de la implementación

​Implementación de la capa empresarial

@Service
public class MessageServiceImpl implements MessageService {
    
    
    private ArrayList<String> msgList = new ArrayList<String>();

    @Override
    public void sendMessage(String id) {
    
    
        System.out.println("待发送短信的订单已纳入处理队列,id:"+id);
        msgList.add(id);
    }

    @Override
    public String doMessage() {
    
    
        String id = msgList.remove(0);
        System.out.println("已完成短信发送业务,id:"+id);
        return id;
    }
}

En la implementación de la capa empresarial de procesamiento de SMS, el conjunto se utiliza para simular primero la cola de mensajes y observar el efecto.

Servicios de la capa de presentación

@RestController
@RequestMapping("/msgs")
public class MessageController {
    
    

    @Autowired
    private MessageService messageService;

    @GetMapping
    public String doMessage(){
    
    
        String id = messageService.doMessage();
        return id;
    }
}

La interfaz de capa de presentación de procesamiento de mensajes cortos desarrolla temporalmente una entrada para procesar mensajes, pero esta empresa es una interfaz simulada diseñada en la capa empresarial correspondiente, y la empresa real no necesita diseñar esta interfaz.

Empecemos Springboot para integrar varios middleware de mensajes, comenzando con ActiveMQ que cumple estrictamente con la especificación JMS.

5.4.4, SpringBoot integra ActiveMQ

ActiveMQ es un producto veterano entre los productos MQ. Es uno de los primeros productos MQ estándar. Antes de que apareciera el protocolo AMQP, ocupaba la gran mayoría del mercado de middleware de mensajes. Más tarde, debido a la aparición de los productos de la serie AMQP, se debilitó Actualmente, solo aparece en algunos productos que se ejecutan en línea y rara vez se usa en el desarrollo de nuevos productos.

5.4.4.1 Instalación

Dirección de descarga del paquete de instalación de la versión de Windows: https://activemq.apache.org/components/classic/download /

​ El paquete de instalación descargado es un archivo zip que se puede utilizar después de la descompresión.Después de la descompresión, se obtendrán los siguientes archivos

inserte la descripción de la imagen aquí

servidor de inicio

activemq.bat

Simplemente ejecute el comando activemq.bat en el directorio win32 o win64 en el directorio bin. Puede elegir según su propio sistema operativo. El puerto de servicio externo predeterminado es 61616.

acceder al servicio de gestión web

Después de iniciar ActiveMQ, se iniciará un servicio de consola web a través del cual se puede administrar ActiveMQ.

http://127.0.0.1:8161/

El puerto predeterminado del servicio de administración web es 8161. Después de acceder, puede abrir la interfaz de administración de ActiveMQ, de la siguiente manera:

inserte la descripción de la imagen aquí

Primero ingrese el nombre de usuario y la contraseña de acceso, el nombre de usuario y la contraseña de inicialización son los mismos, ambos son: administrador, después de iniciar sesión correctamente, ingrese a la interfaz de fondo de administración, de la siguiente manera:

inserte la descripción de la imagen aquí

Se considera que ver la interfaz anterior inicia correctamente el servicio ActiveMQ.

Error al iniciar

Varios puertos están ocupados cuando se inicia ActiveMQ. La siguiente es la información de inicio normal:

wrapper  | --> Wrapper Started as Console
wrapper  | Launching a JVM...
jvm 1    | Wrapper (Version 3.2.3) http://wrapper.tanukisoftware.org
jvm 1    |   Copyright 1999-2006 Tanuki Software, Inc.  All Rights Reserved.
jvm 1    |
jvm 1    | Java Runtime: Oracle Corporation 1.8.0_172 D:\soft\jdk1.8.0_172\jre
jvm 1    |   Heap sizes: current=249344k  free=235037k  max=932352k
jvm 1    |     JVM args: -Dactivemq.home=../.. -Dactivemq.base=../.. -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStorePassword=password -Djavax.net.ssl.keyStore=../../conf/broker.ks -Djavax.net.ssl.trustStore=../../conf/broker.ts -Dcom.sun.management.jmxremote -Dorg.apache.activemq.UseDedicatedTaskRunner=true -Djava.util.logging.config.file=logging.properties -Dactivemq.conf=../../conf -Dactivemq.data=../../data -Djava.security.auth.login.config=../../conf/login.config -Xmx1024m -Djava.library.path=../../bin/win64 -Dwrapper.key=7ySrCD75XhLCpLjd -Dwrapper.port=32000 -Dwrapper.jvm.port.min=31000 -Dwrapper.jvm.port.max=31999 -Dwrapper.pid=9364 -Dwrapper.version=3.2.3 -Dwrapper.native_library=wrapper -Dwrapper.cpu.timeout=10 -Dwrapper.jvmid=1
jvm 1    | Extensions classpath:
jvm 1    |   [..\..\lib,..\..\lib\camel,..\..\lib\optional,..\..\lib\web,..\..\lib\extra]
jvm 1    | ACTIVEMQ_HOME: ..\..
jvm 1    | ACTIVEMQ_BASE: ..\..
jvm 1    | ACTIVEMQ_CONF: ..\..\conf
jvm 1    | ACTIVEMQ_DATA: ..\..\data
jvm 1    | Loading message broker from: xbean:activemq.xml
jvm 1    |  INFO | Refreshing org.apache.activemq.xbean.XBeanBrokerFactory$1@5f3ebfe0: startup date [Mon Feb 28 16:07:48 CST 2022]; root of context hierarchy
jvm 1    |  INFO | Using Persistence Adapter: KahaDBPersistenceAdapter[D:\soft\activemq\bin\win64\..\..\data\kahadb]
jvm 1    |  INFO | KahaDB is version 7
jvm 1    |  INFO | PListStore:[D:\soft\activemq\bin\win64\..\..\data\localhost\tmp_storage] started
jvm 1    |  INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10434-1646035669595-0:1) is starting
jvm 1    |  INFO | Listening for connections at: tcp://CZBK-20210302VL:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600
jvm 1    |  INFO | Connector openwire started
jvm 1    |  INFO | Listening for connections at: amqp://CZBK-20210302VL:5672?maximumConnections=1000&wireFormat.maxFrameSize=104857600
jvm 1    |  INFO | Connector amqp started
jvm 1    |  INFO | Listening for connections at: stomp://CZBK-20210302VL:61613?maximumConnections=1000&wireFormat.maxFrameSize=104857600
jvm 1    |  INFO | Connector stomp started
jvm 1    |  INFO | Listening for connections at: mqtt://CZBK-20210302VL:1883?maximumConnections=1000&wireFormat.maxFrameSize=104857600
jvm 1    |  INFO | Connector mqtt started
jvm 1    |  INFO | Starting Jetty server
jvm 1    |  INFO | Creating Jetty connector
jvm 1    |  WARN | ServletContext@o.e.j.s.ServletContextHandler@7350746f{
    
    /,null,STARTING} has uncovered http methods for path: /
jvm 1    |  INFO | Listening for connections at ws://CZBK-20210302VL:61614?maximumConnections=1000&wireFormat.maxFrameSize=104857600
jvm 1    |  INFO | Connector ws started
jvm 1    |  INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10434-1646035669595-0:1) started
jvm 1    |  INFO | For help or more information please see: http://activemq.apache.org
jvm 1    |  WARN | Store limit is 102400 mb (current store usage is 0 mb). The data directory: D:\soft\activemq\bin\win64\..\..\data\kahadb only has 68936 mb of usable space. - resetting to maximum available disk space: 68936 mb
jvm 1    |  INFO | ActiveMQ WebConsole available at http://127.0.0.1:8161/
jvm 1    |  INFO | ActiveMQ Jolokia REST API available at http://127.0.0.1:8161/api/jolokia/

​ Los puertos ocupados son: 61616, 5672, 61613, 1883, 61614. Si falla el inicio, administre primero los puertos correspondientes. El siguiente es el mensaje de error ocupado por un determinado puerto.Se puede ver desde la posición donde se lanza la excepción que el puerto está ocupado cuando se inicia el puerto 5672, y se muestra java.net.BindException: Dirección ya en uso: JVM_Bind .

wrapper  | --> Wrapper Started as Console
wrapper  | Launching a JVM...
jvm 1    | Wrapper (Version 3.2.3) http://wrapper.tanukisoftware.org
jvm 1    |   Copyright 1999-2006 Tanuki Software, Inc.  All Rights Reserved.
jvm 1    |
jvm 1    | Java Runtime: Oracle Corporation 1.8.0_172 D:\soft\jdk1.8.0_172\jre
jvm 1    |   Heap sizes: current=249344k  free=235038k  max=932352k
jvm 1    |     JVM args: -Dactivemq.home=../.. -Dactivemq.base=../.. -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStorePassword=password -Djavax.net.ssl.keyStore=../../conf/broker.ks -Djavax.net.ssl.trustStore=../../conf/broker.ts -Dcom.sun.management.jmxremote -Dorg.apache.activemq.UseDedicatedTaskRunner=true -Djava.util.logging.config.file=logging.properties -Dactivemq.conf=../../conf -Dactivemq.data=../../data -Djava.security.auth.login.config=../../conf/login.config -Xmx1024m -Djava.library.path=../../bin/win64 -Dwrapper.key=QPJoy9ZoXeWmmwTS -Dwrapper.port=32000 -Dwrapper.jvm.port.min=31000 -Dwrapper.jvm.port.max=31999 -Dwrapper.pid=14836 -Dwrapper.version=3.2.3 -Dwrapper.native_library=wrapper -Dwrapper.cpu.timeout=10 -Dwrapper.jvmid=1
jvm 1    | Extensions classpath:
jvm 1    |   [..\..\lib,..\..\lib\camel,..\..\lib\optional,..\..\lib\web,..\..\lib\extra]
jvm 1    | ACTIVEMQ_HOME: ..\..
jvm 1    | ACTIVEMQ_BASE: ..\..
jvm 1    | ACTIVEMQ_CONF: ..\..\conf
jvm 1    | ACTIVEMQ_DATA: ..\..\data
jvm 1    | Loading message broker from: xbean:activemq.xml
jvm 1    |  INFO | Refreshing org.apache.activemq.xbean.XBeanBrokerFactory$1@2c9392f5: startup date [Mon Feb 28 16:06:16 CST 2022]; root of context hierarchy
jvm 1    |  INFO | Using Persistence Adapter: KahaDBPersistenceAdapter[D:\soft\activemq\bin\win64\..\..\data\kahadb]
jvm 1    |  INFO | KahaDB is version 7
jvm 1    |  INFO | PListStore:[D:\soft\activemq\bin\win64\..\..\data\localhost\tmp_storage] started
jvm 1    |  INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10257-1646035577620-0:1) is starting
jvm 1    |  INFO | Listening for connections at: tcp://CZBK-20210302VL:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600
jvm 1    |  INFO | Connector openwire started
jvm 1    | ERROR | Failed to start Apache ActiveMQ (localhost, ID:CZBK-20210302VL-10257-1646035577620-0:1)
jvm 1    | java.io.IOException: Transport Connector could not be registered in JMX: java.io.IOException: Failed to bind to server socket: amqp://0.0.0.0:5672?maximumConnections=1000&wireFormat.maxFrameSize=104857600 due to: java.net.BindException: Address already in use: JVM_Bind
jvm 1    |      at org.apache.activemq.util.IOExceptionSupport.create(IOExceptionSupport.java:28)
jvm 1    |      at org.apache.activemq.broker.BrokerService.registerConnectorMBean(BrokerService.java:2288)
jvm 1    |      at org.apache.activemq.broker.BrokerService.startTransportConnector(BrokerService.java:2769)
jvm 1    |      at org.apache.activemq.broker.BrokerService.startAllConnectors(BrokerService.java:2665)
jvm 1    |      at org.apache.activemq.broker.BrokerService.doStartBroker(BrokerService.java:780)
jvm 1    |      at org.apache.activemq.broker.BrokerService.startBroker(BrokerService.java:742)
jvm 1    |      at org.apache.activemq.broker.BrokerService.start(BrokerService.java:645)
jvm 1    |      at org.apache.activemq.xbean.XBeanBrokerService.afterPropertiesSet(XBeanBrokerService.java:73)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
jvm 1    |      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
jvm 1    |      at java.lang.reflect.Method.invoke(Method.java:498)
jvm 1    |      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(AbstractAutowireCapableBeanFactory.java:1748)
jvm 1    |      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1685)
jvm 1    |      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1615)
jvm 1    |      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
jvm 1    |      at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:481)
jvm 1    |      at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312)
jvm 1    |      at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
jvm 1    |      at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308)
jvm 1    |      at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
jvm 1    |      at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:756)
jvm 1    |      at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
jvm 1    |      at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
jvm 1    |      at org.apache.xbean.spring.context.ResourceXmlApplicationContext.<init>(ResourceXmlApplicationContext.java:64)
jvm 1    |      at org.apache.xbean.spring.context.ResourceXmlApplicationContext.<init>(ResourceXmlApplicationContext.java:52)
jvm 1    |      at org.apache.activemq.xbean.XBeanBrokerFactory$1.<init>(XBeanBrokerFactory.java:104)
jvm 1    |      at org.apache.activemq.xbean.XBeanBrokerFactory.createApplicationContext(XBeanBrokerFactory.java:104)
jvm 1    |      at org.apache.activemq.xbean.XBeanBrokerFactory.createBroker(XBeanBrokerFactory.java:67)
jvm 1    |      at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:71)
jvm 1    |      at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:54)
jvm 1    |      at org.apache.activemq.console.command.StartCommand.runTask(StartCommand.java:87)
jvm 1    |      at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63)
jvm 1    |      at org.apache.activemq.console.command.ShellCommand.runTask(ShellCommand.java:154)
jvm 1    |      at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63)
jvm 1    |      at org.apache.activemq.console.command.ShellCommand.main(ShellCommand.java:104)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
jvm 1    |      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
jvm 1    |      at java.lang.reflect.Method.invoke(Method.java:498)
jvm 1    |      at org.apache.activemq.console.Main.runTaskClass(Main.java:262)
jvm 1    |      at org.apache.activemq.console.Main.main(Main.java:115)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
jvm 1    |      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
jvm 1    |      at java.lang.reflect.Method.invoke(Method.java:498)
jvm 1    |      at org.tanukisoftware.wrapper.WrapperSimpleApp.run(WrapperSimpleApp.java:240)
jvm 1    |      at java.lang.Thread.run(Thread.java:748)
jvm 1    | Caused by: java.io.IOException: Failed to bind to server socket: amqp://0.0.0.0:5672?maximumConnections=1000&wireFormat.maxFrameSize=104857600 due to: java.net.BindException: Address already in use: JVM_Bind
jvm 1    |      at org.apache.activemq.util.IOExceptionSupport.create(IOExceptionSupport.java:34)
jvm 1    |      at org.apache.activemq.transport.tcp.TcpTransportServer.bind(TcpTransportServer.java:146)
jvm 1    |      at org.apache.activemq.transport.tcp.TcpTransportFactory.doBind(TcpTransportFactory.java:62)
jvm 1    |      at org.apache.activemq.transport.TransportFactorySupport.bind(TransportFactorySupport.java:40)
jvm 1    |      at org.apache.activemq.broker.TransportConnector.createTransportServer(TransportConnector.java:335)
jvm 1    |      at org.apache.activemq.broker.TransportConnector.getServer(TransportConnector.java:145)
jvm 1    |      at org.apache.activemq.broker.TransportConnector.asManagedConnector(TransportConnector.java:110)
jvm 1    |      at org.apache.activemq.broker.BrokerService.registerConnectorMBean(BrokerService.java:2283)
jvm 1    |      ... 46 more
jvm 1    | Caused by: java.net.BindException: Address already in use: JVM_Bind
jvm 1    |      at java.net.DualStackPlainSocketImpl.bind0(Native Method)
jvm 1    |      at java.net.DualStackPlainSocketImpl.socketBind(DualStackPlainSocketImpl.java:106)
jvm 1    |      at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:387)
jvm 1    |      at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:190)
jvm 1    |      at java.net.ServerSocket.bind(ServerSocket.java:375)
jvm 1    |      at java.net.ServerSocket.<init>(ServerSocket.java:237)
jvm 1    |      at javax.net.DefaultServerSocketFactory.createServerSocket(ServerSocketFactory.java:231)
jvm 1    |      at org.apache.activemq.transport.tcp.TcpTransportServer.bind(TcpTransportServer.java:143)
jvm 1    |      ... 52 more
jvm 1    |  INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10257-1646035577620-0:1) is shutting down
jvm 1    |  INFO | socketQueue interrupted - stopping
jvm 1    |  INFO | Connector openwire stopped
jvm 1    |  INFO | Could not accept connection during shutdown  : null (null)
jvm 1    |  INFO | Connector amqp stopped
jvm 1    |  INFO | Connector stomp stopped
jvm 1    |  INFO | Connector mqtt stopped
jvm 1    |  INFO | Connector ws stopped
jvm 1    |  INFO | PListStore:[D:\soft\activemq\bin\win64\..\..\data\localhost\tmp_storage] stopped
jvm 1    |  INFO | Stopping async queue tasks
jvm 1    |  INFO | Stopping async topic tasks
jvm 1    |  INFO | Stopped KahaDB
jvm 1    |  INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10257-1646035577620-0:1) uptime 0.426 seconds
jvm 1    |  INFO | Apache ActiveMQ 5.16.3 (localhost, ID:CZBK-20210302VL-10257-1646035577620-0:1) is shutdown
jvm 1    |  INFO | Closing org.apache.activemq.xbean.XBeanBrokerFactory$1@2c9392f5: startup date [Mon Feb 28 16:06:16 CST 2022]; root of context hierarchy
jvm 1    |  WARN | Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.apache.activemq.xbean.XBeanBrokerService#0' defined in class path resource [activemq.xml]: Invocation of init method failed; nested exception is java.io.IOException: Transport Connector could not be registered in JMX: java.io.IOException: Failed to bind to server socket: amqp://0.0.0.0:5672?maximumConnections=1000&wireFormat.maxFrameSize=104857600 due to: java.net.BindException: Address already in use: JVM_Bind
jvm 1    | ERROR: java.lang.RuntimeException: Failed to execute start task. Reason: java.lang.IllegalStateException: BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext
jvm 1    | java.lang.RuntimeException: Failed to execute start task. Reason: java.lang.IllegalStateException: BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext
jvm 1    |      at org.apache.activemq.console.command.StartCommand.runTask(StartCommand.java:91)
jvm 1    |      at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63)
jvm 1    |      at org.apache.activemq.console.command.ShellCommand.runTask(ShellCommand.java:154)
jvm 1    |      at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63)
jvm 1    |      at org.apache.activemq.console.command.ShellCommand.main(ShellCommand.java:104)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
jvm 1    |      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
jvm 1    |      at java.lang.reflect.Method.invoke(Method.java:498)
jvm 1    |      at org.apache.activemq.console.Main.runTaskClass(Main.java:262)
jvm 1    |      at org.apache.activemq.console.Main.main(Main.java:115)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
jvm 1    |      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
jvm 1    |      at java.lang.reflect.Method.invoke(Method.java:498)
jvm 1    |      at org.tanukisoftware.wrapper.WrapperSimpleApp.run(WrapperSimpleApp.java:240)
jvm 1    |      at java.lang.Thread.run(Thread.java:748)
jvm 1    | Caused by: java.lang.IllegalStateException: BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext
jvm 1    |      at org.springframework.context.support.AbstractRefreshableApplicationContext.getBeanFactory(AbstractRefreshableApplicationContext.java:164)
jvm 1    |      at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1034)
jvm 1    |      at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:555)
jvm 1    |      at org.apache.xbean.spring.context.ResourceXmlApplicationContext.<init>(ResourceXmlApplicationContext.java:64)
jvm 1    |      at org.apache.xbean.spring.context.ResourceXmlApplicationContext.<init>(ResourceXmlApplicationContext.java:52)
jvm 1    |      at org.apache.activemq.xbean.XBeanBrokerFactory$1.<init>(XBeanBrokerFactory.java:104)
jvm 1    |      at org.apache.activemq.xbean.XBeanBrokerFactory.createApplicationContext(XBeanBrokerFactory.java:104)
jvm 1    |      at org.apache.activemq.xbean.XBeanBrokerFactory.createBroker(XBeanBrokerFactory.java:67)
jvm 1    |      at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:71)
jvm 1    |      at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:54)
jvm 1    |      at org.apache.activemq.console.command.StartCommand.runTask(StartCommand.java:87)
jvm 1    |      ... 16 more
jvm 1    | ERROR: java.lang.IllegalStateException: BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext
jvm 1    | java.lang.IllegalStateException: BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext
jvm 1    |      at org.springframework.context.support.AbstractRefreshableApplicationContext.getBeanFactory(AbstractRefreshableApplicationContext.java:164)
jvm 1    |      at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1034)
jvm 1    |      at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:555)
jvm 1    |      at org.apache.xbean.spring.context.ResourceXmlApplicationContext.<init>(ResourceXmlApplicationContext.java:64)
jvm 1    |      at org.apache.xbean.spring.context.ResourceXmlApplicationContext.<init>(ResourceXmlApplicationContext.java:52)
jvm 1    |      at org.apache.activemq.xbean.XBeanBrokerFactory$1.<init>(XBeanBrokerFactory.java:104)
jvm 1    |      at org.apache.activemq.xbean.XBeanBrokerFactory.createApplicationContext(XBeanBrokerFactory.java:104)
jvm 1    |      at org.apache.activemq.xbean.XBeanBrokerFactory.createBroker(XBeanBrokerFactory.java:67)
jvm 1    |      at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:71)
jvm 1    |      at org.apache.activemq.broker.BrokerFactory.createBroker(BrokerFactory.java:54)
jvm 1    |      at org.apache.activemq.console.command.StartCommand.runTask(StartCommand.java:87)
jvm 1    |      at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63)
jvm 1    |      at org.apache.activemq.console.command.ShellCommand.runTask(ShellCommand.java:154)
jvm 1    |      at org.apache.activemq.console.command.AbstractCommand.execute(AbstractCommand.java:63)
jvm 1    |      at org.apache.activemq.console.command.ShellCommand.main(ShellCommand.java:104)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
jvm 1    |      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
jvm 1    |      at java.lang.reflect.Method.invoke(Method.java:498)
jvm 1    |      at org.apache.activemq.console.Main.runTaskClass(Main.java:262)
jvm 1    |      at org.apache.activemq.console.Main.main(Main.java:115)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
jvm 1    |      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
jvm 1    |      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
jvm 1    |      at java.lang.reflect.Method.invoke(Method.java:498)
jvm 1    |      at org.tanukisoftware.wrapper.WrapperSimpleApp.run(WrapperSimpleApp.java:240)
jvm 1    |      at java.lang.Thread.run(Thread.java:748)
wrapper  | <-- Wrapper Stopped
请按任意键继续. . .

5.4.4.2 Integración

Paso ① : importe springboot para integrar el iniciador ActiveMQ

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

Paso ② : Configurar la dirección del servidor ActiveMQ

spring:
  activemq:
    broker-url: tcp://localhost:61616

Paso ③ : use JmsMessagingTemplate para operar ActiveMQ

@Service
public class MessageServiceActivemqImpl implements MessageService {
    
    
    @Autowired
    private JmsMessagingTemplate messagingTemplate;

    @Override
    public void sendMessage(String id) {
    
    
        System.out.println("待发送短信的订单已纳入处理队列,id:"+id);
        messagingTemplate.convertAndSend("order.queue.id",id);
    }

    @Override
    public String doMessage() {
    
    
        String id = messagingTemplate.receiveAndConvert("order.queue.id",String.class);
        System.out.println("已完成短信发送业务,id:"+id);
        return id;
    }
}

Para enviar un mensaje, debe convertir el tipo de mensaje en una cadena antes de enviarlo, por lo que es convertAndSend, que define la ubicación donde se envía el mensaje y el contenido específico del mensaje. Aquí, la identificación se usa como el contenido del mensaje.

Para recibir un mensaje, primero debe recibir el mensaje y luego convertirlo en un tipo de datos específico, por lo que es receiveAndConvert Además de proporcionar la ubicación para leer el mensaje recibido, también debe proporcionar el tipo específico de los datos convertidos.

Paso ④ : use el detector de mensajes para monitorear la ubicación especificada después de que se inicie el servidor y consuma el mensaje inmediatamente después de que aparezca el mensaje

@Component
public class MessageListener {
    
    
    @JmsListener(destination = "order.queue.id")
    @SendTo("order.other.queue.id")
    public String receive(String id){
    
    
        System.out.println("已完成短信发送业务,id:"+id);
        return "new:"+id;
    }
}

Use la anotación @JmsListener para definir el método actual para escuchar la cola de mensajes con el nombre especificado en ActiveMQ.

Si la cola de mensajes actual se procesa y necesita continuar pasando el mensaje actual a otra cola, simplemente use la anotación @SendTo, de modo que se pueda construir una cola de mensajes secuencial para ejecución continua.

Paso ⑤ : cambie el modelo de mensaje del modelo de punto a punto al modelo de publicación-suscripción, simplemente modifique la configuración de jms

spring:
  activemq:
    broker-url: tcp://localhost:61616
  jms:
    pub-sub-domain: true

​ El valor predeterminado de pub-sub-domain es false, es decir, el modelo punto a punto, después de cambiar a verdadero, es el modelo de publicación-suscripción.

Resumir

  1. springboot integra ActiveMQ y proporciona el objeto JmsMessagingTemplate como una cola de mensajes de operación del cliente
  2. Para operar ActiveMQ, debe configurar la dirección del servidor ActiveMQ, el puerto predeterminado es 61616
  3. Durante el desarrollo empresarial, los oyentes generalmente se usan para procesar mensajes en la cola de mensajes, y la configuración del oyente usa la anotación @JmsListener
  4. Configure el atributo pub-sub-domain de jms para cambiar el modelo de mensaje entre el modelo de punto a punto y el modelo de publicación-suscripción

5.4.5 SpringBoot integra RabbitMQ

RabbitMQ es uno de los productos más populares entre los productos MQ y cumple con el protocolo AMQP. El lenguaje de implementación subyacente de RabbitMQ es Erlang, por lo que primero debe instalar Erlang para instalar RabbitMQ.

Instalación de Erlang

Dirección de descarga del paquete de instalación de la versión de Windows: https://www.erlang.org/downloads

Una vez completada la descarga, obtendrá el archivo de instalación exe, instalación de estilo tonto con un solo clic, debe reiniciar después de la instalación, debe reiniciar, debe reiniciar.

​ Durante el proceso de instalación, puede haber indicaciones que dependen de los componentes de Windows, simplemente descargue e instale de acuerdo con las indicaciones, y todas se ejecutarán automáticamente, de la siguiente manera:

inserte la descripción de la imagen aquí

Las variables de entorno deben configurarse después de instalar Erlang; de lo contrario, RabbitMQ no podrá encontrar el Erlang instalado. Los elementos de configuración necesarios son los siguientes, que son equivalentes a las funciones de las variables de entorno de configuración de JDK.

  • ERLANG_HOME
  • CAMINO

5.4.5.1 Instalación

Dirección de descarga del paquete de instalación de la versión de Windows : https://rabbitmq.com/install-windows.html

Una vez que se complete la descarga, obtendrá el archivo de instalación exe, la instalación de estilo tonto con un solo clic, y obtendrá los siguientes archivos una vez que se complete la instalación

inserte la descripción de la imagen aquí

servidor de inicio

rabbitmq-service.bat start		# 启动服务
rabbitmq-service.bat stop		# 停止服务
rabbitmqctl status				# 查看服务状态

Simplemente ejecute el comando rabbitmq-service.bat en el directorio sbin. El parámetro de inicio indica el inicio y el parámetro de parada indica la salida. El puerto de servicio externo predeterminado es 5672.

​ Nota: El proceso de iniciar rabbitmq es en realidad iniciar el servicio del sistema correspondiente a rabbitmq, que requiere privilegios de administrador para ejecutarse.

acceder al servicio de gestión web

RabbitMQ también proporciona un servicio de consola web, pero esta función es un complemento que debe habilitarse antes de poder usarse.

rabbitmq-plugins.bat list							# 查看当前所有插件的运行状态
rabbitmq-plugins.bat enable rabbitmq_management		# 启动rabbitmq_management插件

Después de iniciar el complemento, puede verificar si se está ejecutando en el estado de ejecución del complemento. Después de ejecutarlo, puede abrir la interfaz de administración de fondo del servicio a través del navegador.

http://localhost:15672

El puerto predeterminado del servicio de administración web es 15672. Después de acceder, puede abrir la interfaz de administración de RabbitMQ, de la siguiente manera:

inserte la descripción de la imagen aquí

Primero ingrese el nombre de usuario y la contraseña de acceso, el nombre de usuario y la contraseña de inicialización son los mismos, ambos son: invitado, después de iniciar sesión correctamente, ingrese a la interfaz de fondo de administración, de la siguiente manera:

inserte la descripción de la imagen aquí

5.4.5.2 Integración (modelo directo)

​ RabbitMQ cumple con el protocolo AMQP, por lo que diferentes modelos de mensajes corresponden a diferentes producciones. Primero use el modelo directo más simple para el desarrollo.

Paso ① : importe springboot para integrar el iniciador amqp, el protocolo amqp se implementa como el esquema rabbitmq de forma predeterminada

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

Paso ② : Configure la dirección del servidor de RabbitMQ

spring:
  rabbitmq:
    host: localhost
    port: 5672

Paso ③ : Inicialice la configuración del sistema del modo de conexión directa

Dado que los diferentes modelos de RabbitMQ usan diferentes conmutadores, es necesario inicializar los objetos relacionados con RabbitMQ, como colas, conmutadores, etc.

@Configuration
public class RabbitConfigDirect {
    
    
    @Bean
    public Queue directQueue(){
    
    
        return new Queue("direct_queue");
    }
    @Bean
    public Queue directQueue2(){
    
    
        return new Queue("direct_queue2");
    }
    @Bean
    public DirectExchange directExchange(){
    
    
        return new DirectExchange("directExchange");
    }
    @Bean
    public Binding bindingDirect(){
    
    
        return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct");
    }
    @Bean
    public Binding bindingDirect2(){
    
    
        return BindingBuilder.bind(directQueue2()).to(directExchange()).with("direct2");
    }
}

Después de que se crean la cola Cola y el conmutador DirectExchange conectado directamente, la relación Vinculación entre ellos debe vincularse, de modo que la cola correspondiente se pueda operar a través del conmutador.

Paso ④ : use AmqpTemplate para operar RabbitMQ

@Service
public class MessageServiceRabbitmqDirectImpl implements MessageService {
    
    
    @Autowired
    private AmqpTemplate amqpTemplate;

    @Override
    public void sendMessage(String id) {
    
    
        System.out.println("待发送短信的订单已纳入处理队列(rabbitmq direct),id:"+id);
        amqpTemplate.convertAndSend("directExchange","direct",id);
    }
}

​ El nombre de la interfaz API de operación en el protocolo amqp se parece mucho a la interfaz API de operación de la especificación jms, pero los parámetros que se pasan son muy diferentes.

Paso ⑤ : use el detector de mensajes para monitorear la ubicación especificada después de que se inicie el servidor y consuma el mensaje inmediatamente después de que aparezca el mensaje

@Component
public class MessageListener {
    
    
    @RabbitListener(queues = "direct_queue")
    public void receive(String id){
    
    
        System.out.println("已完成短信发送业务(rabbitmq direct),id:"+id);
    }
}

Use la anotación @RabbitListener para definir el método actual para escuchar la cola de mensajes con el nombre especificado en RabbitMQ.

5.4.5.3 Integración (modelo de tema)

Paso ① : Igual que el anterior

Paso ② : Igual que el anterior

Paso ③ : inicialice la configuración del sistema del modo de tema

@Configuration
public class RabbitConfigTopic {
    
    
    @Bean
    public Queue topicQueue(){
    
    
        return new Queue("topic_queue");
    }
    @Bean
    public Queue topicQueue2(){
    
    
        return new Queue("topic_queue2");
    }
    @Bean
    public TopicExchange topicExchange(){
    
    
        return new TopicExchange("topicExchange");
    }
    @Bean
    public Binding bindingTopic(){
    
    
        return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.*.id");
    }
    @Bean
    public Binding bindingTopic2(){
    
    
        return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("topic.orders.*");
    }
}

El modo de tema admite el modo de coincidencia de clave de enrutamiento, * significa hacer coincidir una palabra, # significa hacer coincidir cualquier contenido, de modo que los mensajes se puedan distribuir a diferentes colas a través del cambio de tema. Para obtener más información, consulte los cursos de la serie RabbitMQ.

clave de coincidencia tema.*.* tema.#
tema.pedido.id verdadero verdadero
pedido.tema.id FALSO FALSO
topic.sm.order.id FALSO verdadero
tema.sm.id FALSO verdadero
tema.id.pedido verdadero verdadero
tema.id FALSO verdadero
tema.orden FALSO verdadero

Paso ④ : use AmqpTemplate para operar RabbitMQ

@Service
public class MessageServiceRabbitmqTopicImpl implements MessageService {
    
    
    @Autowired
    private AmqpTemplate amqpTemplate;

    @Override
    public void sendMessage(String id) {
    
    
        System.out.println("待发送短信的订单已纳入处理队列(rabbitmq topic),id:"+id);
        amqpTemplate.convertAndSend("topicExchange","topic.orders.id",id);
    }
}

Después de enviar el mensaje, haga coincidir la clave de enrutamiento provista actualmente con la clave de enrutamiento establecida al vincular el conmutador, y el mensaje ingresará a la cola correspondiente si la coincidencia de la regla es exitosa.

Paso ⑤ : use el detector de mensajes para escuchar la cola especificada después de que se inicie el servidor

@Component
public class MessageListener {
    
    
    @RabbitListener(queues = "topic_queue")
    public void receive(String id){
    
    
        System.out.println("已完成短信发送业务(rabbitmq topic 1),id:"+id);
    }
    @RabbitListener(queues = "topic_queue2")
    public void receive2(String id){
    
    
        System.out.println("已完成短信发送业务(rabbitmq topic 22222222),id:"+id);
    }
}

Use la anotación @RabbitListener para definir el método actual para escuchar la cola de mensajes con el nombre especificado en RabbitMQ.

Resumir

  1. Springboot integra RabbitMQ y proporciona el objeto AmqpTemplate como una cola de mensajes de operación del cliente
  2. Para operar ActiveMQ, debe configurar la dirección del servidor ActiveMQ, el puerto predeterminado es 5672
  3. Durante el desarrollo empresarial, los oyentes generalmente se usan para procesar mensajes en la cola de mensajes, y la configuración del oyente usa la anotación @RabbitListener
  4. RabbitMQ tiene 5 modelos de mensajes, las colas utilizadas son las mismas, pero los conmutadores son diferentes. Los interruptores son diferentes y las estrategias de entrada de mensajes correspondientes también son diferentes.

5.4.6, SpringBoot integra RocketMQ

RocketMQ fue desarrollado por Ali y luego donado a la Fundación Apache. Actualmente es uno de los principales proyectos de la Fundación Apache y uno de los productos MQ más populares actualmente en el mercado. Cumple con el protocolo AMQP.

5.4.6.1 Instalación

Dirección de descarga del paquete de instalación de la versión de Windows: https://rocketmq.apache.org /

Una vez completada la descarga, obtendrá un archivo comprimido zip, descomprímalo y utilícelo

Después de instalar RocketMQ, debe configurar las variables de entorno de la siguiente manera:

  • ROCKETMQ_HOME
  • CAMINO
  • NOMBRESRV_ADDR (recomendado): 127.0.0.1:9876

​ Con respecto a NAMESRV_ADDR, se recomienda configurar este ítem para principiantes, también se puede establecer el valor correspondiente a través de comandos, la operación es un poco engorrosa, por lo que se recomienda configurarlo. Este elemento se puede controlar de manera flexible después de que el sistema aprenda el conocimiento de RocketMQ.

Modo de trabajo RocketMQ

​ En RocketMQ, el servidor que maneja el negocio se llama intermediario, y los productores y consumidores no se comunican directamente con el intermediario, sino a través del servidor designado. Una vez que se inicia el intermediario, notificará al servidor de nombres que está en línea, de modo que toda la información del intermediario se guarde en el servidor de nombres. Cuando los productores y los consumidores necesitan conectarse con los intermediarios, encuentran los intermediarios correspondientes que manejan el negocio a través del servidor de nombres, por lo que el servidor de nombres desempeña el papel de un centro de información en toda la estructura. Y el servidor de nombres debe iniciarse primero antes de que se inicie el intermediario.

inserte la descripción de la imagen aquí

servidor de inicio

mqnamesrv		# 启动命名服务器
mqbroker		# 启动broker

Ejecute el comando mqnamesrv en el directorio bin para iniciar el servidor de nombres. El puerto de servicio externo predeterminado es 9876.

Ejecute el comando mqbroker en el directorio bin para iniciar el servidor del intermediario. Si NAMESRV_ADDR no está configurado en la variable de entorno, debe configurar el valor de NAMESRV_ADDR a través del comando set antes de ejecutar el comando mqbroker, y debe configurar este elemento cada vez que lo inicias.

Probar el estado de inicio del servidor

RocketMQ proporciona un conjunto de programas de prueba para probar las funciones del servidor, que se pueden usar ejecutando el comando de herramientas en el directorio bin.

tools org.apache.rocketmq.example.quickstart.Producer		# 生产消息
tools org.apache.rocketmq.example.quickstart.Consumer		# 消费消息

5.4.6.2, integración (mensaje asíncrono)

Paso ① : importe springboot para integrar el iniciador RocketMQ, springboot no mantiene esta coordenada

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.1</version>
</dependency>

Paso ② : Configure la dirección del servidor de RocketMQ

rocketmq:
  name-server: localhost:9876
  producer:
    group: group_rocketmq

Establezca el grupo de grupo de consumidores de productores predeterminado.

Paso ③ : Use RocketMQTemplate para operar RocketMQ

@Service
public class MessageServiceRocketmqImpl implements MessageService {
    
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Override
    public void sendMessage(String id) {
    
    
        System.out.println("待发送短信的订单已纳入处理队列(rocketmq),id:"+id);
        SendCallback callback = new SendCallback() {
    
    
            @Override
            public void onSuccess(SendResult sendResult) {
    
    
                System.out.println("消息发送成功");
            }
            @Override
            public void onException(Throwable e) {
    
    
                System.out.println("消息发送失败!!!!!");
            }
        };
        rocketMQTemplate.asyncSend("order_id",id,callback);
    }
}

Utilice el método asyncSend para enviar un mensaje asíncrono.

Paso ④ : use el detector de mensajes para monitorear la ubicación especificada después de que se inicie el servidor y consuma el mensaje inmediatamente después de que aparezca el mensaje

@Component
@RocketMQMessageListener(topic = "order_id",consumerGroup = "group_rocketmq")
public class MessageListener implements RocketMQListener<String> {
    
    
    @Override
    public void onMessage(String id) {
    
    
        System.out.println("已完成短信发送业务(rocketmq),id:"+id);
    }
}

El oyente RocketMQ debe desarrollarse de acuerdo con el formato estándar, implementar la interfaz RocketMQListener y el tipo genérico es el tipo de mensaje.

Use la anotación @RocketMQMessageListener para definir que la clase actual escucha la cola de mensajes del grupo especificado y el nombre especificado en RabbitMQ.

Resumir

  1. Springboot integra RocketMQ y usa el objeto RocketMQTemplate como cliente para operar la cola de mensajes
  2. Para operar RocketMQ, debe configurar la dirección del servidor RocketMQ, el puerto predeterminado es 9876
  3. El desarrollo empresarial generalmente usa escuchas para procesar mensajes en la cola de mensajes, y configurar el escucha usa la anotación @RocketMQMessageListener

5.4.7 SpringBoot integra Kafka

5.4.7.1 Instalación

Dirección de descarga del paquete de instalación de la versión de Windows : https://kafka.apache.org/downloads

​ Una vez completada la descarga, obtendrá el archivo comprimido tgz. Use el software de descompresión para descomprimirlo y usarlo. Se recomienda usar la versión de Windows 2.8.1.

servidor de inicio

La función del servidor kafka es equivalente al intermediario en RocketMQ, y la operación de kafka también necesita un servicio similar al servidor de nombres. En el directorio de instalación de kafka, existe una herramienta similar al servidor de nombres llamada zookeeper, que funciona como un centro de registro, favor de aprender los conocimientos pertinentes en los cursos correspondientes.

zookeeper-server-start.bat ..\..\config\zookeeper.properties		# 启动zookeeper
kafka-server-start.bat ..\..\config\server.properties				# 启动kafka

Ejecute el comando zookeeper-server-start en el directorio de Windows en el directorio bin para iniciar el centro de registro. El puerto de servicio externo predeterminado es 2181.

Ejecute el comando kafka-server-start en el directorio de Windows en el directorio bin para iniciar el servidor kafka. El puerto de servicio externo predeterminado es 9092.

crear tema

De manera similar a la operación anterior de otros productos MQ, kakfa también se basa en la operación del tema, y ​​el tema debe inicializarse antes de la operación.

# 创建topic
kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic angyan
# 查询topic
kafka-topics.bat --zookeeper 127.0.0.1:2181 --list					
# 删除topic
kafka-topics.bat --delete --zookeeper localhost:2181 --topic angyan

Probar el estado de inicio del servidor

Kafka proporciona un conjunto de programas de prueba para probar las funciones del servidor, que se pueden usar ejecutando los comandos en el directorio de Windows en el directorio bin.

kafka-console-producer.bat --broker-list localhost:9092 --topic angyan							# 测试生产消息
kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic angyan --from-beginning	# 测试消息消费

5.4.7.2 Integración

Paso ① : importe springboot para integrar el iniciador de Kafka, esta coordenada la mantiene springboot

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>

Paso ② : Configure la dirección del servidor de Kafka

spring:
  kafka:
    bootstrap-servers: localhost:9092
    consumer:
      group-id: order

Establezca el ID de grupo de consumidores de productores predeterminado.

Paso ③ : use KafkaTemplate para operar Kafka

@Service
public class MessageServiceKafkaImpl implements MessageService {
    
    
    @Autowired
    private KafkaTemplate<String,String> kafkaTemplate;

    @Override
    public void sendMessage(String id) {
    
    
        System.out.println("待发送短信的订单已纳入处理队列(kafka),id:"+id);
        kafkaTemplate.send("angyan2022",id);
    }
}

Use el método de envío para enviar un mensaje, debe pasar el nombre del tema.

Paso ④ : use el detector de mensajes para monitorear la ubicación especificada después de que se inicie el servidor y consuma el mensaje inmediatamente después de que aparezca el mensaje

@Component
public class MessageListener {
    
    
    @KafkaListener(topics = "test")
    public void onMessage(ConsumerRecord<String,String> record){
    
    
        System.out.println("已完成短信发送业务(kafka),id:"+record.value());
    }
}

​ Use la anotación @KafkaListener para definir el método actual para monitorear el mensaje del tema especificado en Kafka. El mensaje recibido se encapsula en el objeto ConsumerRecord, y los datos se pueden obtener del objeto ConsumerRecord.

Resumir

  1. springboot integra Kafka y usa el objeto KafkaTemplate como cliente para operar la cola de mensajes

  2. Para operar Kafka, debe configurar la dirección del servidor Kafka, el puerto predeterminado es 9092

  3. Durante el desarrollo empresarial, los oyentes se usan generalmente para procesar mensajes en la cola de mensajes y la anotación @KafkaListener se usa para configurar el oyente. El mensaje recibido se almacena en el parámetro formal objeto ConsumerRecord

6. Monitoreo

6.1 Importancia del seguimiento

  • El estado del servicio de monitoreo está inactivo
  • Supervise los indicadores de ejecución del servicio (memoria, máquinas virtuales, subprocesos, solicitudes, etc.)
  • registro de monitoreo
  • Servicio de gestión (servicio fuera de línea)

Para los programas de Internet modernos, la escala es cada vez mayor, las funciones son cada vez más complejas y se debe buscar una mejor experiencia del cliente, por lo que la cantidad de información que se debe monitorear es relativamente grande. Dado que la mayoría de los programas de Internet actuales están basados ​​en microservicios, el funcionamiento de un programa requiere de varios servicios para garantizarse, por lo que el primer indicador a monitorear es si el servicio se está ejecutando normalmente, es decir, si el estado del servicio de monitoreo maneja el estado de tiempo de inactividad . .

Una vez que se descubre que un servicio está inactivo, se debe proporcionar una solución correspondiente de inmediato para evitar afectar la función general de la aplicación. En segundo lugar, debido a la gran cantidad de clientes atendidos por los programas de Internet, cuando las solicitudes de los clientes llegan al servidor en un corto período de tiempo, habrá fluctuaciones en varios indicadores de operación del programa. Por ejemplo, el uso de la memoria es grave y la solicitud no puede ser respondida y procesada de manera oportuna, este es el segundo indicador importante a monitorear, que es el indicador de operación del servicio de monitoreo .

Aunque el software proporciona los requisitos de acceso del usuario y completa las funciones correspondientes, ya sea que la operación en segundo plano sea estable o no, y si existen peligros funcionales ocultos que no afectan el uso del cliente, estos también deben monitorearse de cerca. , para monitorear el funcionamiento del sistema, el registro es un buen medio. Si encuentra la información de registro que preocupa a los desarrolladores o al personal de operación y mantenimiento entre los muchos registros, también es un problema que el sistema de monitoreo debe tener en cuenta para filtrar los registros y verlos de manera simple, rápida y efectiva. tercer indicador a monitorear, el registro de ejecución del programa de monitoreo .

Aunque esperamos que el programa funcione sin problemas todo el tiempo, debido a situaciones inesperadas, como ataques al servidor, desbordamiento de la memoria del servidor, etc., el servidor está inactivo. En este momento, el servicio actual no puede satisfacer las necesidades de uso y debe reiniciarse o incluso apagarse.Si Controlar rápidamente el inicio y la detención del servidor también es un problema inevitable en el proceso de operación del programa.Este es el cuarto elemento de monitoreo, el estado del servicio de administración .

Lo anterior es solo para pensar en el tema del monitoreo desde una perspectiva amplia. Todavía hay muchos detalles. Por ejemplo, se ha lanzado una nueva función y se recuerda a los usuarios que renueven sus tarifas regularmente. Esta función no se ejecuta inmediatamente después. se inicia, pero ¿la función actual es verdadera? Inicio, si la consulta rápida encuentra que esta función se ha activado, esto también es un problema a resolver en la supervisión, y así sucesivamente. Parece que el monitoreo es realmente un trabajo muy importante.

De la descripción anterior, se puede ver que el monitoreo es muy importante. ¿Cómo se debe realizar un seguimiento específico? También es necesario proceder desde la perspectiva de la operación real del programa. Por ejemplo, actualmente hay tres servicios que respaldan la operación de un programa y cada servicio tiene su propio estado operativo.

En este momento, la información monitoreada debe consultarse y mostrarse en tres programas diferentes, pero los tres servicios sirven para la operación de un programa. Si no se pueden combinar y mostrar en una plataforma, la carga de trabajo de monitoreo es enorme y la información La simetría es pobre y los datos deben verificarse continuamente en los tres terminales de monitoreo. ¿Qué pasa si el negocio se amplía a 30, 300, 3000? Parece que debe haber una plataforma separada que agregue la información del indicador de monitoreo correspondiente a múltiples servicios monitoreados, lo que es más propicio para el desarrollo del trabajo de monitoreo.

El nuevo programa se usa especialmente para monitorear, y surge un nuevo problema: ¿el programa monitoreado está reportando información activamente o el programa monitoreado está obteniendo información activamente? Si el programa de monitoreo no puede obtener información activamente, significa que el programa de monitoreo puede ver la información reportada por el programa de monitoreo hace mucho tiempo. En caso de que el programa de monitoreo esté inactivo, el programa de monitoreo no podrá distinguir si ha sido sin información durante mucho tiempo, todavía está fuera de línea. Por lo tanto, el programa de monitoreo debe tener la capacidad de iniciar activamente una solicitud para obtener información sobre el servicio monitoreado.

​ Si el programa de monitoreo quiere monitorear el servicio, obtenga activamente la información de la otra parte. ¿Cómo sabe el programa de monitoreo qué programas están siendo monitoreados por sí mismo? Es imposible configurar a quién monitoreo en el programa de monitoreo, por lo que todos los programas en Internet pueden monitorearse y la seguridad de la información no estará garantizada. Un enfoque razonable solo puede ser informar al programa de monitoreo cuando el programa monitoreado comience, diciéndole al programa de monitoreo que puede monitorearme. Parece que la operación de informe activo debe realizarse en el lado del programa monitoreado, lo que requiere que el programa de monitoreo correspondiente esté configurado en el programa monitoreado.

​ El programa monitoreado puede proporcionar una variedad de datos de indicadores al programa de monitoreo, pero cada indicador representa la información confidencial de la empresa, no todos los indicadores pueden ser vistos por cualquier persona, incluso el personal de operación y mantenimiento, por lo que si los indicadores monitoreados están abiertos a el sistema de seguimiento debe establecerse en detalle.

Todo el proceso descrito anteriormente es el proceso básico de un sistema de monitoreo.

Resumir

  1. La monitorización es un trabajo muy importante, y es el medio básico para asegurar el normal funcionamiento del programa.
  2. El proceso de monitoreo se lleva a cabo a través de un programa de monitoreo, que resume la información de todos los programas monitoreados para una visualización centralizada
  3. El programa monitoreado debe informar activamente que se monitorea y, al mismo tiempo, establecer qué indicadores se monitorean

6.2 Plataforma de monitoreo visual

Springboot extrae la mayoría de los indicadores comunes del sistema de monitoreo y presenta la idea general de monitoreo. Luego, algunos compañeros bien intencionados hicieron un sistema de monitoreo muy versátil basado en la idea general de monitorear. Debido a que se hizo en base a la idea central de monitoreo springboot, este programa se llamó Spring Boot Admin .

​ Spring Boot Admin, un proyecto comunitario de código abierto para administrar y monitorear aplicaciones SpringBoot. Este proyecto incluye dos partes, el cliente y el servidor, y la plataforma de monitoreo se refiere al servidor. Si es necesario monitorear el programa que creamos, conviértalo en un cliente y, después de configurar la dirección del servidor, el servidor puede obtener la información correspondiente del cliente a través de solicitudes HTTP y mostrar la información correspondiente a través de la interfaz de usuario.

Ahora desarrollemos este conjunto de programas de monitoreo. Primero, hagamos el servidor. De hecho, el servidor puede entenderse como un programa web, que muestra la información después de recibir alguna información.

desarrollo de servidores

Paso ① : Importe el iniciador correspondiente al administrador de springboot, la versión es consistente con la versión de springboot utilizada actualmente y configúrelo como un proyecto web

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>2.5.4</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Paso ② : agregue la anotación @EnableAdminServer a la clase de arranque, declarando que la aplicación actual se usará como un servidor SpringBootAdmin después del inicio

@SpringBootApplication
@EnableAdminServer
public class SpringbootAdminServerApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(SpringbootAdminServerApplication.class, args);
    }
}

desarrollo de clientes

El desarrollo del programa del cliente es básicamente similar a las ideas de desarrollo del lado del servidor, con algunas configuraciones más.

Paso ① : Importe el iniciador correspondiente al administrador de springboot, la versión es consistente con la versión de springboot utilizada actualmente y configúrelo como un proyecto web

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.5.4</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Paso ② : configure el servidor al que el cliente actual cargará la información y configúrelo a través del archivo yml

spring:
  boot:
    admin:
      client:
        url: http://localhost:8080

Como puede ver, actualmente se monitorea 1 programa, haga clic para ver información detallada.

inserte la descripción de la imagen aquí

Dado que actualmente no hay una configuración de qué información abrir en el servidor de monitoreo, actualmente no hay información efectiva disponible. Es necesario realizar los siguientes dos conjuntos de configuraciones para ver la información.

  1. Abrir información específica al servidor

  2. Permitir que el servidor obtenga la información correspondiente en forma de solicitud HTTP

    La configuración es la siguiente:

server:
  port: 80
spring:
  boot:
    admin:
      client:
        url: http://localhost:8080
management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: "*"

El cliente springbootadmin abre 13 conjuntos de información en el servidor de forma predeterminada, pero a excepción de una de estas informaciones, no se permite ver otra información a través de solicitudes HTTP. Entonces, la información que ve básicamente no tiene contenido, y solo puede ver un contenido, que es la información de salud a continuación.

inserte la descripción de la imagen aquí

​ Pero aún así, no vemos nada en la información de salud. La razón es que hay cierta información en la información de salud que describe qué tecnología se usa en su aplicación actual. A través de la configuración, todos los detalles de la información de salud se pueden abrir para su visualización.

management:
  endpoint:
    health:
      show-details: always

La información detallada de salud es la siguiente:

inserte la descripción de la imagen aquí

En la actualidad, salvo la información de salud, no se puede consultar otra información. El motivo es que los otros 12 tipos de información no se proporcionan al servidor de forma predeterminada para su visualización a través de solicitudes HTTP, por lo que debe habilitar la visualización de los elementos de contenido y utilizar * para indicar que puede visualizarlos todos. Recuerde incluir comillas.

endpoints:
  web:
    exposure:
      include: "*"

Actualice la página del servidor después de la configuración y podrá ver toda la información.

inserte la descripción de la imagen aquí

La cantidad de información que se muestra en la interfaz anterior es muy grande, incluidos 13 grupos de información, incluida la supervisión del indicador de rendimiento, la lista de frijoles cargados, las propiedades del sistema cargado, el control de visualización de registros, etc.

Configurar varios clientes

Puede agregar coordenadas de cliente a otros programas springboot configurando el cliente, de modo que el servidor actual pueda monitorear múltiples programas de cliente. Cada cliente muestra diferente información de monitoreo.

Ingrese al panel de monitoreo, si la aplicación que cargó tiene funciones, puede ver 3 conjuntos de información que se muestran en el panel de monitoreo que son diferentes del proyecto vacío cargado anteriormente.

  • Puede consultar las
    clases

  • Todas las solicitudes para la configuración actual de la aplicación se pueden ver en el mapa

  • En los indicadores de rendimiento, puede ver las
    estadísticas

Resumir

  1. Para desarrollar el servidor de monitoreo, debe importar las coordenadas, luego agregar la anotación @EnableAdminServer a la clase de arranque y configurarlo como un programa web
  2. Para desarrollar el cliente monitoreado, debe importar las coordenadas, luego configurar la dirección del servidor del servidor y configurar los indicadores abiertos.
  3. Se pueden encontrar varios indicadores monitoreados en la plataforma de monitoreo, siempre que el cliente haya abierto los indicadores monitoreados

6.3 Principio de seguimiento

Al consultar los indicadores de mapeo en el monitoreo, puede ver todas las rutas de solicitud que se pueden ejecutar en el sistema actual, la mayoría de las cuales comienzan con /actuador

En primer lugar, estas rutas de solicitud no están escritas por los propios desarrolladores y, en segundo lugar, ¿qué representa esta ruta? Ahora que se puede acceder a esta ruta, puede enviar esta solicitud a través del navegador para ver qué información puede obtener.

Al enviar una solicitud, puede obtener un conjunto de información json, de la siguiente manera

{
    
    
    "_links": {
    
    
        "self": {
    
    
            "href": "http://localhost:81/actuator",
            "templated": false
        },
        "beans": {
    
    
            "href": "http://localhost:81/actuator/beans",
            "templated": false
        },
        "caches-cache": {
    
    
            "href": "http://localhost:81/actuator/caches/{cache}",
            "templated": true
        },
        "caches": {
    
    
            "href": "http://localhost:81/actuator/caches",
            "templated": false
        },
        "health": {
    
    
            "href": "http://localhost:81/actuator/health",
            "templated": false
        },
        "health-path": {
    
    
            "href": "http://localhost:81/actuator/health/{*path}",
            "templated": true
        },
        "info": {
    
    
            "href": "http://localhost:81/actuator/info",
            "templated": false
        },
        "conditions": {
    
    
            "href": "http://localhost:81/actuator/conditions",
            "templated": false
        },
        "shutdown": {
    
    
            "href": "http://localhost:81/actuator/shutdown",
            "templated": false
        },
        "configprops": {
    
    
            "href": "http://localhost:81/actuator/configprops",
            "templated": false
        },
        "configprops-prefix": {
    
    
            "href": "http://localhost:81/actuator/configprops/{prefix}",
            "templated": true
        },
        "env": {
    
    
            "href": "http://localhost:81/actuator/env",
            "templated": false
        },
        "env-toMatch": {
    
    
            "href": "http://localhost:81/actuator/env/{toMatch}",
            "templated": true
        },
        "loggers": {
    
    
            "href": "http://localhost:81/actuator/loggers",
            "templated": false
        },
        "loggers-name": {
    
    
            "href": "http://localhost:81/actuator/loggers/{name}",
            "templated": true
        },
        "heapdump": {
    
    
            "href": "http://localhost:81/actuator/heapdump",
            "templated": false
        },
        "threaddump": {
    
    
            "href": "http://localhost:81/actuator/threaddump",
            "templated": false
        },
        "metrics-requiredMetricName": {
    
    
            "href": "http://localhost:81/actuator/metrics/{requiredMetricName}",
            "templated": true
        },
        "metrics": {
    
    
            "href": "http://localhost:81/actuator/metrics",
            "templated": false
        },
        "scheduledtasks": {
    
    
            "href": "http://localhost:81/actuator/scheduledtasks",
            "templated": false
        },
        "mappings": {
    
    
            "href": "http://localhost:81/actuator/mappings",
            "templated": false
        }
    }
}

​ Cada conjunto de datos tiene una ruta de solicitud, y aquí la ruta de solicitud contiene el estado que se ha visto antes, y se obtiene un conjunto de información al enviar esta solicitud

{
    
    
    "status": "UP",
    "components": {
    
    
        "diskSpace": {
    
    
            "status": "UP",
            "details": {
    
    
                "total": 297042808832,
                "free": 72284409856,
                "threshold": 10485760,
                "exists": true
            }
        },
        "ping": {
    
    
            "status": "UP"
        }
    }
}

​ Existe una relación correspondiente entre la información actual y los datos en el panel de monitoreo

​ Resulta que la información que se muestra en el monitoreo es en realidad los datos json obtenidos después de enviar la solicitud y luego se muestran. De acuerdo con las operaciones anteriores, puede enviar más direcciones de enlace que comiencen con /actuador para obtener más datos, y estos datos se agregan para formar todos los datos que se muestran en la plataforma de monitoreo.

Aquí obtenemos una pieza central de información. La información que se muestra en la plataforma de monitoreo en realidad se obtiene enviando una solicitud a la aplicación monitoreada. ¿Quién desarrolló estas solicitudes? Abra el archivo pom de la aplicación monitoreada, que importa el cliente correspondiente del administrador de springboot e importa un paquete llamado actuador en este recurso. La razón por la que la aplicación supervisada puede proporcionar la ruta de solicitud anterior de forma externa se debe a la adición de este paquete.

El actuador, que puede denominarse punto final, describe un conjunto de información de supervisión. SpringBootAdmin proporciona varios puntos finales integrados. Al acceder al punto final, puede obtener la información de supervisión correspondiente y también puede personalizar la información del punto final según sea necesario. /actuatorPuede acceder a toda la información del punto final de la aplicación enviando una ruta de solicitud . Si hay información detallada en el punto final, puede enviar una solicitud /actuator/端点名称para obtener la información detallada. Todas las descripciones de información de punto final se enumeran a continuación:

IDENTIFICACIÓN describir habilitado por defecto
eventos de auditoría Expone información de eventos de auditoría para la aplicación actual.
frijoles Muestra una lista completa de todos los frijoles Spring en la aplicación.
cachés Exponer cachés disponibles.
condiciones Muestra las condiciones evaluadas en las clases de configuración y configuración automática y por qué coincidieron o no.
accesorios de configuración Muestra una lista de prueba de todas las @ConfigurationProperties.
env Exponer propiedades en Spring ConfigurableEnvironment.
ruta migratoria Muestra las migraciones de bases de datos Flyway aplicadas.
health 显示应用程序健康信息
httptrace 显示 HTTP 追踪信息(默认情况下,最后 100 个 HTTP 请求/响应交换)。
info 显示应用程序信息。
integrationgraph 显示 Spring Integration 图。
loggers 显示和修改应用程序中日志记录器的配置。
liquibase 显示已应用的 Liquibase 数据库迁移。
metrics 显示当前应用程序的指标度量信息。
mappings 显示所有 @RequestMapping 路径的整理清单。
scheduledtasks 显示应用程序中的调度任务。
sessions 允许从 Spring Session 支持的会话存储中检索和删除用户会话。当使用 Spring Session 的响应式 Web 应用程序支持时不可用。
shutdown 正常关闭应用程序。
threaddump 执行线程 dump。
heapdump 返回一个 hprof 堆 dump 文件。
jolokia 通过 HTTP 暴露 JMX bean(当 Jolokia 在 classpath 上时,不适用于 WebFlux)。
logfile 返回日志文件的内容(如果已设置 logging.file 或 logging.path 属性)。支持使用 HTTP Range 头来检索部分日志文件的内容。
prometheus 以可以由 Prometheus 服务器抓取的格式暴露指标。

​ 上述端点每一项代表被监控的指标,如果对外开放则监控平台可以查询到对应的端点信息,如果未开放则无法查询对应的端点信息。通过配置可以设置端点是否对外开放功能。使用enable属性控制端点是否对外开放。其中health端点为默认端点,不能关闭。

management:
  endpoint:
    health:						# 端点名称
      show-details: always
    info:						# 端点名称
      enabled: true				# 是否开放

​ 为了方便开发者快速配置端点,springboot admin设置了13个较为常用的端点作为默认开放的端点,如果需要控制默认开放的端点的开放状态,可以通过配置设置,如下:

management:
  endpoints:
    enabled-by-default: true	# 是否开启默认端点,默认值true

​ 上述端点开启后,就可以通过端点对应的路径查看对应的信息了。但是此时还不能通过HTTP请求查询此信息,还需要开启通过HTTP请求查询的端点名称,使用“*”可以简化配置成开放所有端点的WEB端HTTP请求权限。

management:
  endpoints:
    web:
      exposure:
        include: "*"

​ 整体上来说,对于端点的配置有两组信息,一组是endpoints开头的,对所有端点进行配置,一组是endpoint开头的,对具体端点进行配置。

management:
  endpoint:		# 具体端点的配置
    health:
      show-details: always
    info:
      enabled: true
  endpoints:	# 全部端点的配置
    web:
      exposure:
        include: "*"
    enabled-by-default: true

总结

  1. 被监控客户端通过添加actuator的坐标可以对外提供被访问的端点功能

  2. 端点功能的开放与关闭可以通过配置进行控制

  3. web端默认无法获取所有端点信息,通过配置开放端点功能

6.4、自定义监控指标

端点描述了被监控的信息,除了系统默认的指标,还可以自行添加显示的指标,下面就通过3种不同的端点的指标自定义方式来学习端点信息的二次开发。

INFO端点

​ info端点描述了当前应用的基本信息,可以通过两种形式快速配置info端点的信息

  • 配置形式

    在yml文件中通过设置info节点的信息就可以快速配置端点信息

    info:
      appName: @project.artifactId@
      version: @project.version@
      company: 昂焱数据
      author: ayshuju
    

    配置完毕后,对应信息显示在监控平台上

    也可以通过请求端点信息路径获取对应json信息

  • 编程形式

    通过配置的形式只能添加固定的数据,如果需要动态数据还可以通过配置bean的方式为info端点添加信息,此信息与配置信息共存

    @Component
    public class InfoConfig implements InfoContributor {
          
          
        @Override
        public void contribute(Info.Builder builder) {
          
          
            builder.withDetail("runTime",System.currentTimeMillis());		//添加单个信息
            Map infoMap = new HashMap();		
            infoMap.put("buildTime","2006");
            builder.withDetails(infoMap);									//添加一组信息
        }
    }
    

Health端点

​ health端点描述当前应用的运行健康指标,即应用的运行是否成功。通过编程的形式可以扩展指标信息。

@Component
public class HealthConfig extends AbstractHealthIndicator {
    
    
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
    
    
        boolean condition = true;
        if(condition) {
    
    
            builder.status(Status.UP);					//设置运行状态为启动状态
            builder.withDetail("runTime", System.currentTimeMillis());
            Map infoMap = new HashMap();
            infoMap.put("buildTime", "2006");
            builder.withDetails(infoMap);
        }else{
    
    
            builder.status(Status.OUT_OF_SERVICE);		//设置运行状态为不在服务状态
            builder.withDetail("上线了吗?","你做梦");
        }
    }
}

​ 当任意一个组件状态不为UP时,整体应用对外服务状态为非UP状态。

Metrics端点

​ metrics端点描述了性能指标,除了系统自带的监控性能指标,还可以自定义性能指标。

@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
    
    
    @Autowired
    private BookDao bookDao;

    private Counter counter;

    public BookServiceImpl(MeterRegistry meterRegistry){
    
    
        counter = meterRegistry.counter("用户付费操作次数:");
    }

    @Override
    public boolean delete(Integer id) {
    
    
        //每次执行删除业务等同于执行了付费业务
        counter.increment();
        return bookDao.deleteById(id) > 0;
    }
}

​ 在性能指标中就出现了自定义的性能指标监控项

自定义端点

​ 可以根据业务需要自定义端点,方便业务监控

@Component
@Endpoint(id="pay",enableByDefault = true)
public class PayEndpoint {
    
    
    @ReadOperation
    public Object getPay(){
    
    
        Map payMap = new HashMap();
        payMap.put("level 1","300");
        payMap.put("level 2","291");
        payMap.put("level 3","666");
        return payMap;
    }
}

​ 由于此端点数据spirng boot admin无法预知该如何展示,所以通过界面无法看到此数据,通过HTTP请求路径可以获取到当前端点的信息,但是需要先开启当前端点对外功能,或者设置当前端点为默认开发的端点。

总结

  1. Los indicadores del punto final se pueden personalizar, pero cada indicador diferente tiene diferentes métodos de personalización según su función
  2. El punto final de información puede agregar indicadores de punto final a través de métodos programáticos y de configuración
  3. El punto final de salud agrega indicadores de punto final mediante programación. Es necesario prestar atención a la configuración lógica de agregar el estado de inicio para los indicadores correspondientes.
  4. Indicadores métricos Establecer indicadores agregando operaciones de monitoreo en el negocio.
  5. Puede personalizar el punto final para agregar más indicadores

Supongo que te gusta

Origin blog.csdn.net/shuai_h/article/details/130030579
Recomendado
Clasificación