Un blog para entender los principios de AOP

El principio subyacente de AOP

¿Qué es AOP?

  • AOP—Programación Orientada a Aspectos Programación Orientada a Aspectos.
  • AOP adopta un mecanismo de extracción horizontal para reemplazar el código repetitivo (supervisión del rendimiento, gestión de transacciones, verificación de seguridad, caché) del sistema de herencia vertical tradicional.
  • Después de implementar Spring AOP en Java puro, no se requiere un proyecto de compilación especial ni un cargador de clases, y el código se mejora para la organización de la clase de destino a través del modo proxy durante el tiempo de ejecución.
  • AspectJ es un marco AOP basado en el lenguaje Java. A partir de Spring 2.0, Spring AOP introduce soporte para Aspect. AspectJ amplía el lenguaje Java y proporciona un compilador especial que proporciona organización de código horizontal durante la compilación.

El principio subyacente de AOP

AOP es el mecanismo de proxy, proxy dinámico: (usado en JDK) Proxy dinámico de JDK, que genera proxies para clases que implementan interfaces.

Proxy AOP en Spring: uno es el proxy dinámico JDK y el otro es el mecanismo de proxy CGLib (generación de proxy para clases).

Términos relacionados con AOP:

  • Joinpoint (punto de conexión): El llamado punto de conexión se refiere a aquellos puntos que pueden ser interceptados. En Spring, estos puntos se refieren a métodos, ya que Spring solo admite puntos de unión de tipo de método. Es decir, los puntos de unión se refieren a aquellos cabellos que pueden ser interceptados.
  • Pointcut (punto de entrada): El llamado punto de entrada se refiere a qué Joinpoints queremos interceptar.
  • Asesoramiento (notificación/mejora): La llamada notificación significa que lo que hay que hacer después de interceptar el Joinpoint es notificar. Las notificaciones se dividen en prenotificaciones, postnotificaciones, notificaciones de excepción, notificaciones finales y notificaciones envolventes, es decir, la mejora es la función que debe completar el aspecto (es una mejora a nivel de método).
  • Introducción (introduction): Introducción es una notificación especial.Bajo la premisa de no modificar el código de la clase, Introducción puede agregar dinámicamente algunos métodos o campos (variables) a la clase durante el tiempo de ejecución. Es una mejora a nivel de clase que agrega un atributo o método a la clase original.
  • Target (objeto de destino): El objeto de destino del agente, es decir, el objeto a mejorar.
  • Tejido (implantación): es el proceso de aplicar mejoras a los objetos de destino para crear nuevos objetos proxy. Spring usa la implantación de proxy dinámica, mientras que AspectJ usa la implantación en tiempo de compilación y la implantación en tiempo de carga de clase.
  • Proxy (proxy): después de mejorar una clase mediante la implantación de AOP, se genera una clase de proxy resultante.
  • Aspecto (aspecto): Es una combinación de punto de entrada y notificación (introducción). Se permiten múltiples combinaciones de consejos y puntos de corte.

imagen-20220906000217103

La implementación subyacente de AOP

JDK1.3 introduce la tecnología de proxy dinámico y escribe programas de proxy dinámico (java.lang.reflect.Proxy y java.lang.reflect.InvocationHandler), como se muestra en la figura:

Generar proxy dinámico usando JDK

UserDao

/**
 * DAO的接口
 *
 */
public interface UserDao {
    
    
    public void add();
    public void update();
}

UserDaoImpl

public class UserDaoImpl implements UserDao {
    
    
    @Override
    public void add() {
    
    
        System.out.println("添加用户");
    }
    @Override
    public void update() {
    
    
        System.out.println("修改用户");
    }
}

JDK Proxy

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * JDK的动态代理机制
 * 
 * @author liuxun
 *
 */
public class JDKProxy implements InvocationHandler {
    
    
    private UserDao userDao;
    public JDKProxy(UserDao userDao) {
    
    
        super();
        this.userDao = userDao;
    }
    public UserDao createProxy() {
    
    
        UserDao proxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
                userDao.getClass().getInterfaces(), this);
        return proxy;
    }
    @Override
    // 调用目标对象的任何一个方法都相当于invoke()
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        if ("add".equals(method.getName())) {
    
    
            // 记录日志
            System.out.println("日志记录===================");
            Object result = method.invoke(userDao, args);
            return result;
        }
        return method.invoke(userDao, args);
    }
}

