0
点赞
收藏
分享

微信扫一扫

动态代理、静态代理(易懂有栗子)

桑二小姐 2022-03-31 阅读 48

有问题嘀嘀作者哦​每天分享技术栈,开发工具等​代理(Proxy)是JavaSE基础知识中比较重要但同时也是比较抽象难懂的一个技术。代理又分为静态代理和动态代理,尤其动态代理是重点,在面试中经常被问到,所以是希望兄弟们能够对代理技术有所认识,为此我会在后面还会有详细的说道说道。

什么是代理

Java中的代理和我们生活中所说的代理本质上是一个意思,Java只是基于程序去实现了。

生活示例

生活示例一你搞了一个发明,想申请一个专利,可是你不知道该怎么申请专利,这个时候你找到专利代理机构帮你申请,代理机构帮你填写申请材料并提交到专利局直到专利审批成功。  在这个过程中你就是“委托人”,这个专利代理机构就是“代理人”,你找到代理人把相关材料给他,他帮你去申请专利,整个过程你不用直接出面。生活示例二你要买一个二手房,你要是自己找房源、谈判、过户的话,这一套程序太麻烦。往往你会找一个房屋中介,中介帮你找房源、帮你谈判、帮你跑过户手续。在这个过程中你就是“委托人”,这个房屋中介就是“代理人”,你只需要找到房屋中介把你的购房需求、相关材料、身份证等给他,他就可以帮你完成后续过程,你不用跑来跑去。而且,中介在帮你干完正事(指买房)之后往往还能干一些别的事情(和买房无关的其他事情),比如把你的个人信息卖给了其它人(信息泄露目前很普遍)。上述示例分析1. 有两个角色:委托人和代理人2. 委托人和代理人要达到的目的一样(申请专利或者购买房子)3. 委托人需要去找到代理人(要让代理人帮忙干活,肯定得先找到它)4. 主要是代理人抛头露面,委托人在后台

生活示例的Java代码实现

我们现在按照上面的分析,使用Java实现“购买二手房”的例子定义一个接口,这个接口定义要做的事情。(对应上面第2点分析中的目的一样)

动态代理、静态代理(易懂有栗子)_代理类

定义委托类和代理类,这两个类都实现上面的接口。(对应上面第1和第2点分析)动态代理、静态代理(易懂有栗子)_java_02动态代理、静态代理(易懂有栗子)_代理类_03在代理类中定义了一个IBuyHouse类型的变量,可以这么理解,虚位以待等着委托人上门。(对应上面第3点分析:委托人需要去找到代理人)测试

动态代理、静态代理(易懂有栗子)_动态代理_04

结果

动态代理、静态代理(易懂有栗子)_java_05

小结

上述代码就是java中的代理实现,我们可以发现实现代理需要这么几个组件,如下图所示动态代理、静态代理(易懂有栗子)_java_06

接口类(Subject,如IBuyHouse)委托类(RealSubject,如HouseDelegation)代理类(Proxy,如HouseAgent)代理类持有委托类(即引用委托类)(如在HouseAgent中定义了一个变量delegation)

总结

Java中使用代理技术主要用于扩展原功能又不侵入(修改)源代码比如想在某个类的某个方法执行之前打印一下日志或者记录下开始时间,但是又不好将打印日志和时间的逻辑写入原来的方法里。这时就可以创建一个代理类实现和原方法相同的方法,通过让代理类持有真实对象,然后代码调用的时候直接调用代理类的方法,来达到增强业务逻辑的目的。如我们的例子动态代理、静态代理(易懂有栗子)_java_05

静态代理

上述这种程序员创建代理类,即在程序运行前代理类的.class文件就已经存在了的形式叫做静态代理静态代理是有缺点的,如下假如现在有一个买车的需求,得去找二手车中介(瓜子二手车直卖网),那么把这个功能加载程序中首先定义接口和委托类必不可少,如下接口类

动态代理、静态代理(易懂有栗子)_java_08

委托类

动态代理、静态代理(易懂有栗子)_动态代理_09

