Del modo proxy al principio Spring AOP

Del modo proxy al principio Spring AOP

(1. Introducción

El núcleo de Spring es IOC y AOP. IOC es principalmente la gestión de dependencias, incluida la consulta de dependencia y la inyección de dependencia. En el artículo anterior sobre beans, he realizado una cantidad relativamente grande de análisis sobre el ciclo de vida de los beans y tengo un conocimiento básico de los principios de IOC. Aquí discutiremos el principio de realización de AOP.

AOP (Programación Orientada a Aspectos), también conocida como programación orientada a aspectos. La tecnología AOP utiliza una técnica llamada "transversal" para diseccionar el interior del objeto encapsulado y encapsular esos comportamientos comunes que afectan a múltiples clases en un módulo reutilizable, lo que puede reducir la duplicación del código del sistema y reducir el inter-módulo. El grado de acoplamiento es propicio para la operatividad y el mantenimiento futuras.

AOP divide el sistema de software en dos partes: preocupaciones centrales y preocupaciones transversales. El proceso principal de procesamiento empresarial es la preocupación central, y la parte menos relevante es la preocupación transversal. Una característica de las preocupaciones transversales es que a menudo ocurren en múltiples lugares de la preocupación principal y son básicamente similares en todas partes, como la autenticación de permisos, el registro, el procesamiento de transacciones y otras funciones.

Estos conceptos parecen difíciles de entender y hay demasiados términos técnicos. De hecho, la primera vez que lo supe, no tenía ni idea de qué estaba hablando. Así que todavía somos las viejas reglas, no mire estos conceptos primero e implemente un módulo AOP simple para ayudarnos a entender. Las tecnologías para implementar AOP se dividen principalmente en dos categorías:

Usando tecnología de proxy dinámico, escriba una interfaz de clase de proxy, implemente una clase de proxy real, implemente la interfaz, cree una clase de proxy dinámica, implemente la interfaz InvocationHandler, reescriba el método invoke () y llame al método Proxy.newProxyInstance () para generar Objetos de proxy dinámico.

Usando tecnología de proxy estático, personalice una clase de proxy (clase mejorada) para implementar la misma interfaz que la clase de proxy (clase mejorada), declare el objeto de la clase de proxy en la clase de proxy y use la clase de proxy para llamar al método de clase de proxy método.

Inserte la descripción de la imagen aquí
Esta imagen puede ayudarlo a comprender la principal diferencia entre el proxy estático y el proxy dinámico. Hablaré sobre los detalles específicos en el siguiente ejemplo.

(Dos) versión proxy estática de AOP

Primero, necesitamos definir la interfaz de nuestra clase empresarial principal.

public interface IHello {
    
    
    void sayHello(String str);
}

Luego, cree nuestra clase de destino, que es la clase principal.

public class Hello implements IHello{
    
    
    @Override
    public void sayHello(String str) {
    
    
        System.out.println("hello " + str);
    }
}

Luego viene la clase de proxy. La clase proxy es realmente muy fácil de entender, es decir, una clase llama a un método de la clase objetivo, que es equivalente a una clase que hace cosas para la clase objetivo. Además de los métodos de la clase de destino, la clase de proxy también hará algún otro trabajo, por ejemplo, aquí podemos dejarle escribir registros.

public class ProxyHello implements IHello{
    
        
    private Hello hello;
        
    public ProxyHello() {
    
    
        super();
        this.hello = new Hello();
    }
    
    @Override
    public void sayHello(String str) {
    
    
        Logger.start();  //添加特定的方法
        hello.sayHello(str);  //调用目标类方法
        Logger.end();  //添加特定的方法
    }
}

La clase de llamada utilizada para escribir registros se llamará en la clase de proxy.

public class Logger {
    
    
    public static void start(){
    
    
        System.out.println(new Date()+ " say hello start");
    }
    
    public static void end(){
    
    
        System.out.println(new Date()+ " say hello end");
    }
}

Pruébelo. Cuando se necesita la función de registro, podemos usar la clase de proxy. Cuando no se necesita la función de registro, podemos usar directamente la clase de destino.

public class Test {
    
    
    public static void main(String[] args) {
    
    
        IHello hello = new ProxyHello();  //需要日志功能,使用代理类
        //IHello hello = new Hello();  //不需要日志功能,直接使用目标类
        hello.sayHello("tomorrow");
    }
}

De esta manera, hemos logrado el AOP más simple. Pero hay un problema con la implementación del proxy estático: si tenemos muchas clases como Hello, entonces tenemos que escribir muchas clases como HelloProxy. Obviamente, esto es algo problemático y no es propicio para un mantenimiento posterior. Luego pensaremos en formas de mejorar, y esto conducirá a la realización de agentes dinámicos.

(3) Versión de proxy dinámico de AOP

Veamos primero una implementación de proxy dinámico simple basada en JDK Proxy. En primer lugar, seguimos escribiendo nuestra interfaz comercial principal y la clase de destino en primer lugar.

public interface IHello {
    
    
    void sayHello(String str);
}
public class Hello implements IHello{
    
    
    @Override
    public void sayHello(String str) {
    
    
        System.out.println("hello " + str);
    }
}

Preste atención a la interfaz de registro, el parámetro que pasamos es un objeto de método

public interface ILogger {
    
    
	void start(Method method);
	void end(Method method);
}

La clase de implementación del registro no es la misma que antes, aquí podemos obtener manualmente el nombre del método, de modo que el nombre del método llamado se pueda registrar en el registro.

public class DLogger implements ILogger{
    
    
    @Override
    public void start(Method method) {
    
    
        System.out.println(new Date() + method.getName() + " say hello start");
    }
    
    @Override
    public void end(Method method) {
    
    
        System.out.println(new Date() + method.getName() + " say hello end");
    }
}

Echemos un vistazo a esta clase DynaProxyHello, es fácil encontrar que es la clase de implementación de la interfaz InvocationHandler. En primer lugar, notaremos dos atributos, uno es proxy y el otro es destino, que representan respectivamente nuestro objeto de llamada (en este caso, el módulo de registro) y el objeto de destino. Entre ellos, el método bind llama al método newProxyInstance, que genera una nueva clase java a través de la cadena, y luego compila dinámicamente el objeto devuelto.Tenga en cuenta que es una compilación dinámica. En pocas palabras, simula y escribe dinámicamente una clase java a través de algunos parámetros pasados ​​y devuelve el objeto al usuario después de crear una instancia del objeto.Este objeto es nuestro objeto proxy.

En el método invoke, el método log y el método core se llaman mediante reflexión. Podemos ver start.invoke, method.invoke, end.invoke. Debido a que se usa la reflexión, puede parecer más complicado, pero de hecho la función no es la misma que la del proxy estático anterior. No hay diferencia esencial. Cuando el objeto de clase de proxy que creamos realiza una llamada a un método, se transformará en una llamada al método de invocación.

public class DynaProxyHello implements InvocationHandler{
    
    
    private Object proxy;  //调用对象
    private Object target;  //目标对象

    public Object bind(Object target,Object proxy){
    
    
        this.target = target;
        this.proxy = proxy;
        //生成代理对象
        return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
    
    
        Object result = null;
        //反射得到调用类
        Class clazz = this.proxy.getClass();
        //反射得到调用类的Start方法
        Method start = clazz.getDeclaredMethod("start", new Class[]{
    
    Method.class});
        //反射执行start方法
        start.invoke(this.proxy, new Object[]{
    
    method});
        //执行目标类的核心方法
        method.invoke(this.target, args);
        //反射得到调用类的end方法
        Method end = clazz.getDeclaredMethod("end", new Class[]{
    
    Method.class});
        //反射执行end方法
        end.invoke(this.proxy, new Object[]{
    
    method});
        return result;
    }
}

Probémoslo a continuación.

public class Test {
    
    
    public static void main(String[] args) {
    
    
        IHello hello = (IHello) new DynaProxyHello().bind(new Hello(), new DLogger());  //需要日志功能,使用代理类
        //IHello hello = new Hello();  //不需要日志功能,直接使用目标类
        hello.sayHello("tomorrow");
    }
}

Podemos encontrar que el proxy dinámico de la versión JDK Proxy tiene un problema, es decir, solo puede proxy para la interfaz (la primera línea de la función principal de la clase de prueba IHello hello = (IHello) new DynaProxyHello (). Bind (new Hello (), new DLogger ( )); Debe basarse en la interfaz IHello), si queremos proxy de un objeto común, este método no se puede utilizar. Entonces tenemos que encontrar otra forma.

CGLIB, una herramienta de proxy que es más poderosa que el proxy dinámico de JDK Proxy, usa ASM (un marco de manipulación de código de bytes corto y poderoso) en la parte inferior para manipular el código de bytes para generar nuevas clases, que es más eficiente que usar la reflexión. Puede realizar el proxy de objetos ordinarios (sin interfaz) y el código es más compacto y fácil de leer A continuación usaremos CGLIB para lograr la misma función que el anterior.

Primero, necesitamos importar el paquete cglib, estoy usando la última versión 3.3.0.

<dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
</dependency>

Luego, escriba la interfaz y la clase de implementación del módulo de registro que escribió antes.

public interface ILogger {
    
    
    void start(Method method);
    void end(Method method);
}
public class DLogger implements ILogger {
    
    
    @Override
    public void start(Method method) {
    
    
        System.out.println(new Date() + method.getName() + " say hello start");
    }

    @Override
    public void end(Method method) {
    
    
        System.out.println(new Date() + method.getName() + " say hello end");
    }
}

Luego están nuestras clases de negocios principales.

public class Hello {
    
    
    public void sayHello(String str) {
    
    
        System.out.println("hello " + str);
    }
}

A continuación, está el interceptor más crítico, que es el más diferente del proxy dinámico del JDK anterior. Esta clase necesita implementar la interfaz MethodInterceptor. De hecho, la función implementada es básicamente la misma que el método de invocación anterior. Aquí, primero podemos entenderlo como el método de invocación.

public class DynaProxyHello implements MethodInterceptor {
    
    
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
        DLogger dLogger = new DLogger();
        dLogger.start(method);
        Object object = methodProxy.invokeSuper(o, objects);
        dLogger.end(method);
        return object;
    }
}