PrimaveraPrueba1

import org.junit.Test;
public class SpringTest1 {
    
    
    @Test
    public void demo1() {
    
    
        UserDao userDao = new UserDaoImpl();
        userDao.add();
        userDao.update();
    }
    @Test
    public void demo2() {
    
    
        // 被代理对象
        UserDao userDao = new UserDaoImpl();
        // 创建代理对象的时候传入被代理对象
        UserDao proxy = new JDKProxy(userDao).createProxy();
        proxy.add();
        proxy.update();
    }
}

resultado de la operación

Generación de proxy usando CGLIB

Para clases de negocios que no usan interfaces, no se pueden usar proxies dinámicos JDK. Se puede usar la tecnología Cglib.Cglib usa tecnología de código de bytes de muy bajo nivel, que puede crear subclases para una clase para resolver el problema del proxy sin interfaz.

CGLIB (Biblioteca de generación de código) es una biblioteca de generación de código potente, de alta calidad y alto rendimiento, que puede ampliar las clases de Java e implementar interfaces en tiempo de ejecución. Hibernate lo admite para realizar la generación dinámica de código de bytes PO (objeto persistente de objetos persistentes), e Hibernate genera Javaassist para clases persistentes. De hecho, la esencia del mecanismo proxy de generación de CGLIB es generar una subclase del objeto real.

Si desea utilizar la tecnología CGLIB para generar un proxy, debe descargar el paquete jar de CGLIB, pero CGLIB se ha integrado en el paquete principal de Spring Core, por lo que no necesita hacer referencia directamente al paquete CGLIB.

imagen-20220906000526920

El código específico es el siguiente:

ProductoDao

public class ProductDao {
    
    
    public void add() {
    
    
        System.out.println("添加商品...");
    }
    public void update() {
    
    
        System.out.println("修改商品...");
    }
}

CGLibProxy

import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
 * 使用CGLib生成代理对象
 * 
 */
public class CGLibProxy implements MethodInterceptor {
    
    
    private ProductDao productDao;
    public CGLibProxy(ProductDao productDao) {
    
    
        super();
        this.productDao = productDao;
    }
    public ProductDao createProxy() {
    
    
        // 使用CGLIB生成代理
        // 1.创建核心类
        Enhancer enhancer = new Enhancer();
        // 2.为其设置父类
        enhancer.setSuperclass(productDao.getClass());
        // 3.设置回调
        enhancer.setCallback(this);
        // 4.创建代理
        return (ProductDao) enhancer.create();
    }
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    
    
        if ("add".equals(method.getName())) {
    
    
            System.out.println("日志记录=================");
            // Object obj=method.invoke(productDao, args);
            Object obj = methodProxy.invokeSuper(proxy, args);// 代理方法中执行代理对象父类中的方法
            return obj;
        }
        return methodProxy.invokeSuper(proxy, args);
    }
}

SpringTest2

import org.junit.Test;
public class SpringTest2 {
    
    
    @Test
    public void demo1() {
    
    
        ProductDao productDao = new ProductDao();
        productDao.add();
        productDao.update();
    }
    @Test
    public void demo2() {
    
    
        ProductDao productDao = new ProductDao();
        ProductDao proxy=new CGLibProxy(productDao).createProxy();
        proxy.add();
        proxy.update();
    }
}

resultado de la operación

Los parámetros del método de intercepción implementado cuando CGLIB genera un proxy son los siguientes:

  • @param obj Objeto proxy generado por CGlib según la clase principal especificada
  • método @param método interceptado
  • @param args La matriz de parámetros del método de intercepción
  • El objeto proxy del método proxy @param, que se utiliza para ejecutar el método de la clase principal

Nota: La última versión de Spring ha introducido la clase de desarrollo CGLib en spring-core-3.2.0.RELEASE.ja

Resumen del conocimiento del agente

Durante el tiempo de ejecución, Spring genera objetos proxy dinámicos y no requiere un compilador especial.

La capa inferior de Spring AOP es realizar la implantación horizontal para beans de destino a través del proxy dinámico JDK o la tecnología de proxy dinámico CGLib. El método de selección es el siguiente:

  • Si el objeto de destino implementa varias interfaces, Spring usa el proxy de clase java.lang.reflect.Proxy de JDK.
  • Si el objeto de destino no implementa ninguna interfaz, Spring usa la biblioteca CGLIB para generar una subclase del objeto de destino.

