1、java中的代理是什么?
我们先不说技术体系中的代理,我们先来回忆下我们的现实生活中是否有代理这种情况存在:
比如微商,某个公司做出某种产品,然后微商他们用公司的渠道价买来这种产品,然后再通过微信宣传卖给消费者;
对于这家公司来说,他们的产品他们可能并不直接销售,而是通过他们找的人(微商)来销售,这种模式可以叫做代理,可以理解为代为销售,然后赚取一定的差价。
简单来说显示生活中的代理就是:我有某个东西,但是我不直接使用,而是让某个人代为使用,然后达到跟我自己使用一致的效果。
我们回到技术体系中来,看一下java技术中的代理是什么样的呢?
例如现在有一个类A,A中有一个方法doIt():
public class A{
public void doIt(){
...
}
}
如果某些场景中需要使用A类的doit()方法,但是,项目代码又不想直接通过A类对象调用doIt()方法;
而是又创建了一个类ProxyA,并在类Proxy中定义了一个A类型的成员变量a,并且定义了一个跟A类中的doIt()方法一样的方法;
然后项目代码再通过ProxyA类对象调用doIt()方法。
//这个ProxyA就是一个代理类
public class ProxyA{
//类中定义了一个A类型的成员变量a,表明Proxy要代理的是A类对象
A a;
//构造方法需要传入A类的对象引用,用于初始化变量a,这样Proxy才知道它要代理的是哪个对象
public Proxy(A a){
this.a = a;
}
//这里定义了跟A类一样的方法doIt(),但它们的这个方法的实现逻辑不一样(代理类中的doIt()方法的实现是通过调用a的doIt()方法实现的)
public void doIt(){
a.doIt();
}
}
我们创建一个A类对象和一个Proxy类对象用于测试:
public class Test{
public static void main(String[] args) {
//这里创建了A类对象a,但是并没有直接调用a.doIt();
A a = new A();
//而是通过创建一个A类的代理对象proxyA,然后通过proxyA.doIt()来调用doIt();
ProxyA proxyA = new Proxy(a);
//因为我们的ProxyA类里定义了它所代理的类的成员变量,当我们的代码执行proxy.doIt()时,实际上调用的是a.doIt();
proxyA.doIt();
}
}
其实上述讲解的就是java中的静态代理(后面还会补充)。
简单总结一下java中的代理:
我的项目里需要调用目标类的方法时,不直接通过这个类的对象进行调用,而是通过一个代理类的对象来调用,即通过代理对象来访问目标对象,从而调用目标对象的方法;
2、为什么需要代理?使用代理有什么好处?
根据上面讲述的通过proxyA.doIt()来调用a.doIt(),这样做似乎没有什么用处呀。
确实,如果只是这样纯粹地代理一下,那多定义一个这样的代理类确实是不划算,它把调用关系复杂化了:项目代码-->proxy.doIt()-->a.doIt(),而不使用代理层的话,其调用关系则比较简单:项目代码-->a.doIt();
那我们为什么还要使用代理呢?原因可能是多样的,我这里根据我接触过的场景列举一些:
1)代理类可以对目标类的方法功能进行补充,即可以在调用目标类方法的前后做一些操作。什么意思呢?
假如现在目标类有个方法的作用是计算员工的税后工资,但是在计算税后工资之前我们希望记录一下当前正在计算哪个员工的税后工资;
这属于在调用目标类的方法之前做一些额外的补充操作;
还有在一种情况是调用了目标类的计算税后工资方法之后,考虑到可能有些员工会有旷工现象,需要对有旷工情况的员工进行罚款,所以调用了目标类的计算税后工资方法后,得到员工工资,判断一下员工是否有旷工现象,如果有的话在计算得到的税后工资基础上扣钱;
这就是属于在调用目标类的方法后做的额外操作,以满足业务场景需求。
2)计算目标类的方法调用的耗时;
有些场景下可能需要监控方法的调用耗时,并把它们输出到日志中,便于后续排查问题;
例如涉及到数据上传、下载的方法,有些后上传或者下载慢了,可能需要知道它们的执行情况,这时就可以统计一下这些方法调用的耗时,并输出到日志,如果出现慢的情况,可以通过日志查看方法的调用耗时是否合理;
针对这种场景,我们就需要在代理类中调用目标类的方法之前先获取一下当前时间,然后调用目标类的方法,当目标类的方法执行结束后,再计算出执行所花的时间。
3)有些人可能会疑问了,这些操作我放在目标类的方法里面做不行吗?为什么非得通过代理来做?
当然,其实不用代理实现也是可以的,起码相同的功能不用代理它也是可以实现的;
但是我们技术人不应该对自我要求仅仅停留在仅实现功能的层面上,我们追逐代码的通用性、简洁性、灵活性、低耦合等,提高代码的质量;
以计算税后工资的例子来说,如果我们把打印计算薪资的日志、计算旷工罚款等操作一股脑地都放在了计算税后工资的方法里,假设我们的场景是这样的:
有些地方需要获取税后工资减去旷工扣款,而有些地方仅需要获取税后工资,那我们的计算税后工资方法就没法兼容这两种场景了;
但是如果使用代理模式的话,需要获取税后工资减去旷工扣款的场景则通过代理对象获取,而只是纯粹获取税后工资的则直接调用目标类的计算税后工资方法获取;
我们引入代理后,则可以做到兼容这两种场景,需要代理的地方才使用代理对象,否则我们就直接调用目标类对象。
3、java是如何实现代理的?
java中的代理分为静态代理、动态代理和CGLIB代理。
代理模式:
java中的代理模式是指代码不直接访问目标对象,而是通过一个代理对象来访问目标对象,代理对象充当一个中介作用。
不管哪种代理方式,我们最终都需要获取到代理类的对象实例,根据获取代理信息的不同可分为静态代理和动态代理。
静态代理:
1)静态代理是指 在jvm运行之前就已经获取到代理类的class信息。这怎么理解呢?
在静态代理模式下,代理类需要开发者自己写好,即开发者需要自己实现代理类的.java文件,也就是说在项目编译之前就需要存在代理类的.java文件;
然后再编译阶段就可以将代理类的.java文件编译成.class文件,从而得到代理类的class信息;
因为此时得到了代理类的class文件还没到java程序启动运行阶段呢,所以管这个叫做静态代理,也就是在程序代码还未启动之前就已经获取到代理类的class信息。
2)静态代理如何实现?
根据规范,实现java静态代理需要实现定义一个接口,然后代理类与目标类均需要实现这个接口,把需要代理的方法统统在接口中定义好了,目的是为了约束代理类必须得实现需要代理的方法,保证目标类中需要被代理的方法能够被成功代理。
3)代码实现:
根据上述规范,我们在实际开发中使用静态代理的时候,首先需要定义一个公共接口,接口中定义好需要被代理的方法,然后定义代理类和目标类,并实现所定义的公共接口。
//公共接口Employee定义:
public interface Employee {
double calculateSalary(int id);
}
//目标类EmployeeImpl定义:
public class EmployeeImpl implements Employee {
/**
* 计算员工的税后薪资
* @param id
* @return
*/
public double calculateSalary(int id) {
if (id == 1){
return 10000*0.8;
} else if (id == 2){
return 8000*0.8;
} else if (id == 3){
return 5000*0.8;
} else {
String err = "员工:" + id + " 不存在,请确认员工信息.";
throw new RuntimeException(err);
}
}
}
//代理类EmployeeProxyImpl定义:
public class EmployeeProxyImpl implements Employee {
//代理类需要包含一个目标类的对象引用
EmployeeImpl employee;
//并提供一个带参的构造方法用于指定代理哪个对象
public EmployeeProxyImpl(EmployeeImpl employee){
this.employee = employee;
}
public double calculateSalary(int id) {
//在调用目标类的calculateSalary方法之前做的操作
System.out.println("当前正在计算员工: " + id + "的税后工资");
double salary = employee.calculateSalary(id);
//根据业务场景的需要可以选择在调用目标类的calculateSalary方法之后做些操作
return salary;
}
}
//测试类:
public class Main {
public static void main(String[] args) {
//实例化一个目标类对象
EmployeeImpl employee = new EmployeeImpl();
//将目标类对象传给代理类的构造方法,因为代理类中含有一个目标类的对象引用,会将传给的目标类对象赋予这个对象引用,
//从而指定这个代理类对象代理的是哪个目标类对象实例
EmployeeProxyImpl employeeProxy = new EmployeeProxyImpl(employee);
//计算员工的税后工资,但是不直接调用employee的calculateSalary方法,
//而是通过代理对象employeeProxy调用calculateSalary进行计算,
//因为希望通过代理对象对目标类的方法做一些补充,所以使用代理模式实现员工的税后工资计算。
//这种方式是静态方式
double salary = employeeProxy.calculateSalary(1);
System.out.println("员工:" + 1 + "的工资是:" + salary);
}
}
运行结果
静态代理总结:
1)代理类的信息在jvm运行之前就已经生成,逻辑由开发者实现;
2)代理类与目标类的定义应该严格参照规范,定义公共接口并实现它,需要代理的方法在接口中都要定义好;
-
静态代理原理:在代理类中包含一个目标类的对象引用,然后在使用时创建一个目标类对象并且创建一个代理类对象,并把目标类对象传给代理类对象,然后将它赋予代理类中的目标类对象引用,
然后代理类所代理的方法中通过其所包含的目标类对象引用调用目标类的方法,从而实现通过代理调用目标类方法的效果。
静态代理的调用关系
动态代理:
1)动态代理是指 在java程序运行过程(程序已经启动在运行了)由jvm生成代理类的class信息,该class信息生成后是直接处于内存中的,并没有写入磁盘保存起来;
然后通过反射方式实例化代理类对象,因为代理类的class信息已经存在于内存中,所以可以通过反射方式实例化。
这个应该怎么理解呢?
可以跟上面讲过的静态代理对比下,静态代理是需要开发人员自己实现代理类的逻辑的,且代理类的class信息是在程序运行之前就已经可以获取到的了,.java文件经过编译后可以得到.class文件;
而动态代理是不需要开发人员自己实现代理类的,也就是说使用动态代理方式的话,项目代码中是不存在代理类的.java文件的,既然代理类未由开发者实现,那么程序经过编译之后肯定也不会有代理类的.class文件,
也就是说经过编译之后程序未启动运行之前,关于代理类的信息我们一无所知,它是在程序运行过程中需要用到的时候才会由jvm动态生成的,而且生成之后也只存在于内存中,不会写到磁盘保存成.class文件,更加不会保存为.java文件;
在程序重启或者说发生了gc,这个代理类的class信息从内存中被卸载之后,关于这个代理类的信息就没有了,只有当代码再次访问到代理对象时,才又会重新生成代理类的class信息。
2)动态代理与静态代理的区别是什么?
上面已经讲述,不再赘述。
3)为什么需要引入动态代理?
这就不得不说到静态代理的弊端,我们引入新事物,必定是因为旧事物存在不合理之处,所以才引入新的事物来弥补它的缺陷。
那静态代理有什么缺陷呢?
我们知道静态代理是需要开发者自己实现代理类逻辑的,也就是说要对某个类进行代理的话,需要实现这个类相应的代理类;
如果目标类的数量很多的话,代理类的实现也必然得很多,可能会造成代码量过于庞大,可能会增加代码的冗余度...
再者,如果目标类需要代理的方法很多的话,代理类需要对这些方法一一实现代理逻辑,代理类的实现也将会很庞大。
考虑到这些问题,催生了动态代理这种方式,它相比于静态代理来说,由于不需要开发者自己再实现代理类了,所以在实际大型项目中可能代码量会大大减少。
4)动态代理怎么使用呢?
还是以上面计算税后薪资的例子来讲解:
1)首先也是需要定义一个接口,接口里定义好需要被代理的方法:
ppublic interface Employee {
Double calculateSalary(int id);
}
2)然后需要定义目标类并实现上面定义的接口(虽然代理类不用开发者自己实现了,但是目标类还是需要开发者自己写的的,不然人家jvm可不知道你业务需要的逻辑是什么样的,肯定是没法自动给你生成的)
public class EmployeeImpl implements Employee {
/**
* 计算员工的税后薪资
* @param id
* @return
*/
public double calculateSalary(int id) {
if (id == 1){
return 10000*0.8;
} else if (id == 2){
return 8000*0.8;
} else if (id == 3){
return 5000*0.8;
} else {
String err = "员工:" + id + " 不存在,请确认员工信息.";
throw new RuntimeException(err);
}
}
}
3)然后还需要定义一个handler实现jdk的InvocationHandler接口(注意,每个目标类需要对应定义一个handler,通过handler才知道代理类代理的是哪个目标类对象,而且每个目标类的功能增强需求可能都不一样,所以需要每个目标类实现一个handler子类):
InvocationHandler是java.lang.reflect包下提供的接口,专门为动态代理功能提供的。
/**
* 定义了一个EmployeeInvocationHandler实现了jdk提供的InvocationHandler接口
*/
public class EmployeeInvocationHandler implements InvocationHandler {
/**
* 在这个handler实现子类里会声明一个目标类EmployeeImpl成员变量
*/
EmployeeImpl employee;
/**
* 然后实例化handler的时候需要传入一个目标类对象引用来实例化这个成员变量
* @param {[type]} EmployeeImpl employee [description]
*/
public EmployeeInvocationHandler(EmployeeImpl employee){
this.employee = employee;
}
/**
* 实现InvocationHandler接口的invoke方法,这也是InvocationHandler接口唯一的一个方法;
* 后续通过代理类调用被代理的每个方法时,实际上会调用到这个invoke方法;
* 我们可以在这个invoke方法里做一些业务功能增强操作。
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 在调用目标类的calculateSalary方法之前做的操作
*/
System.out.println("当前正在计算员工: " + args[0] + "的税后工资");
/**
* 这里其实调用的就是目标类方法,调用employee这个目标类对象的方法
*/
Double salary = (Double)method.invoke(employee, args);
System.out.println(args[0] + "的工资是: " + salary);
return salary;
}
}
4)定义一个通用的工具类用于实例化代理对象:
/**
* 这个类可以适用于生成各种目标类的代理对象
*/
public class ProxyInstanceFactory {
/**
*
* @param clazz 目标类所对应的接口的Class对象
* @param handler 目标类所对应的handler实现类对象
* @return
*/
public static Object createProxyInstance(Class clazz, InvocationHandler handler) {
/**
* 这里需要借助java.lang.reflect包下提供的Proxy类来创建动态代理类实例,Proxy类跟InvocationHandler一样专门为动态代理功能提供的;
* Proxy类提供了多种方法用于实例化动态代理类对象:
* 1)getProxyClass(ClassLoader loader, Class<?>... interfaces)
* 2)newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
*/
/**
* 这里选择使用newProxyInstance方法实例化代理对象,因为这种方式我个人觉得比较方便
*/
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), handler);
}
}
5)测试动态代理
public class Main {
public static void main(String[] args) {
//创建被代理对象
Employee employee = new EmployeeImpl();
/**
* 创建handler对象,需要传入被代理对象的引用;
* 因为执行handler的invoke方法时需要知道method是哪个对象的
*/
EmployeeInvocationHandler handler = new EmployeeInvocationHandler(employee);
/**
* 创建代理对象。
*
* 注意,亲测:
* 1、如果直接使用接口的Class对象来实例化动态代理对象的话,会报错:
* java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to proxy.dynamic.Employee
* 即不能使用接口Employee的Class对象
* 2、需要改为子类EmployeeImpl的Class对象
*/
Employee proxyInstance = (Employee) ProxyInstanceFactory.createProxyInstance(EmployeeImpl.class, handler);
proxyInstance.calculateSalary(1);
}
}
如果多个或者所有的目标类的功能增强需求是一样的话,那我们其实是不需要每个目标类都对应实现一个handler子类的,如果满足这种场景的话,那我们可以对代码优化一下:
动态代理使用案例优化版本1:
1)除了第三步实现handler子类之外,其他的步骤不变化;
2)对InvocationHandler的实现做了优化,具体如下:
将InvocationHandler实现类也做成通用的,不需要每个目标类都对应实现一个handler子类:
public class CommonInvocationHandler implements InvocationHandler {
Object employee;
public CommonInvocationHandler(Object employee){
this.employee = employee;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在调用目标类的calculateSalary方法之前做的操作
System.out.println("当前正在计算员工: " + args[0] + "的税后工资");
Double salary = (Double)method.invoke(employee, args);
System.out.println(args[0] + "的工资是: " + salary);
return salary;
}
}
可以看到handler实现子类的成员变量的类型变成了Object类型,而不是目标类的类型了;
每个目标类使用的时候再传入具体类型的对象引用即可,因为Object是所有类的基类,这样通过父类引用指向子类对象的方式就可以做到通用,这就是多态性的体现,无需再每个目标类实现一个handler子类。
动态代理使用案例优化版本2:
1)第1、2步不变,将第3、4步合并,第5步发生一点变化,依然是想做到通用;
2)第3、4步创建handler子类和生成代理对象的实现类合并后的具体代码如下:
public class CommonInvocationHandler implements InvocationHandler {
Object targetObj;
public Object bind(Object targetObj){
this.targetObj = targetObj;
return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(), targetObj.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在调用目标类的calculateSalary方法之前做的操作
System.out.println("当前正在计算员工: " + args[0] + "的税后工资");
Double salary = (Double)method.invoke(targetObj, args);
System.out.println(args[0] + "的工资是: " + salary);
return salary;
}
}
可以看到这里定义了一个新的bind方法,在绑定目标类对象的同时创建了代理对象,这样创建代理对象的类就不需要再定义了,所以就是第3、4步合并了,用一个类完成了之前定义两个类才能完成的功能;
3)测试类的变化:
public class Main {
public static void main(String[] args) {
//创建被代理对象
Employee employee = new EmployeeImpl();
/**
* 创建handler对象,需要绑定目标类对象;
* 因为执行handler的invoke方法时需要知道method是哪个对象的;
*/
CommonInvocationHandler handler = new CommonInvocationHandler();
/**
* 绑定目标类对象的同时创建了代理对象proxyInstance
*/
Employee proxyInstance = (Employee) handler.bind(employee);
/**
* 通过代理对象调用calculateSalary方法,做了功能增强逻辑
*/
proxyInstance.calculateSalary(1);
}
}
5)动态代理的原理分析
我们现在来梳理下动态代理的运行原理:
我们创建代理对象是通过Proxy.newProxyInstance()这个方法来创建的,那我们先看下这个方法的实现:
/**
* 重点关注注释的地方
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/**
* 根据传入的类加载器和Class数组对象生成代理类Class对象
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
/**
* Proxy类中定义了:private static final Class<?>[] constructorParams = { InvocationHandler.class };
*
* 通过反射方式获取代理类的带参构造方法对象,constructorParams即是构造方法的参数类型,根据定义,这个参数是InvocationHandler.class
* 也就是代理类的构造方法需要接收一个InvocationHandler子类对象
*/
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
/**
* 设置暴力反射
*/
cons.setAccessible(true);
return null;
}
});
}
/**
* 通过构造方法实例创建代理类对象,并且把业务逻辑中传入的handler对象引用传给构造方法;
* 创建好代理对象后返回
*
*/
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
我们在前面说过,动态代理模式下,代理类的信息是由jvm生成的,直接放在内存中的,它不会主动写入磁盘,所以正常情况下不会有代理类的.class文件,更不会有它的.java文件;
根据上面的代码分析,获取代理类信息主要是通过下面这个逻辑实现的。
Class<?> cl = getProxyClass0(loader, intfs);
那如果是我们确实想看下这个代理类长什么样,我们该怎么办?有办法能够看到这个代理类的class信息甚至它的反编译java代码吗?
答案是可以的。
1)我们只需要在测试类中加上这行代码:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
2)然后运行程序,就会自动在当前项目下生成我们的代理类的.class文件了,文件相对路径是:com/sun/proxy/$Proxy0.class
3)我们打开文件看一下代码信息,idea自带反编译功能,直接打开文件就可以看到java代码:
/**
* 因为这个是代理类的具体实现逻辑,所以每个需要被代理的目标方法在这里肯定是会有定义的
*
* 这个代理类$Proxy0继承了Proxy,并且实现了我们定义的Employee接口,所以这个类里肯定会重写calculateSalary方法
*/
public final class $Proxy0 extends Proxy implements Employee {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
* 这个静态代码块本来是在最下面的,我把它提到前面来,便于分析;
* 这个代码块的逻辑就是通过反射的方式创建指定类的Class对象,然后通过Class对象获取指定的Method实例;
*/
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
/**
* 例如这里通过反射方式创建proxy.dynamic.Employee接口的Class对象(注意是Class对象,不是类对象),
* 然后通过Class获取calculateSalary的Method实例
*/
m3 = Class.forName("proxy.dynamic.Employee").getMethod("calculateSalary", Integer.TYPE);
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
/**
* 这个就是我们刚才在newProxyInstance方法逻辑中分析的代理类的构造方法,
* 可以看到它的参数类型确实是InvocationHandler
*/
public $Proxy0(InvocationHandler var1) throws {
/**
* 我们看类的继承关系可以知道,$Proxy0代理类继承了父类Proxy,
* 这里调用了父类Proxy的构造方法,我们跟进去看看:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* //可以看到父类的构造方法是将传入的handler赋予了它自己的成员变量protected InvocationHandler h;
* //因为$Proxy0继承自Proxy,所以相当于$Proxy0的父类属性h指向了我们传入的handler对象
* this.h = h;
* }
*/
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
/**
* 每个需要被代理的目标方法都会在代理类中有定义,而且实现了代理方法的逻辑;
* 我们创建了代理类对象后,调用的是代理类的calculateSalary方法
*
*/
public final Double calculateSalary(int var1) throws {
try {
/**
* 然后在代理类的calculateSalary方法里通过handler对象引用h调用了它的invoke方法,传入相关参数;
* 其中根据上面的分析,我们知道m3对应的是calculateSalary方法的Method对象,var1是通过代理类对象调用calculateSalary方法时传入的参数。
* 此处之后请直接跳到步骤4接着看分析哈。
*/
return (Double)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
4)我们跳转到handler实现子类看一下:
public class CommonInvocationHandler implements InvocationHandler {
Object targetObj;
public Object bind(Object targetObj){
this.targetObj = targetObj;
return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(), targetObj.getClass().getInterfaces(), this);
}
/**
* 在代理类的calculateSalary逻辑中调用了handler的invoke方法,并且把Method对象传进来,
* 然后handler invoke方法的关键逻辑是:method.invoke(targetObj, args);
* method对象调用invoke方法时指定target对象,就知道调用的是哪个对象的calculateSalary方法;
* 所以这也是我们为什么需要在handler子类中定义一个对象引用的原因。
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在调用目标类的calculateSalary方法之前做的操作
System.out.println("当前正在计算员工: " + args[0] + "的税后工资");
Double salary = (Double)method.invoke(targetObj, args);
System.out.println(args[0] + "的工资是: " + salary);
return salary;
}
}
5)通过上述的流程分析就能把动态代理对象的调用链给串起来,现在总结一下:
首先实例化目标类对象:Employee employee = new EmployeeImpl();
接着实例化handler子类对象:CommonInvocationHandler handler = new CommonInvocationHandler();
并将handler对象绑定一个目标类对象实例,因为handler内部需要用到目标类对象;
3)接着创建代理类对象:Employee proxyInstance = (Employee) handler.bind(employee);
在handler绑定目标类对象之后立即创建代理类对象并返回;
4)创建代理类对象需要传入目标类对应的类加载器、目标类对应的interfaces数组以及handler子类对象;
5)这样就能得到一个代理对象,然后通过代理对象调用代理方法。
6)动态代理类的类名的命名规则是:
com.sum.proxy.$Proxy0、com.sum.proxy.$Proxy1、com.sum.proxy.$Proxy2、...
后面的数字表示编号。
代理对象里有handler引用,代理类的方法逻辑都是调用的handler对象的invoke方法,handler的invoke方法又是调用的method的invoke方法,调用method.invoke方法时需要指定本次方法调用对应的是哪个对象,这个对象是目标类对象;
就这样,在代理类和目标类之间,通过handler的中转,最终实现了通过代理类调用到目标类的方法。
动态代理的调用关系
动态代理总结:
1)动态代理也需要定义接口
2)也需要自己实现目标类
3)还需要定义一个子类实现InvocationHandler接口,handler相当于一个介于代理类与目标类之间的桥梁,代理类对象通过handler引用来调用目标类对象的方法
4)动态代理类信息在运行期间生成,没有.class文件,没有.java文件(除非指定系统参数,让jvm把生成的class信息以.class文件的形式保存下来),所以你调试代码的话,想要跳到代理类的源码是跳不了的,因为这个.java源码不存在
5)与静态代理相比,如下图可以看到,它们都需要定义接口,并且最终都实现了接口,可以看出来二者的层级都是两层,关系平等啊。
CGLIB代理后续有时间补充,可能会通过另一篇文章发布。。。
java代理模式的简单总结:
1)代理模式的作用是通过代理对象调用目标对象的方法,并在目标对象的方法调用前后做一些操作以满足业务场景的需求;
2)当代码中有较多的地方需要在某个目标类的方法调用前后做些什么操作时可以考虑选择使用代理模式;
3)在业务逻辑中我们不希望改变调用方法名,但是又希望增强方法的功能,此时就可以考虑使用代理模式;
它不会改变调用方法的名称,只不过改为由代理对象来调用,且它调用的方法名与目标类的方法名一样,但是实际上代理类的方法里在调用目标类方法的前后可能已经添加了某些操作;
看起来调用名称没有变化,但是实际上通过代理模式已经对该方法的功能增强了(在调用目标类的方法前后可能加了一些业务逻辑)。
什么是功能增强呢?哪些操作才算功能增强?
比如我上面提到过的一个实际场景的例子,在一个项目中,其中有两个最为关键的接口:上传和下载。对于数据上传和下载的关键方法,我们总是会非常关注它的性能的,希望能够监控它每次执行所消耗的时间,以此评估它在一段时间内的波动如何;
但是呢,我们这些方法定义好之后,我们不想在里面掺入杂质,上传的就做上传的事儿,下载的就做下载的事儿,每个定义好的方法都有自己的使命,干好自己的事情就好,可以不用关心其他的事;
既然不在方法内部统计这个耗时,那应该在哪里统计,所以我们引入了代理,通过代理类的同名方法来做这个计算操作,使得我们最终在不需要改变目标类方法,在不需要掺入杂质的情况下就可以做到不仅可以实现上传、下载...
还可以计算出该方法调用所用的时间。
这就是所谓的功能增强,本来直接调用目标类方法只能实现上传或者下载等,但是现在通过代理类方法调用之后,不仅可以实现上传、下载了,连方法的耗时也可以计算出来了,功能不是强化了吗。
至于哪些操作算是功能增强操作,这个取决于自己对于场景的理解吧,但是经常用到的有在目标类方法调用前后输出相关日志、在调用目标类方法之前校验权限、计算执行目标类方法的耗时等。
网友评论