肝他 ❗ 静态代理、动态代理

301 阅读9分钟

代理

在Java中“代理”就是某个具体实例对象的代表。举个例子,我们找微商买东西的过程,具体实例对象就是“厂家”,这个“代表”就是“微商”。那么像进货、卖货这种业务就是由“微商”来做,“厂家”要做的是生产货物。但是,想要买“厂家”生产的东西,那么必须通过“微商”这个代表才可以。也就是说,代理是某个具体实例对象的入口与出口

代理模式

📢代理模式是一种设计模式,通过代理模式,提供了对被代理类的额外访问方式,通过代理类访问被代理类,我们可以在不修改被代理类的前提下,提供额外的功能操作(增强),从而扩展被代理类的功能。简而言之,代理模式就是设置一个中间代理来控制所有对被代理类的访问,以实现增强被代理类的功能。代理模式有静态代理动态代理两种。

静态代理

在程序还没有运行之前,如果已经存在该代理类,则为静态代理。静态代理中代理类和被代理类都需要实现同一个接口或者继承同一个类。以下例子表示,InterfaceProxy为代理类和被代理都需要实现的接口,Proxy_表示为代理类InterfaceProxyImpl则为被代理类,我们在代理类中创建一个被代理类对象,这样我们就可以通过代理类对被代理类进行访问或者对被代理类做功能上的增强。

  • 接口
public interface InterfaceProxy {

    Integer method(Integer price);

}
  • 被代理类
public class InterfaceProxyImpl implements InterfaceProxy {
    @Override
    public Integer method(Integer price) {
        return price;
    }
}
  • 代理类
public class Proxy_ implements InterfaceProxy {

    //被代理类
    InterfaceProxyImpl ppi = new InterfaceProxyImpl();

    @Override
    public Integer method(Integer price) {
        //调用被代理类的方法
        Integer p = ppi.method(price);
        //对被代理类的增强
        return p*2;
    }
}
  • 测试
public static void main(String[] args) {
    //访问被代理类时通过被代理类来访问
    Proxy_ proxyDemo = new Proxy_();
    
    //通过代理类来访问被代理类
    System.out.println("原厂价:"+proxyDemo.pii.method(10));//原厂价:10
    //同时可以对被代理类进行功能扩展原厂价*2,扩展逻辑在代理类中实现
    Integer proxyPrice = proxyDemo.method(10);
    System.out.println("代理价格:"+proxyPrice);//代理价格:20

}

动态代理

在程序运行前还不存在代理类,在程序运行时才生成代理类的代理方式我们称之为动态代理。假设上面静态代理例子中需要我们实现这样一个功能,在执行被代理类的所有方法之前输出"before",在执行被代理类方法后输出"after",如果被代理类中实现了接口的多个方法,那么我们则需要修改每一个代理类中的方法(如下程序所示)。有了动态代理之后,我们就可以对被代理类的所有方法进行统一的处理,如何处理,将会下面描述。动态代理可以在不修改方法的基础上对方法增强。 实现动态代理主要有两种方式:基于接口的动态代理👉JDK动态代理、基于子类的动态代理(对指定的目标类生成一个子类,并对子类进行增强)👉CGLib动态代理

public class Proxy_ implements InterfaceProxy {

    //被代理类
    InterfaceProxyImpl pii = new InterfaceProxyImpl();
    @Override
    public Integer method(Integer price) {
        System.out.println("before");
        //执行目标方法
        Integer p = pii.method(price);
        System.out.println("after");
        //对被代理类的增强
        return p*2;
    }
    
    @Override
    public Integer method2(Integer price) {
        System.out.println("before");
        //执行目标方法方法
        Integer p = pii.method(price);
        System.out.println("after");
        //对被代理类的增强
        return p*2;
    }
    
    @Override
    public Integer method3(Integer price) {
        System.out.println("before");
        //执行目标方法
        Integer p = pii.method(price);
        System.out.println("after");
        //对被代理类的增强
        return p*2;
    }
}

JDK动态代理