Primero se deben crear proxies para las interfaces en el programa para facilitar el desacoplamiento del programa para el mantenimiento.

Los métodos marcados como finales no se pueden representar porque no se pueden anular por las siguientes razones:

  • El proxy dinámico de JDK es para generar subclases para interfaces, y los métodos en las interfaces no se pueden modificar con final.
  • CGLib genera subclases para la clase de destino, por lo que las clases o los métodos no se pueden modificar con final.

Spring solo admite puntos de unión de métodos y no proporciona uniones de propiedades.

AOP en primavera

Spring tradicional AOP (tipo mejorado)

AOP no está definido por la primavera, sino por la Alianza AOP.

La Alianza AOP define org.aopalliance.aop.Interface.Advice for Advice

Spring se puede dividir en 5 categorías según la posición del punto de conexión de Advice en el método de clase de destino:

  • El aviso previo org.springframework.aop.MethodBeforeAdvice se mejora antes de la ejecución del método de destino.
  • El consejo posterior org.springframework.aop.AfterReturningAdvice implementa la mejora después de que el método de destino se haya ejecutado correctamente. (Después se ejecuta tanto antes como después de la ejecución del método)
  • Surround aconseja a org.aopalliance.intercept.MethodInterceptor que implemente mejoras antes y después de la ejecución del método de destino.
  • Aviso de lanzamiento de excepción org.springframework.aop.ThrowsAdvice implementa mejoras después de que un método lanza una excepción.
  • La introducción informa a org.springframework.aop.IntroductionInterceptor para agregar algunos métodos o propiedades nuevos en la clase de destino.

Tipos de aspecto de Spring AOP

  • Advisor: para los aspectos tradicionales en Spring, Advisor se compone de un punto de corte y una notificación, mientras que Aspect se compone de varios puntos de corte y varias notificaciones. Advisor representa un aspecto general, y Advice en sí mismo es un aspecto que intercepta todos los métodos de la clase de destino (un aspecto sin un punto de corte intercepta todos los métodos)
  • PointcutAdvisor: representa un aspecto con un punto de corte, que puede especificar qué métodos de la clase objetivo interceptar.
  • Asesor de introducción: representa el aspecto de introducción, que se utiliza para la notificación de introducción (no se utiliza en primavera).

Desarrollo de AOP en primavera

Caso del aspecto del asesor: notificación anticipada

Mejoras para todos los métodos: facetas sin puntos de corte, los pasos de desarrollo son los siguientes:

Paso 1: Introducir paquetes jar relacionados con spring aop

  • spring-aop-3.2.0.RELEASE.jar
  • com.springsource.org.aopalliance-1.0.0.jar (de AOP Alliance en el paquete de dependencia)

Paso 2: escribir el objeto proxy (interfaz y clase de implementación)

Paso 3: escribir código mejorado (escribir clase mejorada para implementar una interfaz de tipo mejorada)

Paso 4: Generar Proxy (Configurar Generar Proxy)

<bean id="helloAdvice" class="cn.test.springaop.HelloServiceBeforeAdvice"></bean>
<bean id="target" class="cn.test.springaop.HelloService" />
<bean id="helloService" class="org.springframework.aop.framework.ProxyFactoryBean" >
    <property name="proxyInterfaces" value="cn.test.springaop.IHelloService" />
    <!--如果不是针对接口代理,可以设置  <property name="proxyTargetClass" value="true"></property> ,将使用CGLib-->
    <property name="interceptorNames" value="helloAdvice"></property>
    <property name="target" ref="target"></property>
</bean>

El proxy generado por Spring se basa en la clase ProxyFactoryBean, y la capa inferior elige automáticamente si usar el proxy dinámico de JDK o el proxy de CGLIB.

  • Las propiedades configurables comúnmente utilizadas de ProxyFactoryBean son las siguientes:

  • target: el objeto de destino del proxy

  • proxyInterfaces: la interfaz que implementará el proxy. El valor es la ruta completa de la interfaz. Si el proxy implementa varias interfaces, puede usar los siguientes métodos para asignar valores:

  • proxyTargetClass: si desea usar el proxy de la clase en lugar de la interfaz, cuando se establece en verdadero, use el proxy CGLib.

  • interceptorNames: nombres de consejos que deben entretejerse en el objetivo.

  • singleton: devuelve si el proxy es una instancia única, el valor predeterminado es un singleton.

  • optimizar: cuando se establece en verdadero, se fuerza el uso de CGLib.

