51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

动态代理学习

抛出问题:给原有的很多方法加上日志 实现方式:静态代理,静态代理有什么问题,怎么克服?

1. 给原有的方法加上日志

假设现在我们有一个类Calculator,代表一个计算器,它可以进行加减乘除操作. 在每个方法执行前后打印日志。你有什么好的方案?

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;
	}

	//乘法、除法...
}

修改方法一 直接修改

	//加
	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;
	}
  • 直接修改源程序,不符合开闭原则。应该对扩展开放,对修改关闭。

  • 如果要修改的地方过多,修改量太大。且重复性太高

  • 维护困难,后续不需要的时候,都删掉也很困难。

修改方法二 静态代理

> 代理是一种模式,可以理解为要买火车票的中介,用户不再直接访问目标对象,而是访问中介。那么中介就有操作空间,可以在原有基础上进行前拦截、后拦截等操作,以满足自己的业务需求。

编写一个代理类,并在内部维护一个目标类的引用。定义一个同名方法、或者实现与目标对象相同的接口。 调用时该目标类方法时,进行前拦截、后拦截操作。 代码修改为: 抽象接口

public interface Calculator {
	int add(int a, int b);
	int subtract(int a, int b);
}

目标对象实现类

public class CalculatorImpl implements Calculator {

	public int add(int a, int b) {
		return a + b;
	}

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

}

代理对象实现类

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);
	}
}

静态代理的思考

>静态代理,解决了不符合开闭原则的问题。没有修改原程序。 但是缺点依然明显,免不了的大量创建代理类,免不了的维护困难、存在重复代码。

如果只有一个类需要增强,那么这么写似乎没有问题。但是如果要给很多类的方法增强,难不成每个都写一个代理类?仔细思考,目标类目标方法这些都有了,我们想增强的逻辑也是清楚的。 如果每次运行时,在我们想要增强的地方,就自己产生了一个代理对象,把增强逻辑和目标对象的逻辑绑定在一起,那不就好了?

所以,我们其实不需要一个个代理类,而是需要代理对象去真正的干事情。

所以我们期待的效果是这样的,传入一个接口,JVM自动给了我一个实现了这个接口的代理对象。

要生成代理对象,应该经历怎样的步骤?

那要先研究下,要生成一个对象,要经历什么样的步骤?从图中可以看出,要new一个person对象,需要先编译Person.java文件为Person.class字节码文件。 然后加载到内存中。实际上加载进内存后,JVM就把它看做是一个对象了,Class<Person> personClass,毕竟jvm眼中,万物皆对象嘛。 程序员是无法自己new一个Class<T> 对象的。因为它的构造函数是私有的。当需要这个class对象时,JVM会自己调用这个私有构造器,传入类加载器,让它加载字节码文件到内存,然后JVM为其创建对应的Class对象。Class<Person> personClass、Class<Student> studentClass等等。 而new关键字

  • 第一步作用就是,让类加载器把class文件加载到内存,变成class对象,举例personClass。

  • 第二步是执行这个class对象的静态代码块、静态初始化语句。

  • 第三步是申请内存,创建一个空白对象。

  • 第四步是执行class对象的构造器函数,子类调用父类构造器,然后执行自己的构造器。

第二步到第四步,可以理解为都是personClass做的,或者JVM需要参照personClass的各种信息,来产生Person对象。 所以,要得到一个类的实例,关键是得到这个类的Class对象。

接口Class和类Class的区别

看下上述代码的接口Calculator的Class对象,和CalculatorImpl实现类的Class对象的区别。

import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) {
        Class&lt;Calculator&gt; calculatorClass = Calculator.class;
        Constructor&lt;?&gt;[] calculatorClassConstructors = calculatorClass.getConstructors();
        Method[] calculatorClassMethods = calculatorClass.getMethods();
        System.out.println("---------打印接口的Class对象的构造器信息---------");
        printClassInfo(calculatorClassConstructors);
        System.out.println("---------打印接口的Class对象的方法信息---------");
        printClassInfo(calculatorClassMethods);

        System.out.println("----------------------------------------------");
        Class&lt;CalculatorImpl&gt; calculatorImplClass = CalculatorImpl.class;
        Constructor&lt;?&gt;[] constructors = calculatorImplClass.getConstructors();
        Method[] methods = calculatorImplClass.getMethods();
        System.out.println("---------打印CalculatorImpl的Class对象的构造器信息---------");
        printClassInfo(constructors);
        System.out.println("---------打印CalculatorImpl的Class对象的方法信息---------");
        printClassInfo(methods);

    }

    private static void printClassInfo(Executable[] executables) {
        for (Executable executable : executables) {
            String name = executable.getName();
            StringBuilder stringBuilder = new StringBuilder(name);
            stringBuilder.append("(");
            Class&lt;?&gt;[] parameterTypes = executable.getParameterTypes();
            for (Class&lt;?&gt; parameterType : parameterTypes) {
                stringBuilder.append(parameterType.getName()).append(",");
            }
            if(parameterTypes.length != 0){
                stringBuilder.deleteCharAt(stringBuilder.length()-1);
            }
            stringBuilder.append(")");
            System.out.println(stringBuilder.toString());
        }
    }
}