JDK动态代理是基于接口实现的,因此需要被代理类至少实现一个接口,即只能对该类所实现的接口中定义的方法进行代理。JDK动态代理利用反射类Proxy来生成代理对象,代理对象在执行目标方法前通过InvocationHandler来拦截,在invoke方法里面写我们的增强逻辑,从而实现在不修改方法的情况下对目标方法进行增强。

newProxyInstance方法的参数解释

Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
  • ClassLoader loader:类加载器,用于加载代理对象的字节码。但该参数需要写的是被代理类的类加载器,即代理谁就写谁的ClassLoader。如代理InterfaceProxyImpl类,则为InterfaceProxyImpl.getClass().getClassLoader()

  • Class<?>[] interfaces:字节码数组,存放被代理类实现的接口,用于让代理对象和被代理对象有相同的方法(即让代理对象和被代理对象实现同一个接口),同样为代理谁就写谁的Interfaces,如代理InterfaceProxyImpl类,则为InterfaceProxyImpl.getClass().getInterfaces()

  • InvocationHandler h:用于提供增强的代码,它是让我们写如何代理,其中我们需要重写InvocationHandler中的invoke()方法,📢当目标方法被执行时,就会被InvocationHandler拦截,然后执行invoke方法,我们对方法的增强逻辑就是写在invoke方法中

invoke方法的参数

public Object invoke(Object proxy, Method method, Object[] args){
    return null;
}
  • Object proxy表示代理对象的引用(一般情况下不会使用到)
  • Method method表示被调用的方法对象(即被代理类中的方法),method.invoke(目标对象,方法的参数)表示的就是执行目标方法
  • Object[] args表示被调用的方法执行所需的参数

使用例子

  • 准备一个接口
public interface InterfaceProxy {

    void method(Integer price);

    void method2(String string);

}
  • 被代理类,需要实现上面的InterfaceProxy接口
public class InterfaceProxyImpl implements InterfaceProxy {
    @Override
    public void method(Integer price) {
        System.out.println("代理后:"+price);

    }

    @Override
    public void method2(String string) {
        System.out.println("代理后:"+string);

    }
}
  • 生成代理类
public class test {
    //动态代理
    public static void main(String[] args) {
        //被代理类、目标对象
        final InterfaceProxyImpl proxyed = new InterfaceProxyImpl();

        InterfaceProxy proxy = (InterfaceProxy)Proxy.newProxyInstance(proxyed.getClass().getClassLoader(),
                proxyed.getClass().getInterfaces(), new InvocationHandler() {
                    //执行被代理对象的接口中任何的方法都会经过该方法(就会有拦截的功能)
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //获取方法执行的参数
                        Object arg = args[0];
                        //当接口中有多个方法时,需要判断要进行对哪个方法进行增强,如果需要对所有方法都增强,则无法判断
                        //如果当前执行的方法的名称和"method"相同,则对该方法增强
                        if(method.getName().equals("method")){
                            System.out.println("before1");
                            //执行目标方法
                            Integer returnVal = (Integer) method.invoke(proxyed,(Integer)arg*2 );
                            System.out.println("after1");
                            //invoke方法的返回值即为代理类执行方法的返回值,当目标对象的方法类型为void时,可以return null
                            return null;
                        }else{
                            System.out.println("before2");
                            //执行目标方法
                            String returnVal = (String) method.invoke(proxyed,"点赞+关注:"+(String)arg );
                            System.out.println("after2");
                            return returnVal;
                        }
                    }
                });
        //通过代理对象调用目标方法
        proxy.method(8);

        System.out.println("===============================");

        proxy.method2("不喝奶茶的Programmer");
    }
}

程序输出:
    before1
    代理后:16
    after1
    ===============================
    before2
    代理后:点赞+关注:不喝奶茶的Programmer
    after2

😋 通过以上例子我们可以看出,使用动态代理方式不需要我们手动生成代理类,而是Proxy的newProxyinstance()方法来生成的,运行后不生成代理类的字节码文件即.class文件,而是在运行时动态生成代理类的字节码,并加载到JVM中。同时,我们可以通过动态代理使用在不修改方法的基础上对方法进行统一的增强。

