浅谈JDK动态代理<转载>

一个小需求:给原有方法添加日志打印

假设如今咱们有一个类Calculator,表明一个计算器,它能够进行加减乘除操做html

public class Calculator {

    //加
    public int add(int a, int b) {
        int result = a + b;
        return result;
    }

    //减
    public int subtract(int a, int b) {
        int result = a - b;
        return result;
    }

    //乘法、除法...
}

现有一个需求:在每一个方法执行先后打印日志。你有什么好的方案?java

直接修改ide

不少人最直观的想法是直接修改Calculator类:this

public class Calculator {

    //加
    public int add(int a, int b) {
        System.out.println("add方法开始...");
        int result = a + b;
        System.out.println("add方法结束...");
        return result;
    }

    //减
    public int subtract(int a, int b) {
        System.out.println("subtract方法开始...");
        int result = a - b;
        System.out.println("subtract方法结束...");
        return result;
    }

    //乘法、除法...
}

上面的方案是有问题的:编码

  1. 直接修改源程序,不符合开闭原则。应该对扩展开放,对修改关闭
  2. 若是Calculator有几十个、上百个方法,修改量太大
  3. 存在重复代码(都是在核心代码先后打印日志)
  4. 日志打印硬编码在代理类中,不利于后期维护:好比你花了一上午终于写完了,组长告诉你这个功能取消,因而你又要打开Calculator花十分钟删除日志打印的代码!

因此,此种方案PASS!spa


静态代理实现日志打印

“静态代理”四个字包含了两个概念:静态、代理。咱们先来了解什么叫“代理”,至于何为“静态”,须要和“动态”对比着讲。3d

代理是一种模式,提供了对目标对象的间接访问方式,即经过代理访问目标对象。如此便于在目标实现的基础上增长额外的功能操做,前拦截,后拦截等,以知足自身的业务需求。
引用博客: Java静态代理和动态代理 - 纪煜楷 - 博客园

引用自:Java静态代理和动态代理 - 纪煜楷 - 博客园代理

经常使用的代理方式能够粗分为:静态代理和动态代理。日志

静态代理的实现比较简单:编写一个代理类,实现与目标对象相同的接口,并在内部维护一个目标对象的引用。经过构造器塞入目标对象,在代理对象中调用目标对象的同名方法,并添加前拦截,后拦截等所需的业务功能。code

按上面的描述,代理类和目标类须要实现同一个接口,因此我打算这样作:

  • 将Calculator抽取为接口
  • 建立目标类CalculatorImpl实现Calculator
  • 建立代理类CalculatorProxy实现Calculator

接口

/**
 * Calculator接口
 */
public interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);
}

目标对象实现类

/**
 * 目标对象实现类,实现Calculator接口
 */
public class CalculatorImpl implements Calculator {

    //加
    public int add(int a, int b) {
        int result = a + b;
        return result;
    }

    //减
    public int subtract(int a, int b) {
        int result = a - b;
        return result;
    }

    //乘法、除法...
}

代理对象实现类

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

        //调用目标对象的add,并在先后打印日志
    @Override
    public int add(int a, int b) {
        System.out.println("add方法开始...");
        int result = target.add(a, b);
        System.out.println("add方法结束...");
        return result;
    }

        //调用目标对象的subtract,并在先后打印日志
    @Override
    public int subtract(int a, int b) {
        System.out.println("subtract方法开始...");
        int result = target.subtract(a, b);
        System.out.println("subtract方法结束...");
        return result;
    }

    //乘法、除法...
}

使用代理对象完成加减乘除,而且打印日志

public class Test {
    public static void main(String[] args) {
        //把目标对象经过构造器塞入代理对象
        Calculator calculator = new CalculatorProxy(new CalculatorImpl());
        //代理对象调用目标对象方法完成计算,并在先后打印日志
        calculator.add(1, 2);
        calculator.subtract(2, 1);
    }
}

静态代理示意图

静态代理的优势:能够在不修改目标对象的前提下,对目标对象进行功能的扩展和拦截。可是它也仅仅解决了上一种方案4大缺点中的第1点:

  1. 直接修改源程序,不符合开闭原则。应该对扩展开放,对修改关闭 √
  2. 若是Calculator有几十个、上百个方法,修改量太大 ×
  3. 存在重复代码(都是在核心代码先后打印日志) ×
  4. 日志打印硬编码在代理类中,不利于后期维护:好比你花了一上午终于写完了,组长告诉你这个功能取消,因而你又要打开Calculator花十分钟删除所有新增代码!×
    • *

静态代理的问题

上面案例中,代理类是咱们事先编写的,并且要和目标对象类实现相同接口。因为CalculatorImpl(目标对象)须要日志功能,咱们即编写了CalculatorProxy(代理对象),并经过构造器传入CalculatorImpl(目标对象),调用目标对象同名方法的同时添加加强代码。

可是这里有个问题!代理对象构造器的参数类型是Calculator,这意味着它只能接受Calculator的实现类对象,亦即咱们写的代理类CalculatorProxy只能给Calculator作代理,它们绑定死了!

若是如今咱们系统须要全面改造,给其余类也添加日志打印功能,就得为其余几百个接口都各自写一份代理类...

本身手动写一个类并实现接口实在太麻烦了。仔细一想,咱们其实想要的并非代理类,而是代理对象!那么,可否让JVM根据接口自动生成代理对象呢?

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

答案是确定的。

相关文章
相关标签/搜索