结果

---------打印接口的Class对象的构造器信息---------
---------打印接口的Class对象的方法信息---------
subtract(int,int)
add(int,int)
----------------------------------------------
---------打印CalculatorImpl的Class对象的构造器信息---------
com.yubo.dynamicproxy.CalculatorImpl()
---------打印CalculatorImpl的Class对象的方法信息---------
subtract(int,int)
add(int,int)
wait(long,int)
wait(long)
wait()
equals(java.lang.Object)
toString()
hashCode()
getClass()
notify()
notifyAll()

可以看出:

  • 接口Class对象,没有构造方法,不能直接new对象。

  • 实现类Class对象,有构造方法,可以直接new对象。

  • 接口类和实现类都有关键方法add、subtract,实现类还有一些从Object继承的方法

现在我们希望通过接口创建实例。但是接口是没有构造方法的,不能new > 它本身是具备完善的类结构信息的。就像一个武艺高强的大内太监(接口),他空有一身绝世神功(类结构信息),却后继无人。如果江湖上有一位妙手圣医,能克隆他的一身武艺,那么克隆人不就武艺高强的同时,还能生儿育女了吗? 所以我们就想,JDK有没有提供这么一个方法,比如getXxxClass(),我们传进一个接口Class对象,它帮我们克隆一个具有相同类结构信息,又具备构造器的新的Class对象呢?

动态代理

很巧,JVM的动态代理级就有上述的功能。你给它一个Calculator接口,自然也就有了calculatorClass文件。 这个文件没有构造器方法。 JVM通过某种方法,得到了一个崭新的class文件,这个otherCalculatorClass,是有构造方法的。然后我们再去创建代理对象,就简单了。JDK的文档,Proxy.getProxyClass可以达到这个效果。

上图的黑盒就有了答案,使用Class calculatorProxyClazz = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);达到代理类的类对象 代码更改如下

import java.lang.reflect.*;

public class Main {
    public static void main(String[] args) {
        //Class&lt;Calculator&gt; calculatorClass = Calculator.class;
        Class&lt;?&gt; proxyClass = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);
        Constructor&lt;?&gt;[] calculatorClassConstructors = proxyClass.getConstructors();
        Method[] calculatorClassMethods = proxyClass.getMethods();
        System.out.println("---------打印接口的Class对象的构造器信息---------");
        printClassInfo(calculatorClassConstructors);
        System.out.println("---------打印接口的Class对象的方法信息---------");
        printClassInfo(calculatorClassMethods);

        System.out.println("----------------------------------------------");
        Class&lt;CalculatorImpl&gt; calculatorImplClass = CalculatorImpl.class;
        Constructor&lt;?&gt;[] constructors = calculatorImplClass.getConstructors();
        Method[] methods = calculatorImplClass.getMethods();
        System.out.println("---------打印CalculatorImpl的Class对象的构造器信息---------");
        printClassInfo(constructors);
        System.out.println("---------打印CalculatorImpl的Class对象的方法信息---------");
        printClassInfo(methods);

    }

    private static void printClassInfo(Executable[] executables) {
        for (Executable executable : executables) {
            StringBuilder stringBuilder = new StringBuilder();
            if(executable instanceof Method){
                Type returnType = ((Method) executable).getGenericReturnType();
                stringBuilder.append(returnType.getTypeName()).append("---");
            }
            String name = executable.getName();
            stringBuilder.append(name);
            stringBuilder.append("(");
            Class&lt;?&gt;[] parameterTypes = executable.getParameterTypes();
            for (Class&lt;?&gt; parameterType : parameterTypes) {
                stringBuilder.append(parameterType.getName()).append(",");
            }
            if(parameterTypes.length != 0){
                stringBuilder.deleteCharAt(stringBuilder.length()-1);
            }
            stringBuilder.append(")");
            System.out.println(stringBuilder.toString());
        }
    }
}

运行结果

---------打印接口的Class对象的构造器信息---------
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
---------打印接口的Class对象的方法信息---------
int---subtract(int,int)
int---add(int,int)
boolean---equals(java.lang.Object)
java.lang.String---toString()
int---hashCode()
java.lang.Class&lt;?&gt;---getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
java.lang.Object---newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
java.lang.reflect.InvocationHandler---getInvocationHandler(java.lang.Object)
boolean---isProxyClass(java.lang.Class)
void---wait(long,int)
void---wait(long)
void---wait()
java.lang.Class&lt;?&gt;---getClass()
void---notify()
void---notifyAll()
----------------------------------------------
---------打印CalculatorImpl的Class对象的构造器信息---------
com.yubo.dynamicproxy.CalculatorImpl()
---------打印CalculatorImpl的Class对象的方法信息---------
int---subtract(int,int)
int---add(int,int)
void---wait(long,int)
void---wait(long)
void---wait()
boolean---equals(java.lang.Object)
java.lang.String---toString()
int---hashCode()
java.lang.Class&lt;?&gt;---getClass()
void---notify()
void---notifyAll()

这次又构造方法了。

赞(9)
未经允许不得转载:工具盒子 » 动态代理学习