CGLib动态代理

经过上面的学习,我们已经对JDK动态代理有了一定的了解,JDK动态代理只能对实现了接口的类生成代理,而不能针对类,如果要代理的类为一个普通类、没有实现任何接口,那么JDK动态代理就没法使用了,所以CGLib动态代理应运而生🤳。CGLib是基于子类的动态代理,CGLib动态代理通过ASM开源包,对被代理类的字节码文件即.class文件加载进来,通过修改其字节码动态生成被代理类的子类,子类重写被代理类的所有不是final的方法(final修饰的方法不允许子类重写),然后在子类中采用方法拦截技术拦截所有的被代理类方法的调用,然后将我们的增强代码织入到其中

CGLib是通过Enhancer的静态方法来create来生成代理对象的

create()方法如下:

public static Object create(Class type, Callback callback) {
    Enhancer e = new Enhancer();
    e.setSuperclass(type);
    e.setCallback(callback);
    return e.create();
}

⏩其中各参数表示:

  • Class type:字节码。用于指定被代理对象的字节码,即要代理谁就写谁的getClass()
  • Callback callback:定义一个拦截器,用于提供增强的代码,一般写的都是该接口的子接口MethodInterceptor的实现类,也可以直接实现,实现MethodInterceptor接口后需要重写其中的intercept()方法,也就是每次执行目标方法前,CGLib会回调MethodInterceptor接口方法拦截,然后执行我们重写的intercept()方法,我们的代理逻辑就是写在intercept()方法中。

intercept()方法的各个参数

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    return null;
}
  • Object o:动态生成的代理对象的引用
  • Method method:表示被调用的方法对象(即被代理类中的方法),method.invoke(目标对象,方法的参数)表示的就是执行目标方法 - Object[] objects:表示被调用的方法执行时所需的参数
  • MethodProxy methodProxy:当前执行方法的代理对象,可以通过methodProxy.invokeSuper(代理对象,参数)来执行目标方法

CGLib动态代理的使用

  • CGLib 是第三方提供的工具,所以我们使用它首先需要引入第三方CGLib库
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.1_3</version>
</dependency>
  • 准备被代理类proxy_,无需实现任何接口
public class proxy_ {

    public void method1(String str){
        System.out.println(str);
    }
}
  • 生成代理对象Proxy
public static void main(String[] args) {
    final proxy_ proxyed = new proxy_();
    proxy_ Proxy = (proxy_)Enhancer.create(proxyed.getClass(), new MethodInterceptor() {
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("before");
            method.invoke(proxyed, "点赞+关注:"+objects[0]);
            System.out.println("after");
            return null;
        }
    });
    //通过代理对象执行目标方法
    Proxy.method1("不喝奶茶的Programmer");
}

程序输出:
        before
        点赞+关注:不喝奶茶的Programmer
        after

🥨通过上面对CGLib动态代理的使用过程的了解,你可能会问,如果我的被代理类也实现了接口,那能不能使用CGLib动态代理呢?✔答案是可以的,在这里就不再描述了,大家可以去试试🥠。

总结(JDK、CGLib动态代理的区别)

  • JDK动态代理是基于接口的动态代理,是针对接口实现代理,被代理类必须至少实现一个接口,而CGLib是基于子类的动态代理,是针对类实现代理。
  • JDK动态代理是通过反射生成代理对象,而CGlib底层通过ASM字节码生成框架,使用字节码技术生成代理对象,比使用Java反射效率要高。

🏁🏁动态代理在很多框架都会使用到,如Spring的AOP、Mybatis都有很多地方使用到动态代理,因此掌握好动态代理非常有助于我们后面的学习,知识都是一点一点嵌套的,正所谓不积跬步无以至千里嘛。好了,以上就是对静态代理和动态代理的介绍,如果有错误的地方,还请留言指正,如果觉得本文对你有帮助那就点个赞👍吧😻😍

默认标题_动态分割线_2021-07-15-0.gif