Luego está la clase de prueba, esta clase obviamente es más complicada, echemos un vistazo a lo que hizo. Primero, configure la clase de destino Hello como clase principal, luego configure el interceptor DynaProxyHello y, finalmente, ejecute enhancer.create () para generar dinámicamente una clase de proxy y convertirla de Object a la clase principal Hello. Entre ellos, la clase Enhancer es un mejorador de código de bytes en CGLib, que puede extender fácilmente la clase que desea manejar. No necesitamos comprender este detalle por el momento. Los amigos que quieran comprender pueden buscar en Google por sí mismos.

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Enhancer enhancer = new Enhancer();
        //继承目标类
        enhancer.setSuperclass(Hello.class);
        //设置回调
        enhancer.setCallback(new DynaProxyHello());
        //生成代理对象
        Hello hello = (Hello) enhancer.create();
        hello.sayHello("tomorrow");
    }
}

Un agente tan dinámico basado en CGLib está terminado Personalmente creo que este proceso es un poco más simple que el de un agente dinámico basado en JDK, pero involucra más puntos de conocimiento y requiere un pensamiento cuidadoso. Podemos ver la diferencia entre la implementación AOP de los dos modos de proxy dinámico que acabamos de mencionar:

Inserte la descripción de la imagen aquí

Para no venderlo, de hecho Spring AOP se basa en proxy dinámico. Si el objeto que se va a utilizar como proxy implementa una interfaz, Spring AOP utilizará JDK Proxy para crear el objeto proxy. Para los objetos que no implementan la interfaz, JDK Proxy no se puede usar como proxy. En este momento, Spring AOP usará CGlib para generar una subclase del objeto proxy como proxy. Por supuesto, también podemos configurar para forzar el uso de CGlib.

23 de julio de 2020

Supongo que te gusta

Origin blog.csdn.net/weixin_43907422/article/details/107494485
Recomendado
Clasificación