El código de ejemplo es el siguiente:

ClienteDao

public interface CustomerDao {
    
    
    public void add();
    public void update();
    public void delete();
    public void find();
}

ClienteDaoImpl

public class CustomerDaoImpl implements CustomerDao {
    
    
    public void add() {
    
    
        System.out.println("添加客户");
    }
    public void update() {
    
    
        System.out.println("修改客户");
    }
    public void delete() {
    
    
        System.out.println("删除客户");
    }
    public void find() {
    
    
        System.out.println("查询客户");
    }
}

Definir el tipo de mejora MyBeforeAdvice

/**
 * 前置增强
 */
public class MyBeforeAdvice implements MethodBeforeAdvice {
    
    
    /**
     * method:执行的方法
     * args:参数
     * target:目标对象
     */
    public void before(Method method, Object[] args, Object target) throws Throwable {
    
    
        System.out.println("前置增强...");
    }
}

Configurar applicationContext.xml mejorado

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 不带有切点的切面 -->
    <!-- 定义目标对象 -->
    <bean id="customerDao" class="spring3.aop.demo3.CustomerDaoImpl" />
    <!-- 定义增强 -->
    <bean id="beforeAdvice" class="spring3.aop.demo3.MyBeforeAdvice" />
    <!-- spring支持配置生成代理 -->
    <bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 设置目标对象 -->
        <property name="target" ref="customerDao" />
        <!-- 设置实现的接口,value中写接口的全路径 -->
        <property name="proxyInterfaces" value="spring3.aop.demo3.CustomerDao" />
        <!-- 需要使用value: 需要实现增强代码类的名称 -->
        <property name="interceptorNames" value="beforeAdvice" />
        <!-- 强制使用CGLIB代理 -->
        <property name="optimize" value="true" />
    </bean>
</beans>

SpringTest3

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest3 {
    
    
    @Autowired
    // @Qualifier("customerDao") // 需要注入真实的对象,必须注入代理对象
    @Qualifier("customerDaoProxy")
    private CustomerDao customerDao;
    @Test
    public void demo1() {
    
    
        customerDao.add();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}

Depuración de puntos de interrupción para ver el tipo de CustomerDao

imagen-20220906002541767

Se encuentra que aunque se implementa la interfaz, el objeto de proxy dinámico generado por CGLIB es porque el atributo de optimización se especifica como verdadero al definir las propiedades de ProxyFactoryBean en XML, es decir, se obliga a generar el objeto de proxy dinámico usando CGLIB. Si no se establece, Spring elegirá automáticamente la forma de generar objetos proxy dinámicos. Elimine este atributo y la vista de depuración de puntos de interrupción es la siguiente:

imagen-20220906002554748

Debido a que los objetos proxy se generan para las interfaces, Spring dará prioridad al uso de JDK para generar objetos proxy dinámicos.

Nota: Debido a la versión de JDK, el agente dinámico de JDK informará un error al usar JDK1.5. Al usar JDK1.8, aunque se puede usar el proxy dinámico JDK, @RunWith y @ContextConfiguration generarán una excepción de parámetro ilegal, por lo que es mejor elegir JDK1.7

PointcutAdvisor pointcut y aspect case: notificación circundante

Para un Advisor sin punto de corte, su propia mejora de Advice es un aspecto que mejora todos los métodos de la clase objetivo por defecto, por lo tanto, en el caso anterior, el atributo interceptorNames configurado con el nombre de aspecto pointcut es el Advice configurado por sí mismo. El llamado aspecto con un punto de corte es para mejorar ciertos métodos especificados en la clase de destino. Es precisamente porque el uso de Advice ordinario como un aspecto interceptará todos los métodos de la clase objetivo, que no es lo suficientemente flexible, por lo que en el desarrollo real, a menudo se usa un aspecto con un punto de corte.

Las clases de implementación de PointcutAdvisor comúnmente utilizadas son las siguientes:

  • DefaultPointcutAdvisor es el tipo de faceta más utilizado, que puede definir una faceta a través de cualquier combinación de Pointcut y Advice.
  • RegexpMethodPointcutAdvisor Construye una expresión regular para definir un aspecto de punto de corte.

El código de ejemplo es el siguiente:

OrderDao (clase de destino de proxy)

/**
 * 目标对象
 *
 */
public class OrderDao {
    
    
    public void add() {
    
    
        System.out.println("添加订单");
    }
    public void update() {
    
    
        System.out.println("修改订单");
    }
    public void delete() {
    
    
        System.out.println("删除订单");
    }
    public void find() {
    
    
        System.out.println("查询订单");
    }
}

MyAroundAdvice (clase de mejora de notificación de consejos)

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
 * 增强的类
 * 使用的是环绕增强
 * 注意:对于增强类型的接口可以使用AOP联盟的也可以使用Spring扩展后自带的
 */
public class MyAroundAdvice implements MethodInterceptor {
    
    
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    
    
        System.out.println("环绕前增强...");
        Object result=methodInvocation.proceed(); // 指定目标对象的方法
        System.out.println("环绕后增强...");
        return result;
    }
}

