Directorio de artículos
Patrones de diseño en primavera.
1. Inversión de control (IoC) e inyección de dependencia (DI)
IoC (Inversión de control, Inversión de control) es un concepto muy, muy importante en Spring: no es una tecnología, sino una idea de diseño desacoplada. El objetivo principal de IoC es utilizar un "tercero" (contenedor de IoC en Spring) para lograr el desacoplamiento entre objetos con dependencias (objetos de administración de contenedores de IOC, simplemente puede usarlos), reduciendo así el acoplamiento entre códigos.
IoC es un principio, no un patrón, y los siguientes patrones (entre otros) implementan el principio de IoC.
El contenedor Spring IoC es como una fábrica: cuando necesitamos crear un objeto, solo necesitamos configurar el archivo de configuración/las anotaciones sin pensar en cómo se crea el objeto. El contenedor IoC es responsable de crear objetos, conectarlos, configurarlos y manejar todo el ciclo de vida de esos objetos desde su creación hasta su destrucción completa.
En proyectos reales, si una clase de Servicio tiene cientos o incluso miles de clases como capa inferior, necesitamos crear una instancia de este Servicio. Es posible que tenga que descubrir los constructores de todas las clases inferiores de este Servicio cada vez, lo que puede causar que Drive gente loca. Si usa IOC, solo necesita configurarlo y hacer referencia a él cuando sea necesario, lo que aumenta en gran medida la mantenibilidad del proyecto y reduce la dificultad de desarrollo.
Con respecto a la comprensión de Spring IOC, se recomienda leer esta respuesta en Zhihu: https://www.zhihu.com/question/23277575/answer/169698662abrir en una nueva ventana , Es muy bueno.
¿Cómo entender la inversión de control? Por ejemplo: "El objeto a depende del objeto b. Cuando el objeto a necesita usar el objeto b, debe crearlo por sí mismo. Pero cuando el contenedor IOC se introduce en el sistema, hay una brecha entre objeto a y objeto b. La conexión directa se pierde. En este momento, cuando el objeto a necesita usar el objeto b, podemos especificar el contenedor IOC para crear un objeto by inyectarlo en el objeto a. El proceso por el cual el objeto a se vuelve dependiente del objeto b cambia de un comportamiento activo a un comportamiento pasivo y el control se invierte, de ahí el nombre de inversión de control.
DI (Dependency Inject, inyección de dependencia) es un patrón de diseño que implementa la inversión de control.La inyección de dependencia consiste en pasar variables de instancia a un objeto.
2. Patrón de diseño de fábrica
Spring usa el patrón de fábrica para crear objetos bean a través de BeanFactory
o .ApplicationContext
Compara los dos:
BeanFactory
: Inyección retrasada (solo se inyectará cuando se use un determinado bean), en comparación con , ocuparáApplicationContext
menos memoria y el programa se iniciará más rápido.ApplicationContext
: Cuando se inicia el contenedor, independientemente de si lo usa o no, todos los beans se crean a la vez.BeanFactory
Solo proporciona el soporte de inyección de dependencia más básico.DespuésApplicationContext
de la expansiónBeanFactory
, además deBeanFactory
algunas funciones, hay funciones adicionales, por lo que la mayoría de los desarrolladoresApplicationContext
las usarán más.
ApplicationContext
Tres clases de implementación:
ClassPathXmlApplication
: Trate los archivos de contexto como recursos de classpath.FileSystemXmlApplication
: carga información de definición de contexto desde un archivo XML en el sistema de archivos.XmlWebApplicationContext
: carga información de definición de contexto desde el archivo XML en el sistema web.
Ejemplo:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext(
"C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml");
HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");
obj.getMsg();
}
}
3. Patrón de diseño singleton
Método para realizar
1. Estilo chino hambriento
Código:
public class Test {
public static void main(String[] args) {
Single single = Single.getSingle();
}
}
class Single{
private Single(){
}
private static Single s = new Single();
public static Single getSingle(){
return s;
}
}
2. Estilo del hombre perezoso
La carga diferida solo inicia la creación de instancias cuando realmente se usa.
Problemas de seguridad de subprocesos:
verifique dos veces el compilador de optimización de bloqueo
(JIT), la CPU puede reordenar las instrucciones, lo que resulta en el uso de instancias no inicializadas, que se pueden modificar agregando la palabra clave volátil. Para los campos modificados por volátil, se puede evitar que las instrucciones se repitan .Fila.
Código:
class Single {
// 禁止指令重排
private volatile static Single s;
private Single() {
}
public static Single getSingle() {
if (s == null) {
synchronized (Single.class) {
if (s == null) {
s = new Single();
// 字节码层
// JIT , CPU 有可能对如下指令进行重排序
// 1 .分配空间
// 2 .初始化
// 3 .引用赋值
// 如重排序后的结果为如
// 1 .分配空间
// 3 .引用赋值 如果在当前指令执行完,有其他线程来获取实例,将拿到尚未初始化好的实例
// 2 .初始化
}
}
}
return s;
}
}
2. Clase interna estática
Esencialmente, el mecanismo de carga de clases se utiliza para garantizar la seguridad de los subprocesos.
La inicialización de la clase solo se activará cuando se use realmente, por lo que también es una forma de carga diferida.
Código:
class Single {
private static class InnerSingle {
private static Single s = new Single();
}
private Single() {
// 防止利用反射攻击
if(InnerSingle.s == null){
throw new RuntimeException("单例不允许多个实例");
}
}
public static Single getSingle() {
return InnerSingle.s;
}
}
4. Clase de enumeración
Naturalmente, no admite la reflexión para crear instancias correspondientes y tiene su propio mecanismo de deserialización. El
mecanismo de carga de clases se utiliza para garantizar la seguridad de los subprocesos.
Código:
public enum EnumSingleton {
INSTANCE;
}
Patrón singleton en primavera
En nuestro sistema, hay algunos objetos que en realidad solo necesitamos uno, como: grupo de subprocesos, caché, cuadro de diálogo, registro, objeto de registro, objetos que actúan como controladores de dispositivos para impresoras, tarjetas gráficas, etc. De hecho, este tipo de objeto solo puede tener una instancia. La creación de varias instancias puede causar algunos problemas, como un comportamiento anormal del programa, uso excesivo de recursos o resultados inconsistentes.
Ventajas de utilizar el patrón singleton :
- Para los objetos de uso frecuente, se puede omitir el tiempo dedicado a crear objetos, lo que supone una sobrecarga del sistema muy considerable para esos objetos pesados;
- Dado que se reduce la cantidad de operaciones nuevas, también se reducirá la frecuencia de uso de la memoria del sistema, lo que reducirá la presión del GC y acortará el tiempo de pausa del GC.
El alcance predeterminado de los beans en Spring es singleton. Además del alcance singleton, los beans en Spring también tienen los siguientes alcances:
- prototipo : se crea una nueva instancia de bean cada vez que se recupera. En otras palabras,
getBean()
dos veces consecutivas, obtienes diferentes instancias de Bean. - Solicitud (disponible solo para aplicaciones web): cada solicitud HTTP generará un nuevo bean (bean de solicitud), que solo es válido dentro de la solicitud HTTP actual.
- sesión (disponible solo para aplicaciones web): cada solicitud HTTP de una nueva sesión generará un nuevo bean (bean de sesión), que solo es válido dentro de la sesión HTTP actual.
- aplicación/sesión global (disponible solo para aplicaciones web): cada aplicación web crea un Bean (Bean de aplicación) cuando se inicia, y el bean solo es válido durante el tiempo de inicio actual de la aplicación.
- websocket (disponible solo para aplicaciones web): cada sesión de WebSocket genera un nuevo bean.
Spring ConcurrentHashMap
implementa el patrón singleton mediante una forma especial de implementar un registro singleton.
El código central de la implementación singleton de Spring es el siguiente:
// 通过 ConcurrentHashMap(线程安全) 实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized (this.singletonObjects) {
// 检查缓存中是否存在实例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//...省略了很多代码
try {
singletonObject = singletonFactory.getObject();
}
//...省略了很多代码
// 如果实例对象在不存在,我们注册到单例注册表中。
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
//将对象添加到单例注册表
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
}
}
}
¿Existen problemas de seguridad de subprocesos con beans singleton?
La mayoría de las veces no utilizamos subprocesos múltiples en nuestros proyectos, por lo que pocas personas prestan atención a este problema. Los Singleton Beans tienen problemas de subprocesos, principalmente porque existe competencia por recursos cuando varios subprocesos operan el mismo objeto.
Hay dos soluciones comunes:
- Intente evitar definir variables miembro mutables en Beans.
- Defina una variable miembro en la clase
ThreadLocal
y guarde las variables miembro requeridas enThreadLocal
(una forma recomendada).
Sin embargo, la mayoría de los beans en realidad no tienen estado (sin variables de instancia) (como Dao, Servicio). En este caso, los beans son seguros para subprocesos.
4. Patrón de diseño de agencia
Aplicación del patrón proxy en AOP
AOP (Programación orientada a aspectos) puede encapsular lógica o responsabilidades (como procesamiento de transacciones, gestión de registros, control de permisos, etc.) que no tienen nada que ver con el negocio, pero que los módulos comerciales comúnmente invocan para reducir el código duplicado en el sistema. , reduce el acoplamiento entre módulos y facilita la escalabilidad y mantenibilidad futuras.
Spring AOP se basa en un proxy dinámico . Si el objeto a ser proxy implementa una determinada interfaz, entonces Spring AOP usará JDK Proxy para crear el objeto proxy. Para los objetos que no implementan la interfaz, JDK Proxy no se puede usar como proxy. Esta vez, Spring AOP usará Cglib para generar una subclase del objeto proxy como proxy, como se muestra en la siguiente figura:
Por supuesto, también puede utilizar AspectJ. Spring AOP ha integrado AspectJ. AspectJ debe considerarse como el marco AOP más completo en el ecosistema Java.
Después de usar AOP, podemos abstraer algunas funciones comunes y usarlas directamente donde sean necesarias, lo que simplifica enormemente la cantidad de código. También es conveniente cuando necesitamos agregar nuevas funciones, lo que también mejora la escalabilidad del sistema. AOP se utiliza en funciones de registro, gestión de transacciones y otros escenarios.
¿Cuál es la diferencia entre Spring AOP y AspectJ AOP?
Spring AOP es una mejora en tiempo de ejecución, mientras que AspectJ es una mejora en tiempo de compilación. Spring AOP se basa en Proxying, mientras que AspectJ se basa en la manipulación de Bytecode.
Spring AOP ha integrado AspectJ, que debe considerarse como el marco AOP más completo del ecosistema Java. AspectJ es más poderoso que Spring AOP, pero Spring AOP es relativamente más simple.
Si tenemos menos aspectos no hay mucha diferencia de rendimiento entre ambos. Sin embargo, cuando hay demasiados aspectos, es mejor elegir AspectJ, que es mucho más rápido que Spring AOP.
5. Método de plantilla
El patrón Método de plantilla es un patrón de diseño de comportamiento que define el esqueleto de un algoritmo en funcionamiento y al mismo tiempo difiere algunos pasos a subclases. Los métodos de plantilla permiten a las subclases redefinir la implementación de ciertos pasos específicos de un algoritmo sin cambiar la estructura del algoritmo.
public abstract class Template {
//这是我们的模板方法
public final void TemplateMethod(){
PrimitiveOperation1();
PrimitiveOperation2();
PrimitiveOperation3();
}
protected void PrimitiveOperation1(){
//当前类实现
}
//被子类实现的方法
protected abstract void PrimitiveOperation2();
protected abstract void PrimitiveOperation3();
}
public class TemplateImpl extends Template {
@Override
public void PrimitiveOperation2() {
//当前类实现
}
@Override
public void PrimitiveOperation3() {
//当前类实现
}
}
En Spring JdbcTemplate
, HibernateTemplate
las clases que terminan en Plantilla y que operan en la base de datos utilizan el patrón de plantilla. En circunstancias normales, usamos la herencia para implementar el patrón de plantilla, pero Spring no usa este método, sino que usa el patrón de devolución de llamada para cooperar con el patrón del método de plantilla, lo que no solo logra el efecto de reutilización del código, sino que también aumenta la flexibilidad. . .
6. Patrón de observador
El patrón de observador es un patrón de comportamiento de un objeto. Representa una relación de dependencia entre objetos. Cuando un objeto cambia, todos los objetos que dependen de este objeto también reaccionarán. El modelo basado en eventos de Spring es una aplicación clásica del patrón de observador. El modelo basado en eventos de Spring es muy útil y puede desacoplar nuestro código en muchos escenarios. Por ejemplo, cada vez que agregamos un producto, necesitamos actualizar el índice del producto, en este momento podemos usar el patrón de observador para resolver este problema.
Tres roles en el modelo impulsado por eventos de Spring
rol del evento
ApplicationEvent
( org.springframework.context
Debajo del paquete) Actúa como un evento. Esta es una clase abstracta que hereda java.util.EventObject
e implementa java.io.Serializable
la interfaz.
Los siguientes eventos existen de forma predeterminada en Spring y todos son ApplicationContextEvent
implementaciones de (heredados de ApplicationContextEvent
):
ContextStartedEvent
:ApplicationContext
Evento desencadenado después del inicio;ContextStoppedEvent
:ApplicationContext
Evento desencadenado después de detenerse;ContextRefreshedEvent
:ApplicationContext
Evento desencadenado después de que se completa la inicialización o actualización;ContextClosedEvent
:ApplicationContext
Evento desencadenado después del cierre.
rol de oyente de eventos
ApplicationListener
Actuando como un detector de eventos, es una interfaz con un solo método definido onApplicationEvent()
para el procesamiento ApplicationEvent
. ApplicationListener
El código fuente de la clase de interfaz es el siguiente: puede ver la definición de la interfaz y los eventos en la interfaz solo necesitan implementarse ApplicationEvent
. Por lo tanto, en Spring solo necesitamos implementar el método ApplicationListener
de la interfaz onApplicationEvent()
para completar el evento de escucha.
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
rol de editor de eventos
ApplicationEventPublisher
Al actuar como editor de eventos, también es una interfaz.
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
this.publishEvent((Object)event);
}
void publishEvent(Object var1);
}
ApplicationEventPublisher
publishEvent()
Este método de interfaz AbstractApplicationContext
se implementa en la clase. Al leer la implementación de este método, encontrará que el evento realmente se ApplicationEventMulticaster
transmite.
Resumen del proceso del evento de primavera.
- Definir un evento: implementar un evento heredado
ApplicationEvent
y escribir el constructor correspondiente; - Defina un detector de eventos: implemente
ApplicationListener
la interfaz y anuleonApplicationEvent()
el método; - Publicar mensajes utilizando publicadores de eventos: puede publicar mensajes mediante el método
ApplicationEventPublisher
depublishEvent()
.
Ejemplo:
// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;
private String message;
public DemoEvent(Object source,String message){
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{
//使用onApplicationEvent接收消息
@Override
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMessage();
System.out.println("接收到的信息是:"+msg);
}
}
// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String message){
//发布事件
applicationContext.publishEvent(new DemoEvent(this, message));
}
}
Cuando se llama al método de, por ejemplo DemoPublisher
, la consola imprimirá: .publish()
demoPublisher.publish("你好")
接收到的信息是:你好
7.Modo adaptador
El patrón de adaptador convierte una interfaz en otra interfaz que el cliente desea y permite que clases con interfaces incompatibles trabajen juntas.
Patrón de adaptador en Spring AOP
Sabemos que la implementación de Spring AOP se basa en el modo proxy, pero la mejora o el consejo de Spring AOP utiliza el modo adaptador y la interfaz relacionada es AdvisorAdapter
.
Los tipos de asesoramiento comúnmente utilizados son: BeforeAdvice
(antes de llamar al método de destino, aviso previo), AfterAdvice
(después de llamar al método de destino, aviso posterior), AfterReturningAdvice
(después de ejecutar el método de destino, antes del retorno), etc. Cada tipo de Consejo tiene un interceptor correspondiente: MethodBeforeAdviceInterceptor
, AfterReturningAdviceInterceptor
, ThrowsAdviceInterceptor
etc.
Las notificaciones predefinidas de Spring deben adaptarse a MethodInterceptor
objetos de tipo interfaz (interceptor de métodos) a través del adaptador correspondiente (por ejemplo: MethodBeforeAdviceAdapter
llamando getInterceptor
al método, MethodBeforeAdvice
adaptarse a MethodBeforeAdviceInterceptor
).
Patrón de adaptador en Spring MVC
En Spring MVC, DispatcherServlet
se llama según la información de la solicitud HandlerMapping
y analiza la solicitud correspondiente Handler
. Después de analizar el correspondiente Handler
(que es lo que normalmente llamamos Controller
controlador), HandlerAdapter
el adaptador comienza a procesarlo. HandlerAdapter
Como interfaz deseada, se utiliza la clase de implementación del adaptador específico para adaptar la clase de destino Controller
como la clase que necesita adaptarse.
¿Por qué utilizar el patrón adaptador en Spring MVC?
Hay muchos tipos en Spring MVC Controller
y diferentes tipos Controller
manejan las solicitudes de diferentes maneras. Si no utiliza el modo adaptador, DispatcherServlet
puede obtener el tipo correspondiente directamente Controller
y juzgar lo que necesita usted mismo, como el siguiente código:
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
Si agregamos otro Controller
tipo, debemos agregar otra línea de declaración de juicio al código anterior. Esta forma dificulta el mantenimiento del programa y viola el principio de apertura y cierre en el patrón de diseño: abierto para expansión y cerrado para modificación.
patrón decorador
El patrón decorador puede agregar dinámicamente algunas propiedades o comportamientos adicionales a un objeto. En comparación con el uso de la herencia, el patrón decorador es más flexible. En pocas palabras, cuando necesitamos modificar la función original, pero no queremos modificar el código original directamente, diseñamos un Decorador para que quepa fuera del código original. De hecho, el patrón decorador se usa en muchos lugares del JDK, como en InputStream
las familias, InputStream
hay subclases debajo de la clase FileInputStream
(leer archivos), BufferedInputStream
(aumentar el caché, mejorar en gran medida la velocidad de lectura de archivos), etc., que se pueden InputStream
ampliar . sin modificar el código su función.
Al configurar DataSource en Spring, DataSource puede ser diferentes bases de datos y fuentes de datos. ¿Podemos cambiar dinámicamente diferentes fuentes de datos según las necesidades del cliente con menos modificación del código de clase original? En este momento, se utilizará el patrón decorador (todavía no entiendo el principio específico de esto). El patrón contenedor utilizado en Spring contiene Wrapper
o en el nombre de la clase Decorator
. Básicamente, estas clases agregan dinámicamente algunas responsabilidades adicionales a un objeto.
Resumir
¿Qué patrones de diseño se utilizan en el marco Spring?
- Patrón de diseño de fábrica : Spring usa el patrón de fábrica para
BeanFactory
crearApplicationContext
objetos Bean. - Patrón de diseño de proxy : implementación de la funcionalidad Spring AOP.
- Patrón de diseño singleton : los frijoles en primavera son singleton de forma predeterminada.
- Patrón de método de plantilla : en Spring
jdbcTemplate
,hibernateTemplate
las clases que terminan en Plantilla y que operan en la base de datos utilizan el patrón de plantilla. - Patrón de diseño de contenedor : nuestro proyecto necesita conectarse a múltiples bases de datos, y diferentes clientes accederán a diferentes bases de datos según sus necesidades durante cada visita. Este modelo nos permite cambiar dinámicamente entre diferentes fuentes de datos según las necesidades del cliente.
- Patrón de observador: el modelo basado en eventos de Spring es una aplicación clásica del patrón de observador.
- Patrón de adaptador : la mejora o el consejo de Spring AOP utiliza el patrón de adaptador, y Spring MVC también utiliza la adaptación del patrón de adaptador
Controller
. - …