点赞再看,养成习惯,听说微信搜公众号《Java鱼仔》会让自己的技术更上一层楼
(一)什么是代理?
在生活中经常会遇到代理,比如买房我们是去找中介,而不是自己一栋楼一栋楼去挑选,这里的中介就是代理。代理即通过代理对象访问目标对象,还可以在目标对象基础上增强额外的功能。java的代理分为静态代理和动态代理。静态代理即在代码运行前,代理类就已经存在了。动态代理指代理类不是写在代码中的,而是在运行过程中产生的。
(二)静态代理
静态代理中有以下几个角色:
抽象对象:一般是使用接口
真实角色:被代理的角色(房东)
代理角色:代理真实角色,顺便会带一些附加操作(中介)
客户:访问代理角色的人(租客)
静态代理就是在代码运行之前,代理类就已经存在了。通过一个实例来模拟静态代理;:
以租房为例,租房有两种方式,一种是直接找房东去租房,另一种是找租房软件,第二种就是通过代理访问目标对象。
首先新建一个抽象对象(租房的接口),只带一个rent方法:
//抽象角色
public interface Rent {
void rent();
}
然后需要一个真实对象(房东)
//真实角色(房东)
public class Landlord implements Rent{
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
接着是代理角色(中介),因为要帮房东租房,所以也需要继承Room接口,代理角色在卖房的同时,还可以做一些自己的事情
//代理角色(中介)
public class Proxy implements Rent{
private Landlord landlord;
public Proxy(){
}
public Proxy(Landlord landlord) {
this.landlord = landlord;
}
@Override
public void rent() {
System.out.println("收服务费");
landlord.rent();
System.out.println("收中介费");
}
}
最后是由客户去访问中介实现租房
//客户
public class Client {
public static void main(String[] args) {
Landlord landlord=new Landlord();
Proxy proxy = new Proxy(landlord);
proxy.rent();
}
}
然后观察结果:
收服务费
房东出租房子
收中介费
静态代理缺点是,一个真实的角色就需要一个代理角色,代码量会大大增加。
(三)动态代理
动态代理指代理类不是写在代码中的,而是在运行过程中产生的。java提供了两种实现动态代理的方式,分别是基于Jdk的动态代理和基于Cglib的动态代理。
(3.1) 基于Jdk的Proxy
首先介绍基于Jdk的Proxy,继续使用租房的案例:
新建一个ProxyInvocationhandler 类,这个类是动态代理的核心部分,需要继承InvocationHandler 接口。
InvocationHandler 是一个接口,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类invoke,由它实现处理内容。
通过Proxy.newProxyInstance可以生成得到代理类。
通过上面几个知识点我们就可以写出一个通用的动态代理类了:
public class ProxyInvocationhandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target){
this.target=target;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过反射执行被代理的方法
Object result = method.invoke(target, args);
return result;
}
}
接下来是客户去调用:首先是代理对象去代理真实的对象,然后通过getProxy()方法生成动态的代理类,最后执行被代理对象的方法。
public class Client2 {
public static void main(String[] args) {
//真实对象
Landlord landlord=new Landlord();
//代理角色,不存在
ProxyInvocationhandler proxyInvocationhandler=new ProxyInvocationhandler();
//设置要代理的对象
proxyInvocationhandler.setTarget(landlord);
//动态生成代理类(注意使用的是接口)
Rent proxy = (Rent) proxyInvocationhandler.getProxy();
proxy.rent();
}
}
当调用proxy.rent()方法时,会自动执行invoke方法,以实现动态代理。
最后结果如下:
房东出租房子
运行时就会在控制台上打印出来,我们还可以在rent()方法执行前后做一些自己的操作,这也是Spring AOP的核心
JDK动态代理类实现了InvocationHandler接口,重写的invoke方法。
JDK动态代理的基础是反射机制(method.invoke(对象,参数))Proxy.newProxyInstance()
之前我讲静态代理的时候说静态代理的缺点在于对于每一个被代理的对象,都需要建一个代理类。因为静态代理是在项目运行前就写好的。但是动态代理就不是这样,由于动态代理在运行时才创建代理类,因此只需要写一个动态代理类就好。比如我再创建一个被代理的对象卖房:
写一个通用接口Sell
public interface Sell {
void sellRoom();
}
接着还是写一个被代理对象的类:
public class RealSell implements Sell {
public void sellRoom() {
System.out.println("房东要卖房了");
}
}
接下来在main方法中执行动态代理
public static void main(String[] args) {
//真实对象
RealSell realSell=new RealSell();
//代理角色,不存在
ProxyInvocationhandler proxyInvocationhandler=new ProxyInvocationhandler();
//设置要代理的对象
proxyInvocationhandler.setTarget(realSell);
//动态生成代理类
Sell proxy = (Sell) proxyInvocationhandler.getProxy();
proxy.sellRoom();
}
最终实现结果如下:
房东要卖房了
通过动态代理,我可以通过一个动态代理类,去代理多个对象。
(3.2) 基于CGlib的动态代理
基于Jdk的动态代理局限性在于代理的类必须要实现接口,而基于CGlib的动态代理则没有这个限制:
搭建CGlib环境我们首先要引入一个CGlib的jar包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
此时我们不再需要接口,直接新建一个CGRoom类:
public class CGRoom {
public void rent(String roomName){
System.out.println("租了"+roomName);
}
}
CGlib实现动态代理的核心在于MethodInterceptor接口:
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理执行之前:"+method.getName());
Object object=methodProxy.invokeSuper(o,objects);
System.out.println("代理执行之后:"+method.getName());
return object;
}
这个接口只有一个intercept()方法,这个方法有4个参数:
1)表示增强的对象,即实现这个接口类的一个对象;
2)表示要被拦截的方法;
3)表示要被拦截方法的参数;
4)表示要触发父类的方法对象;
最后生成代理类对象并输出执行结果
public static void main(String[] args) {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer=new Enhancer();
//设置目标类的字节码文件
enhancer.setSuperclass(CGRoom.class);
//设置回调函数
enhancer.setCallback(new MyMethodInterceptor());
//创建代理对象
CGRoom proxy= (CGRoom) enhancer.create();
proxy.rent("碧桂园");
}
运行结果:
代理执行之前:rent
租了碧桂园
代理执行之后:rent
(四)总结
JDK动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承,所以JDK动态代理只能代理接口);CGLIB能够代理普通类;