applicationContext.xml (para configuración)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 带有切点的切面 -->
    <!-- 定义目标对象 -->
    <bean id="orderDao" class="spring3.aop.demo4.OrderDao" />
    <!-- 定义增强 -->
    <bean id="aroundAdvice" class="spring3.aop.demo4.MyAroundAdvice" />
    <!-- 定义切点切面 -->
    <bean id="myPointcutAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!-- 定义表达式,规定哪些方法执行拦截 -->
        <!-- . 任意字符 * 任意个 .* 任意个任意字符 -->
        <!-- 对所有方法进行拦截 -->
        <!-- <property name="pattern" value=".*"/> -->
        <!-- 拦截指定类中以add开头的所有方法 -->
        <!-- <property name="pattern" value="spring3\.aop\.demo4\.OrderDao\.add.*"/> -->
        <!-- 拦截方法名包含add的方法 -->
        <!-- <property name="pattern" value=".*add.*"/> -->
        <!-- 多种格式的拦截:拦截方法名中包含add或find的方法 -->
        <property name="patterns" value=".*add.*,.*find.*" />
        <!-- 应用增强 -->
        <property name="advice" ref="aroundAdvice" />
    </bean>
    <!-- 定义生成代理对象 -->
    <bean id="orderDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 配置目标 -->
        <property name="target" ref="orderDao" />
        <!-- 针对类代理 -->
        <property name="proxyTargetClass" value="true" />
        <!-- 在目标应用上增强 -->
        <property name="interceptorNames" value="myPointcutAdvisor" />
    </bean>
</beans>

El resultado de la operación es el siguiente:

proxy automático

En el caso anterior, cada proxy está entretejido en el proxy de aspecto a través de ProxyFactoryBean. En el desarrollo real, hay muchos beans. Si cada proxy está configurado con ProxyFactoryBean, causará una gran cantidad de desarrollo y mantenimiento.

Solución: proxy automático (basado en beans de posprocesamiento, mejoras realizadas durante la creación del bean, los beans generados son proxies)

Hay varias formas de implementar un proxy automático:

  • BeanAutoProxyCreator crea un proxy basado en el nombre del Bean.
  • DefaultAdvisorAutoProxyCreator crea proxies basados ​​en la información contenida en el propio Advisor.
  • AnnotionAwareAspectJAutoProxyCreator realiza un proxy automático basado en las anotaciones de AspectJ en beans.

BeanNameAutoProxyCreator: Genera un proxy por nombre.

El código de ejemplo es el siguiente:

applicationContext2.xml configuración automática de proxy

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 定义目标对象 -->
    <bean id="customerDao" class="spring3.aop.demo3.CustomerDaoImpl" />
    <bean id="orderDao" class="spring3.aop.demo4.OrderDao" />
    <!-- 定义增强 -->
    <bean id="beforeAdvice" class="spring3.aop.demo3.MyBeforeAdvice" />
    <bean id="aroundAdvice" class="spring3.aop.demo4.MyAroundAdvice" />
    <!-- 自动代理:按照Bean名称的代理 基于后处理Bean,后处理Bean不需要配置ID -->
    <bean
        class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <!-- 对bean名称以Dao结尾的进行自动代理 -->
        <property name="beanNames" value="*Dao" />
        <!-- 定义通知类型 也可以指定多个 -->
        <property name="interceptorNames" value="beforeAdvice" />
    </bean>
