java 代理模式[Proxy]--AOP

java 代理模式[Proxy]--AOP,第1张

一直都想解读一下关于SpringAop源码的部分,但是要解析看懂SpringAop的前置基础是得需要了解Spring运行的大致流程和设计模式的代理模式和Aop的原理思想

那么今天咱们就先来了解一下代理模式和Aop的基本概念吧,后续再来解读SpringAOP的原理吧

一、代理模式

代理类和被代理类实现同一个接口

在使用时通过代理类的对象来调用被代理类的具体方法即可,相当于在被代理类外层又包装了一层

比如:一个Tank(坦克)的实体类 实现了Movable可移动的接口

再创建一个TankProxy坦克代理类,同样去实现Movable可移动的接口

在使用的时候就只通过TankProxy这个类来间接调用Tank类实例化出来的对象相当于是通过TankProxy代理来来代为处理Tank类要做的事情

如图:

代码如下:

// 创建一个 坦克类,实现可移动的接口
public class Tank implements Movable {
    @Override
    public void move() {
        System.out.println("tank move ....");
    }
}
public class TankProxy implements Movable {
    private Tank tank;
    public TankProxy(Tank tank) {
        this.tank = tank;
    }
    @Override
    public void move() {
        System.out.println("代理类 do something before tank move..");
        tank.move();
        System.out.println("代理类 do something after tank move..");
    }
}
public class ProxyMainTest {
    // 测试
    public static void main(String[] args) {
        TankProxy proxy = new TankProxy(new Tank());
        proxy.move();
    }
}
// 输出结果:
  代理类 do something before tank move..
  tank moving ....
  代理类 do something after tank move..

以上就为简单的一个代理模式,也可称为静态代理

静态代理: 旨在给一个目标类(如:Tank)扩展功能的时候,使用一个代理类(TankProxy)来实现扩展增强的那部分功能,在不改变原先那个被代理类的前提下实现对原有功能的扩展。

代理类在java代码里进行的扩展增强,是在程序运行之前就已经存在的,而动态代理则是在程序运行的过程中创建的代理类

动态代理:

动态代理是程序在运行的过程中去创建代理类,代理类的代码不是现有的,而是在程序运行的过程中动态创建的对应的代理类的字节码文件($Proxy开头的class文件)

动态代理的两种方式有:JDK的动态代理和CGlib方式的动态代理

结合代码如下:

JDK动态代理

动态代理的代理类需要实现java.lang.reflect.InvocationHandler接口,重写invoke() 方法来增强扩展

/**
 * @author xiaolong.ge
 * @since 13 五月 2022
 */
 public class Tank implements Movable {
    @Override
    public void move() {
        System.out.println("tank move ....");
    }
}
public class JdkDynamicTankProxy implements InvocationHandler {
//    Object obj;
    Tank tank;
    public JdkDynamicTankProxy(Tank tank) {
        this.tank = tank;
    }
    /**
     * @param proxy 代理类的对象
     * @param method 被代理类中要执行的方法
     * @param args 方法传入的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeMove();
        // 调用 tank的 move方法
        Object o = method.invoke(tank,args);
        afterMove();
        return o;
    }
    
    public void beforeMove() {
        System.out.println("before tank move");
    }
    public void afterMove() {
        System.out.println("after tank move");
    }
}

public class ProxyMainTest {
    public static void main(String[] args) {
        // 设置 保留创建的代理类的字节码文件 
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        JdkDynamicTankProxy proxyEnhance = new JdkDynamicTankProxy(new Tank());
        // 生成 tank 代理类 $Proxy0 的代理对象 
        // m 就是 $Proxy0 类的实例对象 
        Movable m = (Movable) Proxy.newProxyInstance(Tank.class.getClassLoader(),new Class[]{Movable.class},proxyEnhance);
       
        m.move();  // 此时的 move方法 就是调用的 $Proxy0 类中的 move方法
        // 最终会调用到 JdkDynamicTankProxy的 invoke方法
    }
}
// 执行结果
  before tank move
  tank moving ....
  after tank move

生成的代理类的字节码文件$Proxy0.class

反编译为java代码如下:

// 生成的代理类的代码 --反编译为java代码如下:
public final class $Proxy0 extends Proxy implements Movable {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    
    public final void move() throws  {
        try {
            // 此处的属性 h ---> 对应的是 JdkDynamicTankProxy的实例 proxy
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.gexl.designpattern.proxy.Movable").getMethod("move");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

jdk的动态代理模式,被代理的类是必须要实现某个接口 如:Tank 必须要实现Movable接口

而CGLIB实现动态代理,被代理的类则不需要某个接口

CGlib动态代理:Code Generation Library

引入jar包

 <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
import net.sf.cglib.proxy.MethodInterceptor;

public class MoveMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before move ...");
        Object result = methodProxy.invokeSuper(o,objects);
        System.out.println("after move ...");
        return result;
    }
}
public class Tank {
     public void move() {
        System.out.println("tank moving ....");
     }
}
--------------------------------------------------------
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
 * @author xiaolong.ge
 * @since 13 五月 2022
 */
public class CGlibTankProxy {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Tank.class);
        enhancer.setCallback(new MoveMethodInterceptor());
        Tank tank = (Tank)enhancer.create();
        tank.move();
    }
}
// 执行结果:
  before move ...
  tank moving ....
  after move ...