然后我们又需要去定义一个代理类了那如果有另外10个,100个,1000个不同的委托类而且委托的方法还不一样呢?我们就去定义10个,100个,1000个代理类吗????定义这么多代理类,好low好费劲……….. 也就是说因为代理对象需要与目标对象实现一样的接口所以会有很多代理类类太多。而且一旦接口增加方法目标对象与代理对象都要维护 那怎么办呢,动态代理出场!!! 通过动态代理技术:最重要的是拿到一个代理对象,这个代理对象可以有相比于原来的业务增强逻辑

动态代理

注意:使用动态代理,我们最大的改变就是不需要定义一个个的代理类了。最重要的是获取到代理对象,有了代理对象,我们就可以直接调用代理对象了。往下看怎么回事

JDK动态代理

JDK动态代理不仅可以代理有接口有实现类的情况,也可以代理只有接口没有实现类的情况。使用JDK动态代理无需引入任何外部的jar包,JDK已经给我们提供了一种获取代理对象的API,只需要我们传入相关信息,它就可以返回我们需要的代理对象

Object obj = Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)

obj就是我们要拿到的代理对象需要传入的三个参数ClassLoader:这是一个类加载器,是java虚拟机底层的玩意,固定的写法,如下:当前类名.class.getClassLoader()或者 委托类对象.getClass().getClassLoader() ​interfaces​:是一个数组,因为代理类也是类,类是可以实现多个接口的,你想让这个代理对象实现什么接口你就传入即可,也正因为有接口这个参数,所以使用JDK动态代理必须有接口存在​      比如​new Class[]{IBuyCar.class,IBuyHouse.class} 或者委托类对象.getClass().getInterfaces()​InvocationHandler​:这个参数是帮我们增强业务逻辑的,也就是说增加额外功能的,你在​interfaces​中指定的所有接口的接口方法都可以在这里针对性的增强,​InvocationHandler​是一个接口,我们需要实现在这里实现这个接口,在实现类中写我们增强逻辑

有接口有委托类的情况

比如对咱们上面示例的买二手车和买二手房的情况进行改造。定义一个​InvocationHandler接口的实现,用于增加额外功能逻辑

import java.lang.reflect.InvocationHandler;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyProxyHasDelegation implements InvocationHandler {

// 把委托对象传递进来进行增强
private Object object;

public MyProxyHasDelegation(Object object) {
this.object = object;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

Object result = null;

// 判断是哪个接口的代理对象
String interf = method.getDeclaringClass().getName();
if("IBuyCar".equalsIgnoreCase(interf)) {
if(method.getName().equalsIgnoreCase("buyCar")) {
System.out.println("我是二手车中介@@@,请支付5000元服务费");
// 触发原来的业务逻辑
result = method.invoke(object,args);
System.out.println("我是二手车中介@@@,把委托人的信息卖钱,挣点外快");
}
}


if("IBuyHouse".equalsIgnoreCase(interf)) {
if(method.getName().equalsIgnoreCase("buyHouse")) {
System.out.println("我是二手房中介###,请支付5000元服务费");
// 触发原来的业务逻辑
result = method.invoke(object,args);
System.out.println("我是二手房中介###,把委托人的信息卖钱,挣点外快");
}
}

return result; // 如果原有业务逻辑有返回值别忘了返回
}
}
if(method.getName().equalsIgnoreCase("buyCar")) {
}
}
}

获取代理对象,并调用代理对象

public static void main(String[] args) {
//=====================获取IBuyHouse的代理对象===================
HouseDelegation houseDelegation = new HouseDelegation();
// 针对houseDelegation进行增强,获取代理对象
IBuyHouse iBuyHouse = (IBuyHouse)Proxy.newProxyInstance(houseDelegation.getClass().getClassLoader(),
houseDelegation.getClass().getInterfaces(),new MyProxyHasDelegation(houseDelegation));
// 使用代理对象的功能
iBuyHouse.buyHouse();

//=====================获取IBuyCar的代理对象===================
CarDelegation carDelegation = new CarDelegation();
// 针对carDelegation进行增强,获取代理对象
IBuyCar iBuyCar = (IBuyCar)Proxy.newProxyInstance(carDelegation.getClass().getClassLoader(),
carDelegation.getClass().getInterfaces(),new MyProxyHasDelegation(carDelegation));
// 使用代理对象的功能
iBuyCar.buyCar();

}