</beans>

SpringTest5 nueva clase de prueba

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import spring3.aop.demo3.CustomerDao;
import spring3.aop.demo4.OrderDao;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringTest5 {
    
    
    @Autowired
    @Qualifier("orderDao")
    private OrderDao OrderDao;
    @Autowired
    @Qualifier("customerDao")
    private CustomerDao CustomerDao;
    @Test
    public void demo1() {
    
    
        OrderDao.add();
        OrderDao.delete();
        CustomerDao.update();
    }
}

DefaultAdvisorAutoProxyCreator: Genera un proxy basado en la información definida en el aspecto

applicationContext3.xml configura el proxy automático en función de la información de aspecto

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 定义目标对象 -->
    <bean id="customerDao" class="spring3.aop.demo3.CustomerDaoImpl" />
    <bean id="orderDao" class="spring3.aop.demo4.OrderDao" />
    <!-- 定义增强 -->
    <bean id="beforeAdvice" class="spring3.aop.demo3.MyBeforeAdvice" />
    <bean id="aroundAdvice" class="spring3.aop.demo4.MyAroundAdvice" />
    <!-- 定义一个带有切点的切面 -->
    <bean id="mypointcutAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="pattern" value=".*add.*" />
        <property name="advice" ref="aroundAdvice" />
    </bean>
    <!-- 根据切面信息自动生成代理 -->
    <bean
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
</beans>

Clase de prueba SpringTest6

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import spring3.aop.demo3.CustomerDao;
import spring3.aop.demo4.OrderDao;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class SpringTest6 {
    
    
    @Autowired
    @Qualifier("orderDao")
    private OrderDao orderDao;
    @Autowired
    @Qualifier("customerDao")
    private CustomerDao customerDao;
    @Test
    public void demo1() {
    
    
        orderDao.add();
        orderDao.update();
        orderDao.delete();
        customerDao.add();
    }
}

Nota: Distinguir entre proxy basado en ProxyFactoryBean y proxy automático

ProxyFactoryBean primero debe tener un objeto proxy, y el objeto proxy se pasa a la clase proxy para generar un proxy, mientras que el proxy automático se basa en el Bean de posprocesamiento. Durante el proceso de generación del bean, se genera un objeto proxy y el se devuelve el objeto proxy. es el objeto proxy.

AOP (*****) de AspectJ en primavera

AspectJ es un marco orientado a aspectos que amplía el lenguaje Java. AspectJ define la gramática AOP, por lo que tiene un compilador especial que se usa para cumplir con el archivo Class de la especificación de codificación de bytes de Java.

AspectJ es un marco AOP basado en el lenguaje Java.

Después de Spring 2.0, se agregó compatibilidad con las expresiones de corte de puntos de AspectJ.

@AspectJ es una nueva característica de AspectJ1.5 que, mediante la tecnología de anotación JDK5, permite definir aspectos directamente en la clase Bean.

En la nueva versión del framework Spring, se recomienda utilizar el método AspectJ para desarrollar AOP.

Para usar AspectJ, debe importar paquetes jar relacionados con Spring AOP y AspectJ.

  • spring-aop-3.2.0.RELEASE.jar
  • com.springsource.org.aopalliance-1.0.0.jar
  • primavera-aspectos-3.2.0.RELEASE.jar
  • com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

Habilite @AspectJ a través de la configuración: es decir, introduzca restricciones AOP en el archivo de configuración (también busque xsd-config.xml) y péguelo: luego habilite el proxy automático de AspectJ

imagen-20220906005155247

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 开启AspectJ自动代理-->    
    <aop:aspectj-autoproxy />
</beans>

Expresiones de AspectJ

Defina el punto de corte a través del atributo de valor en el consejo

A través de la función de ejecución, puede definir el método de corte de puntos.

Sintaxis: ejecución (<modificador de acceso>?<tipo de retorno><nombre de método>(parámetro)<excepción>) ¿dónde? Indica que se puede omitir.

