代理模式:静态代理与动态代理JDK、CGLIB、javassist动态代理

参考资料


一,代理模式​

  代理模式是23种设计模式中的一种,代理模式就是给一个对象提供一个代理,就像我们生活中的中介(租房、留学、相亲),由代理对象控制原目标对象的访问,所以在代理对象中就可以实现对目标对象功能的增强、扩展、甚至改写。

  • 为什么需要代理?

  因为一个良好的设计不应该被轻易的修改,这正是开闭原则的体现:一个良好的设计应该对修改关闭,对扩展开放。而代理正是为了扩展类而存在,他可以控制对现有类(也就是被代理的类)的访问,通俗的说就是可以拦截对现有类方法调用并做出响应的处理

  代理模式的实际应用场景:aop、权限控制、服务监控、缓存、日志、限流、事务、拦截过滤等。

1.1 代理模式分为静态代理动态代理

1.1.1 静态代理

  静态代理就是我们自己手写代理类;aspect静态代理(编译器生成代理类)。静态代理:(目标接口、目标接口实现、代理类)。

// 创建目标接口TargetInterface
public interface TargetInterface {
    void sayHello(String name);
    void sayThanks(String name);
}

// 创建目标接口的实现类
public class TargetIntegerfaceImpl implements TargetInterface{
    @Override
    public void sayHello(String name) {
      System.out.println("sayHello: " + name);
    }

    @Override
    public void sayThanks(String name) {
      System.out.println("sayThanks: " + name);
    }
}

// 创建测试类
public class proxy_static_test {
    public static void main(String[] args) {
        // 1. 通过创建接口的实现类, 来代理该接口
        TargetInterface targetInterface = new TargetIntegerfaceImpl();
        targetInterface.sayHello("张三");
        targetInterface.sayThanks("李四");
    }
}
// 创建静态代理对象TargetProxy
public class TargetProxy implements TargetInterface {

    private TargetInterface targetInterface;

    public TargetProxy(TargetInterface targetInterface) {
        this.targetInterface = targetInterface;
    }

    @Override
    public void sayHello(String name) {
        // 使用代理来达到对目标方法增强
        System.out.println("start..........");

        // 中间调用目标接口的真正实现
        targetInterface.sayHello(name);

        System.out.println("end..........");
    }

    @Override
    public void sayThanks(String name) {
        // 使用代理来达到对目标方法增强
        System.out.println("start..........");

        // 中间调用目标接口的真正实现
        targetInterface.sayThanks(name);

        System.out.println("end..........");
    }
}

// 创建测试类
public class proxy_static_test {
    public static void main(String[] args) {
        // 2. 使用静态代理的方法调用接口中的方法
        /*
            创建静态代理实例TargetProxy, 传入一个该接口的实现类, 这样静态代理Proxy 对象就已经被我们创建好了, 它含有目标接口的引用,
         同时也含有目标接口的具体实现类, 当我们想调用接口中的方法时, 直接去找代理对象就可以了, 这个步骤中代理对象就可以对我们的方法进行增强.
         */
        TargetProxy targetProxy = new TargetProxy(new TargetIntegerfaceImpl());
        targetProxy.sayHello("王五");
        targetProxy.sayHello("赵六");
    }
}

1.1.2 动态代理

A. JDK 动态代理

  底层采用的是Java反射技术,获取真正的代理类,代理对象会在jvm 中进行创建,通过指定接口的ClassLoader 来创建一个指定接口的代理对象,代理类会实现InvocationHandler接口,来拦截我们的接口请求,在invoke()中做增强与请求转发处理:

@SuppressWarnings("unchecked")
public <T> T getProxy(Class interfaces) {
	// 1. jvm 内存中生成一个class 类
	// 2. 根据该class 类反射一个代理对象 $Proxy@
	return (T) Proxy.newProxyInstance(interfaces.getClassLoader(), new Class<?>[] {interfaces}, this);
}

