抛出问题:给原有的很多方法加上日志 实现方式:静态代理,静态代理有什么问题,怎么克服?
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<Calculator> calculatorClass = Calculator.class;
Constructor<?>[] calculatorClassConstructors = calculatorClass.getConstructors();
Method[] calculatorClassMethods = calculatorClass.getMethods();
System.out.println("---------打印接口的Class对象的构造器信息---------");
printClassInfo(calculatorClassConstructors);
System.out.println("---------打印接口的Class对象的方法信息---------");
printClassInfo(calculatorClassMethods);
System.out.println("----------------------------------------------");
Class<CalculatorImpl> calculatorImplClass = CalculatorImpl.class;
Constructor<?>[] 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<?>[] parameterTypes = executable.getParameterTypes();
for (Class<?> 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<Calculator> calculatorClass = Calculator.class;
Class<?> proxyClass = Proxy.getProxyClass(Calculator.class.getClassLoader(), Calculator.class);
Constructor<?>[] calculatorClassConstructors = proxyClass.getConstructors();
Method[] calculatorClassMethods = proxyClass.getMethods();
System.out.println("---------打印接口的Class对象的构造器信息---------");
printClassInfo(calculatorClassConstructors);
System.out.println("---------打印接口的Class对象的方法信息---------");
printClassInfo(calculatorClassMethods);
System.out.println("----------------------------------------------");
Class<CalculatorImpl> calculatorImplClass = CalculatorImpl.class;
Constructor<?>[] 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<?>[] parameterTypes = executable.getParameterTypes();
for (Class<?> 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<?>---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<?>---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<?>---getClass()
void---notify()
void---notifyAll()
这次又构造方法了。