java动态代理

本文从四个方面认识动态代理

  • 什么是代理?
  • 为什么使用代理?
  • 如何使用动态代理?
  • 动态代理的原理

一 什么是代理

要理解动态代理是什么先要了解一种设计模式那就是代理模式

  • 代理模式的定义

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。


image.png

一个是真正的你要访问的对象(目标类或委任类),一个是代理对象,真正对象与代理对象实现同一个接口,先访问代理类再访问真正要访问的对象。

  • 各个组成部分

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

  • 不同的代理模式
    代理模式分为静态代理、动态代理。
  1. 静态代理
    静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

  2. 动态代理
    动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

当然本文的主要讨论是动态代理。

二 为什么使用代理?

之所以使用代理是因为代理有以下几个有点:

  • 职责清晰
    真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

  • 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。

  • 高扩展性

三 如何使用动态代理?

  • 静态代理
    再了解使用动态代理之前,先了解静态代理的用法这样才能对之后的动态代理有更深的认识。

下面从一个简单的需求例子说明:

假设有一个叫Exec的类里面有n个方法m1 m2 m3 ...

class Exec{
    public int m1(){
      一顿操作...
    }

    public int m2(){
      又一顿操作...
    }

    public int m3(){
      又又一顿操作...
    }
    ...
}

现有一个需求:在每个方法执行前后打印日志。以下有几个方案

方案1直接修改

最直观的方法,代码如下:

class Exec{
    public int m1(){
       System.out.println("m1开始...");
        一顿操作...
       System.out.println("m1结束...");    
        return result;
}

    public int m2(){
       System.out.println("m2开始...");
      又一顿操作...
       System.out.println("m2结束...");    
        return result;
    }

    public int m3(){
       System.out.println("m3开始...");    
      又又一顿操作...
       System.out.println("m3结束...");    
       return result;
    }
    ...  ...
}

上面的方案是有问题的:

  1. 直接修改源程序,不符合对扩展开放,对修改关闭的开闭原则。
  2. 如果Exec类有几十个、上百个方法,修改量太大。
  3. 存在重复代码(都是在核心代码前后打印日志)。
  4. 日志打印硬编码在代理类中,不利于后期维护:比如你花一段时间写完,被告知此功能取消,你又要去删除日志打印代码。

方案2使用静态代理类

根据上文对静态代理的描述,该方案如下:

  • 将Exec抽取为接口
  • 创建目标类ExecImpl实现Exec
  • 创建代理类ExecProxy实现Exec

接口:

/**
 * Exec接口
 */
 public interface Exec{
        int m1();
        int m2();
        int m3();
        ...
 }

目标对象实现类:

/**
 * 目标对象实现类,实现Exec接口
 */
 public class ExecImpl implements Exec{
        @Override
        public int m1( ) {
                一顿操作...
                return 1;
        }

        @Override
        public int  m2( ) {
               又一顿操作...
                return 2;
        }

        @Override
        public int  m3( ) {
               又又一顿操作...
              return 3;
        }
        ... ...
}

代理对象实现类:

/**
 * 代理对象实现类,实现Exec接口
 */
 public class ExecProxy  implements Exec{
        //代理对象内部维护一个目标对象引用
        private Exec  target;
        
        //构造方法,传入目标对象
        public ExecProxy(Exec  target) {
                this.target = target;
         }

        //调用目标对象的m1,并在前后打印日志
        @Override
        public int m1( ) {
                System.out.println("m1方法开始...");
                一顿操作...
                System.out.println("m1方法结束...");
                return result;
        }

        @Override
        public int  m2( ) {
                System.out.println("m2方法开始...");
               又一顿操作...
                System.out.println("m2方法结束...");
                return result;
        }

        @Override
        public int  m3( ) {
                System.out.println("m3方法开始...");
               又又一顿操作...
                System.out.println("m3方法结束...");
                return result;
        }
        ... ...
}

使用代理对象完成目标对象里的方法,并且打印日志