/**
 * invoke() 是 InvocationHandler 接口中代理的拦截方法, 此处覆盖该方法, 让它对目标接口的方法进行拦截
 *
 * @param proxy 这个就是我们的代理类, 就是jdk 生成的那个以 $Proxy. 开头的代理类
 * @param method interface.method() 目标接口中的方法
 * @param args interface.method(args) 目标接口中的参数
 * @return
 * @throws Throwable
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("TargetProxy 前置增强.....");

    // 调用目标接口中的方法
    Object result = method.invoke(target, args);

    System.out.println("TargetProxy 后置增强.....");
    return result;
}

B. cglib 动态代理

  使用cglib实现动态代理,完全不受代理类必须实现接口的限制,cglib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK8之前,cglib动态要比JDK的动态代理(JDK使用的是Java反射)效率要高。唯一需要注意的是,如果被代理的类被final修饰,那么它将不可被继承,即不可被代理,同样,如果被代理的类中存在final修饰的方法,那么该方法也不可被代理。因为cglib原理是动态生成被代理类的子类。

  cglib原理:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法调用,顺势织入横切逻辑,它比Java反射的JDK动态代理要快。

  cglib底层:使用字节码处理器框架ASM,来转换字节码并生成新的类。不推荐直接使用ASM,因为他要求你必须对JVM内部结构,包括class文件的格式和属性都很熟悉。

  cglib缺点:对于final类与final方法cglib无法进行代理。

  cglib在代理接口与代理实体类时有不同的处理代理逻辑,代理类实现MethodInterceptor接口,来拦截我们的接口请求,在intercept()中做增强与请求转发处理:

/**
 * cglib动态代理关键代码, 获取真正的cglib 动态代理类
 * 1. jvm 内存中生成一个class 类
 * 2. 根据该class 类反射创建一个代理对象 $Proxy@564546548
 * @param tClass
 * @param <T>
 * @return
 */
public <T> T getProxy(Class<T> tClass) {

    // 字节码增强工具类
    Enhancer enhancer = new Enhancer();

    // 实体类代理 -> 设置父类
    enhancer.setSuperclass(tClass);
    // 接口代理 -> 设置接口
//        enhancer.setInterfaces(new Class[] {tClass});

    // 设置回调类
    enhancer.setCallback(this);

    //创建代理类
    return (T) enhancer.create();
}

public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    System.out.println("cglib 代理增强开始......");

    // 如果是代理类则直接调用目标方法
    Object result = methodProxy.invokeSuper(obj, args);
    // 如果是代理接口则需要像mybatis一样自己实现接口
//        System.out.println("cglib代理接口自己实现啦啦啦:方法参数含有+");
//        Arrays.stream(args).forEach(System.out::println);

    System.out.println("cglib 代理增强结束......");
    return result;
}

C. javassist 动态代理

  javassist与JDK代理类似,它是通过代理工厂来创建代理对象的,代理类实现MethodHandler接口,来拦截我们的接口请求,在invoke()中做增强与请求转发处理:

public Object getProxy(Class<?> tClass) throws InstantiationException, illegalaccessexception {
    // 代理工厂
    ProxyFactory proxyFactory = new ProxyFactory();

    // 设置需要创建子类的父类
    proxyFactory.setSuperclass(tClass);

    // 通过字节码技术动态创建子类实例
    proxyFactory.writeDirectory = "D:\\software\\data\\idea\\mybatis-3-mybatis-3.5.4";
    Object proxy = proxyFactory.createClass().newInstance();

    // 在调用目标方法之前, Javassist会回调MethodHandler接口方法拦截, 在其中可以实现自己的代理增强方法, 类似JDK中的InvocationHandler
    ((ProxyObject) proxy).setHandler(this);

    // 返回代理对象
    return proxy;
}

@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
    System.out.println("Javassist 事务开始.....");

    // 调用目标类中的方法
    Object result = proceed.invoke(self, args);

    System.out.println("Javassist 事务结束.....");
    return result;
}

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...
win11本地账户怎么改名?win11很多操作都变了样,用户如果想要...