总结同样进行了增强,是不是代理类不见了呢!!!!这就是动态代理的好处,不需要你定义代理类了,你只需要能拿到代理对象就可以。

仅有接口的情况

假如说上面我们只定义了IBuyCar接口和IBuyHouse接口,没有委托类(实现类),也是可以玩的。定义一个​InvocationHandler接口的实现,用于写业务逻辑,你把所有的业务逻辑写在invoke方法中就行了

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyProxyNoDelegation implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;

// 判断是哪个接口的代理对象
String interf = method.getDeclaringClass().getName();
if("IBuyCar".equalsIgnoreCase(interf)) {
System.out.println("我是二手车中介@@@,请支付5000元服务费");
System.out.println("第一步:找车源");
System.out.println("第三步:车辆检查");
System.out.println("第二步:谈判");
System.out.println("第三步:车辆过户");
System.out.println("我是二手车中介@@@,把委托人的信息卖钱,挣点外快");

}
if("IBuyHouse".equalsIgnoreCase(interf)) {
System.out.println("我是二手房中介###,请支付5000元服务费");
System.out.println("第一步:找房源");
System.out.println("第二步:谈判");
System.out.println("第三步:房屋过户");
System.out.println("我是二手房中介###,把委托人的信息卖钱,挣点外快");
}


return null;
}
}

测试

public static void main(String[] args) {

Object object = Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{IBuyHouse.class, IBuyCar.class},
new MyProxyNoDelegation());


// 上面定义一次代理对象,下面针对两个接口都可以使用
((IBuyCar)object).buyCar();
((IBuyHouse)object).buyHouse();
}

总结业务逻辑从无到有不也是一种增强嘛!是不是代理类不见了而且连实现类都不需要了呢!!!!这就是我们Mapper动态代理的底层原理(只要定义接口,不需要写实现类)

CGLIB动态代理

Java中的动态代理包括JDK动态代理和CGLIB动态代理。使用这两种代理方式我们都可以不用定义代理类,区别在于使用JDK动态代理必须有一个接口类,使用CGLIB动态代理不需要接口类。所以如果你要对一个实现了接口的类进行业务增强就用JDK动态代理,如果就对一个普通类进行业务增强就用CGLIB动态代理。如下cglib是第三方jar,因此需要引入坐标

<dependency>   
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>

比如有一个委托类(业务类),无需实现接口(当然,实现接口也可以,不影响)

public class Book {      
public void addBook() {
System.out.println("新增图书...");
}
}

获取代理对象测试

public static void main(String[] args) {
Book book = new Book();


// 获取book对象的代理对象,
// Enhancer类似于JDK动态代理中的Proxy
// 通过实现接口MethodInterceptor能够对book中各个方法进行拦截增强,类似于JDK动态代理中的InvocationHandler
Book bookProxy = (Book)Enhancer.create(book.getClass(), new MethodInterceptor() {
Object obj = null;
// 和JDK动态代理的invoke方法一样,都是对业务逻辑的增强
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("日志开始........................");
obj = method.invoke(book,objects);
System.out.println("日志结束........................");
return obj;
}
});


bookProxy.addBook();
}

运行结果:

动态代理、静态代理(易懂有栗子)_代理类_10

总结

你要清晰的明白

  • 使用代理技术就是为了帮我们在不入侵原有代码的情况下增强业务逻辑

  • 你完全可以使用静态代理一个一个去定义代理类,但是这样的话太过于繁琐,而且有些情况下你不知道未来会有什么接口(比如咱们的Mybatis,你现在有个UserMapper.java,以后还可能有更多其他的Mapper接口,这些都是不确定的),所以就是用动态代理去给他们生成代理对象吧

  • 有接口就用JDK动态代理,没有接口就用CGLIB动态代理

    ​SVN(小乌龟)的基本使用和常用操作以及安装教程2020-10-29动态代理、静态代理(易懂有栗子)_代理类_11


​​Git的基本使用和常用操作以及安装教程2020-11-01动态代理、静态代理(易懂有栗子)_代理类_12 ​​


END
如果看到这里,说明你喜欢这篇文章,请 转发、点赞。同时本公众号可以第一时间接受到博文推送。


举报

相关推荐

0 条评论