public class Test {
        public static void main(String[] args) {
          //把目标对象通过构造器传入代理对象
          Exec exec= new ExecProxy(new ExecImpl());
         //代理对象调用目标对象的方法,并在前后打印日志
          exec.m1();
          exec.m2();
          exec.m3();
          ...
        }
}  

静态代理的优点:可以在不修改目标对象的前提下,对目标对象进行功能的扩展和拦截。但是它也仅仅解决了上一种方案4大缺点中的第1点:直接修改源程序,不符合开闭原则。应该对扩展开放,对修改关闭。

  • 静态代理的问题

上面案例中,代理类是我们事先编写的,而且要和目标对象类实现相同接口。由于ExecImpl(目标对象)需要日志功能,我们即编写了ExecProxy(代理对象),并通过构造器传入ExecImpl(目标对象),调用目标对象同名方法的同时添加增强代码。

但是这里有个问题。代理对象构造器的参数类型是Exec,它只能接受Exec的实现类对象,亦即我们写的代理类ExecProxy只能给Exec做代理,它们绑定死了。

如果现在我们系统需要全面改造,给其他类也添加日志打印功能,就得为其他几百个接口都各自写一份代理类。

image.png

自己手动写一个类并实现接口实在太麻烦。如何少写或者不写代理类,却能完成代理功能?仔细一想,我们其实想要的并不是代理类,而是代理对象。那么,能否让JVM根据接口自动生成代理对象呢?

比如,有没有一个方法,我传入接口,它就给我自动返回代理对象?

image.png

答案是有就是接下来讲得动态代理。

方案3动态代理

  • 方式1
public class ProxyTest {
   public static void main(String[] args) throws Throwable {
       ExecImpl  target = new ExecImpl();
 //传入目标对象
//目的:1.根据它实现的接口生成代理对象 2.代理对象调用目标对象方法
       Exec  execProxy = (Exec) getProxy(target);//目标对象(委托对象)
       execProxy .m1();
       execProxy .m2();
   }

   private static Object getProxy(final Object target) throws Exception {
       //参数1:随便找个类加载器给它, 参数2:目标对象实现的接口,让代理对象实现相同接口
       Class proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
       Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
       Object proxy = constructor.newInstance(new InvocationHandler() {
           @Override
           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
               System.out.println(method.getName() + "方法开始执行...");
               Object result = method.invoke(target, args);
               System.out.println("返回结果是:" + result);
               System.out.println(method.getName() + "方法执行结束...");
               return result;
           }
       });
       return proxy;
   }
}

打印结果:

m1方法开始执行...
返回结果是: 1
m1方法执行结束...
m2方法开始执行...
返回结果是: 2
m2方法执行结束...

可惜,还是太麻烦了。有没有更简单的方式获取代理对象?

public class ProxyTest {
    public static void main(String[] args) throws Throwable {
        ExecImpl target = new ExecImpl();//委托对象(目标对象)
        Exec execProxy = (Exec) getProxy(target);
        execProxy.m1();
        execProxy.m2();
    }

    private static Object getProxy(final Object target) throws Exception {
        Object proxy = Proxy.newProxyInstance(
                target.getClass().getClassLoader(),//类加载器
                target.getClass().getInterfaces(),//让代理对象和目标对象实现相同接口
                new InvocationHandler(){
                        //代理对象的方法最终都会被JVM导向它的invoke方法
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println(method.getName() + "方法开始执行...");
                        Object result = method.invoke(target, args);
                        System.out.println("返回结果是:" + result);
                        System.out.println(method.getName() + "方法执行结束...");
                        return result;
                    }
                }
        );
        return proxy;
    }
}

打印结果:

m1方法开始执行...
返回结果是: 1
m1方法执行结束...
m2方法开始执行...
返回结果是: 2
m2方法执行结束...

四 动态代理的原理

Proxy类的静态方法getProxyClass()方法,给它传一个接口Class对象,它能返回一个加强版Class对象。

Proxy类和JVM,让我们不写代理类却直接得到代理Class对象,进而得到代理对象。

静态代理

image.png

动态代理

image.png

你可能感兴趣的:(java动态代理)