Por ejemplo:

  • Haga coincidir todos los métodos de ejecución de todas las clases (público * * (...)) ... cualquier número de parámetros de cualquier tipo
  • Haga coincidir todos los métodos de todas las clases bajo la ejecución del paquete especificado (* aspectj.test.dao.*(…)) excluyendo los subpaquetes
  • ejecución(* aspectj.test.dao… (…)) … representa todas las clases bajo el paquete actual y sus descendientes
  • Hacer coincidir la ejecución de todos los métodos (* aspectj.test.service.UserService.*(…)) de la clase especificada
  • Coincide con todos los métodos de clase que implementan una ejecución de interfaz específica (* aspectj.test.dao.GenericDao+.*(…))
  • Coincide con la ejecución de todos los métodos de guardado(* save*(…))

Implementación basada en anotaciones de AspectJ

Tipos de consejos para AspectJ, @AspectJ proporciona diferentes tipos de consejos

  • @Before pre-advice, equivalente a BeforeAdvice
  • Notificación de publicación @AfterReturning, equivalente a AfterReturningAdvice
  • @Alrededor de consejos, equivalente a MethodInterceptor
  • @AfterThrowing lanza notificaciones, equivalente a ThrowAdvice
  • @Después de la notificación final final, independientemente de si hay una excepción, la notificación se ejecutará
  • Notificación de introducción de @DeclareParents, equivalente a IntroductionInterceptor (rara vez se usa)

@Antes de la notificación previa, puede pasar el objeto JointPoint en el método correspondiente para obtener la información de corte de puntos.

@AfterReturning Además de pasar el objeto JointPoint en el método, la notificación posterior también puede definir el valor de retorno del método como un parámetro a través del atributo de retorno. El tipo de parámetro generalmente es Object

@Alrededor de notificación envolvente, el valor de retorno del método around es el valor de retorno de la ejecución del método de proxy de destino. El parámetro es ProceedingJoinPoint, que puede llamar al método de destino de intercepción para ejecutar. Preste especial atención: si el método de proceder de ProceedingJoinPoint no se llama, el método de destino será interceptado.

@AfterThrowing Notificación de lanzamiento Al configurar el atributo de lanzamiento, puede configurar el parámetro de objeto de excepción Throwable

Nombre el pointcut por @Pointcut

  • Definir puntos de corte en cada notificación resultará en una gran carga de trabajo y no es fácil de mantener. Para puntos de corte repetidos, puede usar @Pointcut para definirlos.
  • Método Pointcut: método anulado privado sin parámetros, el nombre del método es el nombre del punto de corte.
  • Cuando aconseje varios puntos de corte, puede usar || para conectar

El nuevo proyecto primero importa varios paquetes necesarios en Spring, bean, context, core, expression, log related commons.loggoing, log4j, luego importa paquetes Spring AOP (aop, paquetes de alianza aop (paquetes dependientes)) y finalmente importa paquetes Aspect y paquetes dependientes.

Cree un nuevo archivo de configuración, introduzca restricciones de AOP, configure el proxy automático y, finalmente, codifique para lograrlo.

El código de ejemplo es el siguiente:

Escribe la clase mejorada UserDao

public class UserDao {
    
    
    public void add(){
    
    
        System.out.println("添加用户");
    }
    public int update(){
    
    
        System.out.println("修改用户");
        return 1;
    }
    public void delete(){
    
    
        System.out.println("删除用户");
        int d = 1/ 0;
    }
    public void find(){
    
    
        System.out.println("查询用户");
    }
}

La anotación MyAspect implementa el aspecto de AspectJ

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
 * 
 * 切面类:就是切点和增强的组合
 *
 */
@Aspect
public class MyAspect {
    
    
    @Before("execution(* aop.aspectj.demo1.UserDao.add(..))")
    public void before(JoinPoint joinPoint) {
    
    
        System.out.println("前置增强..." + joinPoint);
    }
    @AfterReturning(value = "execution(* aop.aspectj.demo1.UserDao.update(..))", returning = "returnVal")
    public void afterReturning(Object returnVal) {
    
    
        System.out.println("后置增强...方法的返回值" + returnVal);
    }
    @AfterThrowing(value = "MyAspect.myPointcut1()", throwing = "e")
    public void afterThrowing(Throwable e) {
    
    
        System.out.println("警告:出现异常了!!! " + e.getMessage());
    }
    @After("MyAspect.myPointcut1()||MyAspect.myPointcut2()")
    public void after(){
    
    
        System.out.println("最终通知...");
    }
    @Pointcut("execution(* aop.aspectj.demo1.UserDao.delete(..))")
    public void myPointcut1() {
    
    
    }
    @Pointcut("execution(* aop.aspectj.demo1.UserDao.find(..))")
    public void myPointcut2() {
    
    
    }
}

