引用

https://segmentfault.com/a/1190000011291179
https://www.jianshu.com/p/23d3f1a2b3c7
https://blog.csdn.net/qq_33661044/article/details/79767596

代理模式

代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,在不修改原目标对象的前提下,扩展目标对象的功能。

静态代理 (似装饰者模型,但代理不提供对象本身的增强)

  • 静态代理方式需要代理对象和目标对象实现一样的接口。(目的是与实现建立关系)

接口

public interface IUserDao {
    void save();
}

目标对象

public class UserDao implements IUserDao{
    @Override
    public void save() {
        System.out.println("保存数据");
    }
}

代理目标对象

public class UserDaoProxy implements IUserDao{
    private IUserDao target;
    public UserDaoProxy(IUserDao target) {
        this.target = target;
    }
    @Override
    public void save() {
        System.out.println("开启事务");//扩展了额外功能
        target.save();
        System.out.println("提交事务");
    }
}

执行

public class StaticUserProxy {
    @Test
    public void testStaticProxy(){
        //目标对象
        IUserDao target = new UserDao();
        //代理对象
        UserDaoProxy proxy = new UserDaoProxy(target);
        proxy.save();//执行的是代理的方法
    }
}

结果输出

#开启事务
#保存数据
#提交事务

动态代理

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。

  • 动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。
  • 如果所有代理接口都是公共的,则代理类是公共的,最终的,而不是抽象的。
  • 如果任何代理接口是非公共的,则代理类是非公共的,最终的,并且不是抽象的

动态代理的核心

  1. Proxy类 newProxyInstance返回指定接口的代理类的实例
  2. InvocationHandler接口 invoke 在代理实例上处理方法调用并返回结果。

我们先来看看newProxyInstance

@CallerSensitive//权限相关,作用是跳过被注释方法的权限检查而向上查找。
 public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
     //所有被实现的业务接口
      final Class<?>[] intfs = interfaces.clone();
     //寻找或生成指定的代理类
      Class<?> cl = getProxyClass0(loader, intfs);
      //通过反射类中的Constructor获取其所有构造方法
      final Constructor<?> cons = cl.getConstructor(constructorParams);
      //通过Constructor返回代理类的实例
      return cons.newInstance(new Object[]{h});
}

interfaces接口通过loader生成代理类,然后传入(通过构造方法)InvocationHandler做实现生成实例。引用文章上的一句话:

  • 当我们将业务接口和业务代理操作类DynamicProxyHandler传入Proxy中后,Proxy会为我们生成一个实现了接口并继承了Proxy的业务代理类$Proxy0。

使用实例

public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Hello.class }, // 传入要实现的接口
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}

interface Hello {
    void morning(String name);
}

故而在运行期动态创建一个interface实例的方法如下:

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
  • 使用的ClassLoader,通常就是接口类的ClassLoader;
  • 需要实现的接口数组,至少需要传入一个接口进去;
  • 用来处理接口方法调用的InvocationHandler实例。
  1. 将返回的Object强制转型为接口。

cglib代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。(详细原理将单独写一篇博客)

  • 使用cglib代理的对象则无需实现接口,达到代理类无侵入。
  • Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效
public class ProxyFactory implements MethodInterceptor{

    private Object target;//维护一个目标对象
    public ProxyFactory(Object target) {
        this.target = target;
    }
    
    //为目标对象生成代理对象
    public Object getProxyInstance() {
        //工具类
        Enhancer en = new Enhancer();
        //设置父类
        en.setSuperclass(target.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建子类对象代理
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开启事务");
        // 执行目标对象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("关闭事务");
        return null;
    }
}

执行

public class TestProxy {
    @Test
    public void testCglibProxy(){
        //目标对象
        UserDao target = new UserDao();
        System.out.println(target.getClass());
        //代理对象
        UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
        System.out.println(proxy.getClass());
        //执行代理对象方法
        proxy.save();
    }
}

动态代理&cglib与Spring 的关系

  1. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
  2. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
  3. 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
    明天再写qwq

Q.E.D.


在线