CGLib 实现动态代理的原理是,创建一个被代理类的子类来作为代理类,最后调用实现了MethodInterceptor 类的intercept方法来做增强

二、Aop-面向切面编程 Aspect-oriented programming

面向切面编程,即在正常的代码执行流程下,横向织入所需要执行的代码,

如图:

Aop的织入方式根据代理方式的不同,分为静态织入和动态织入 分别对应的静态代理在源码上织入和动态代理时在生成的代理类织入

织入的点就叫做切入点 PointCut

存放切入代码逻辑的类 就叫做 切面-Aspect (切面类)

Before 或者 After 即切入的时机 则统称为通知Advice

通知还有 After-returning: 在目标方法(被代理的对象的方法)成功执行返回之后调用

After-throwing: 在目标方法抛出异常后调用通知

Around: 包裹了被通知的方法,在被通知的方法调用之前和之后执行自定义的代码

执行顺序:
1 : @Around环绕通知
2 : @Before通知执行
3 : @Before通知执行结束
4 : @Around环绕通知执行结束
5 : @After后置通知执行了!
6 : @AfterReturning第一个后置返回通知执行

需要引入 对应的aop 的jar包:aspectjweaver-1.8.9.jar

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

代码如下:


import com.gexl.service.LoadFileService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
 * @author xiaolong.ge
 * @since 13 五月 2022
 */
public class TestAspect {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(App.class);
        LoadFileService loadFileService = (LoadFileService) ac.getBean("loadFileService");
        // 此处获取的 loadFileService 为spring通过cglib 创建的代理对象
        loadFileService.loadFile("D:\temp\log");
    }
}
// 执行结果如下:
加载文件方法执行之前。。
加载文件方法。。。
加载文件方法执行之后。。

----------------------------------------------
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @author xiaolong.ge
 * @since 13 五月 2022
 */
@Aspect
@Component
public class MyAspect {
     // 切面类, 配置前置通知  此时的切入点 就是 LoadFileService.loadFile()方法
    @Before("execution(void com.gexl.service.LoadFileService.loadFile(String))")
    public void beforeLoadFile() {
        System.out.println("加载文件方法执行之前。。");
    }
    @After("execution(void com.gexl.service.LoadFileService.loadFile(..))")
    public void afterLoadFile() {
        System.out.println("加载文件方法执行之后。。");
    }
}
------------------------------------------
@Service
public class LoadFileService {
    public void loadFile(String fileName){
        System.out.println("加载文件方法。。。");
    }
}

需要注意的点:

  1. 需要注意切入的表达式的配置,一定要正确,我就配置错误了,找了一下午才找到问题,execution(方法修饰符 返回类型 方法全限定名(参数))

我在MyAspect.beforeFileLoad() 方法上一开始配置的是void execution(com.gexl.service.LoadFileService.loadFile());

没有给方法配置参数 … 所以导致一直没有匹配到要代理的方法,没有创建代理对象,一直没有调用不到beforeLoadFile() 方法, 就少了两个点

execution(com.gexl.service.LoadFileService.loadFile(…))

2.表达式
execution(public * com.gexl.controller..(…))

* 只能匹配一级路径
.. 可匹配多级,可以是包路径,也可以匹配多个参数
+ 只能放在类后面,表明本类及所有子类

还可以指定开头 如:匹配所有get开头的,第一个参数为Long 类型的方法

@Pointcut(“execution(* …get(Long, …))”)

@annotation(annotationType) 匹配带有指定注解的方法

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/924090.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存