applicationContext.xml introduce restricciones AOP y configura proxy automático

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 自动生成代理 底层就是AnnotationAwareAspectJAutoProxyCreator -->
    <aop:aspectj-autoproxy />
    <bean id="userDao" class="aop.aspectj.demo1.UserDao" />
    <bean id="myAspect" class="aop.aspectj.demo1.MyAspect" />
</beans>

Escribir una clase de prueba SpringTest1

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest1 {
    
    
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao;
    @Test
    public void demo1() {
    
    
        userDao.add();
        userDao.update();
        userDao.find();
        userDao.delete();
    }
}

Implementación de AspectJ basada en XML

ProductoDao

public class ProductDao {
    
    
    public int add() {
    
    
        System.out.println("添加商品...");
        int d = 10 / 0;
        return 100;
    }
    public void update() {
    
    
        System.out.println("修改商品...");
    }
    public void delete() {
    
    
        System.out.println("删除商品...");
    }
    public void find() {
    
    
        System.out.println("查询商品...");
    }
}

Clase de aspecto MyAspectXML

import org.aspectj.lang.ProceedingJoinPoint;
/**
 * 切面类
 */
public class MyAspectXML {
    
    
    public void before() {
    
    
        System.out.println("前置通知...");
    }
    public void afterReturning(Object returnVal) {
    
    
        System.out.println("后置通知...返回值:" + returnVal);
    }
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    
    
        System.out.println("环绕前增强...");
        Object result = proceedingJoinPoint.proceed();
        System.out.println("环绕后增强...");
        return result;
    }
    public void afterThrowing(Throwable e) {
    
    
        System.out.println("异常通知..." + e.getMessage());
    }
    public void after() {
    
    
        System.out.println("最终通知...");
    }
}

clase de aspecto de configuración applicationContext2.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 定义被增强的类 -->
    <bean id="productDao" class="aop.aspectj.demo2.ProductDao" />
    <!-- 定义切面 -->
    <bean id="myAspectXML" class="aop.aspectj.demo2.MyAspectXML" />
    <!-- 定义AOP配置 -->
    <aop:config>
        <!-- 定义切点 -->
        <aop:pointcut expression="execution(* aop.aspectj.demo2.ProductDao.add(..))" id="myPointcut"/>
        <aop:pointcut expression="execution(* aop.aspectj.demo2.ProductDao.delete(..))" id="myPointcut2"/>
        <!-- 配置Aspect增强类 -->
        <aop:aspect ref="myAspectXML">
        <!-- 前置通知 -->
        <aop:before method="before" pointcut="execution(* aop.aspectj.demo2.ProductDao.update(..))" />
        <!-- 后置通知 -->
        <aop:after-returning method="afterReturning" pointcut-ref="myPointcut" returning="returnVal"/>
        <!-- 环绕通知 -->
        <aop:around method="around" pointcut-ref="myPointcut2"/>
        <!-- 异常通知 -->
        <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="e"/>
        <!-- 最终通知 -->
        <aop:after method="after" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

Clase de prueba SpringTest2

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringTest2 {
    
    
    @Autowired
    @Qualifier("productDao")
    private ProductDao productDao;
    @Test
    public void demo1() {
    
    
        productDao.update();
        productDao.delete();
        productDao.find();
        productDao.add();
    }
}

¿Cuál es la diferencia entre Spring AOP y AspectJ AOP?

Spring AOP es una mejora del tiempo de ejecución, mientras que AspectJ es una mejora del tiempo de compilación. Spring AOP se basa en el uso de proxy (Proxying), mientras que AspectJ se basa en la manipulación de código de bytes (Manipulación de código de bytes).

Spring AOP ha integrado AspectJ, y AspectJ debe considerarse como el marco AOP más completo en el ecosistema de Java. AspectJ es más poderoso que Spring AOP, pero Spring AOP es relativamente más simple,

Si tenemos menos aspectos, entonces la diferencia de rendimiento entre los dos no es grande. Sin embargo, cuando hay demasiados aspectos, lo mejor es elegir AspectJ, que es mucho más rápido que Spring AOP.

Supongo que te gusta

Origin blog.csdn.net/m0_61820867/article/details/126927401
Recomendado
Clasificación