代理,反射,AOP
创始人
2024-02-20 00:03:44
0

这篇文章主要讲三个点
1.设计模式中的代理模式
2.JAVA中的反射,因为用到了动态代理,这里举一下JDK代理和GCLIB代理的例子
3.介绍一下spring的aop是怎么用到了代理


1.设计模式中的代理模式


代理模式解决的问题:
在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
和适配器模式的区别:
适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

静态代理:
静态代理的实现思想就是说代理类持有一个被代理类
以租房为例,我们一般用租房软件、找中介或者找房东。这里的中介就是代理者。

首先定义一个提供了租房方法的接口。

public interface IRentHouse {void rentHouse();
}

定义租房的实现类

public class RentHouse implements IRentHouse {@Overridepublic void rentHouse() {System.out.println("租了一间房子。。。");}
}

我要租房,房源都在中介手中,所以找中介

public class IntermediaryProxy implements IRentHouse {private IRentHouse rentHouse;public IntermediaryProxy(IRentHouse irentHouse){rentHouse = irentHouse;}@Overridepublic void rentHouse() {System.out.println("交中介费");rentHouse.rentHouse();System.out.println("中介负责维修管理");}
}

这里中介也实现了租房的接口。

再main方法中测试

public class Main {public static void main(String[] args){//定义租房IRentHouse rentHouse = new RentHouse();//定义中介IRentHouse intermediary = new IntermediaryProxy(rentHouse);//中介租房intermediary.rentHouse();}
}

动态代理:
随着业务复杂度的增加,我们不可能创建非常多的代理类,对每一个业务都增加一个代理,就要提供通用的代理方法,这就要通过动态代理来实现了

动态代理是反射的一个非常重要的应用场景。动态代理常被用于一些 Java 框架中。例如 Spring 的 AOP ,Dubbo 的 SPI 接口,就是基于 Java 动态代理实现的。

动态代理的方式有两种:

JDK动态代理

利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
优点

JDK动态代理是JDK原生的,不需要任何依赖即可使用;
通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快;
缺点

如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;
JDK动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面,Spring仍然会使用JDK的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。
JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;

CGLIB动态代理

利用asm开源包,对代理对象类的class文件加载进来**,通过修改其字节码生成子类来处理。**
优点
使用CGLib代理的类,不需要实现接口,因为CGLib生成的代理类是直接继承自需要被代理的类;
CGLib生成的代理类是原来那个类的子类,这就意味着这个代理类可以为原来那个类中,所有能够被子类重写的方法进行代理;
CGLib生成的代理类,和我们自己编写并编译的类没有太大区别,对方法的调用和直接调用普通类的方式一致,所以CGLib执行代理方法的效率要高于JDK的动态代理;
缺点
由于CGLib的代理类使用的是继承,这也就意味着如果需要被代理的类是一个final类,则无法使用CGLib代理;
由于CGLib实现代理方法的方式是重写父类的方法,所以无法对final方法,或者private方法进行代理,因为子类无法重写这些方法;
CGLib生成代理类的方式是通过操作字节码,这种方式生成代理类的速度要比JDK通过反射生成代理类的速度更慢
JDK动态代理和CGLIB字节码生成的区别?

JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。
因为是继承,所以该类或方法最好不要声明成final 。

JDK动态代理:
在 Java 的动态代理机制中,有两个重要的类(接口),一个是 InvocationHandler 接口、另一个则是 Proxy 类,这一个类和一个接口是实现我们动态代理所必须用到的。

public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}

我们来看看 InvocationHandler 这个接口的唯一一个方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy - 代理的真实对象。
method - 所要调用真实对象的某个方法的 Method 对象
args - 所要调用真实对象某个方法时接受的参数

Proxy 这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

public static Object newProxyInstance(ClassLoader loader, Class[] interfaces,  InvocationHandler h)  throws IllegalArgumentExcept

这个方法的作用就是得到一个动态的代理对象。

参数说明:

loader - 一个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载。
interfaces - 一个 Interface 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h - 一个 InvocationHandler 对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个 InvocationHandler 对象上

JDK动态代理示例
首先我们定义了一个 Subject 类型的接口,为其声明了两个方法:

public interface Subject {void hello(String str);String bye();
}

接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject 类:

public class RealSubject implements Subject {@Overridepublic void hello(String str) {System.out.println("Hello  " + str);}@Overridepublic String bye() {System.out.println("Goodbye");return "Over";}
}

下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:

public class InvocationHandlerDemo implements InvocationHandler {// 这个就是我们要代理的真实对象private Object subject;// 构造方法,给我们要代理的真实对象赋初值public InvocationHandlerDemo(Object subject) {this.subject = subject;}@Overridepublic Object invoke(Object object, Method method, Object[] args)throws Throwable {// 在代理真实对象前我们可以添加一些自己的操作System.out.println("Before method");System.out.println("Call Method: " + method);// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用Object obj = method.invoke(subject, args);// 在代理真实对象后我们也可以添加一些自己的操作System.out.println("After method");System.out.println();return obj;}
}

最后,来看看我们的 Client 类:

public class Client {public static void main(String[] args) {// 我们要代理的真实对象Subject realSubject = new RealSubject();// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的InvocationHandler handler = new InvocationHandlerDemo(realSubject);/** 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上*/Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);System.out.println(subject.getClass().getName());subject.hello("World");String result = subject.bye();System.out.println("Result is: " + result);}
}

GCLIB代理:
目标类(一个公开方法,另外一个用final修饰):

public class Dog{final public void run(String name) {System.out.println("狗"+name+"----run");}public void eat() {System.out.println("狗----eat");}
}

方法拦截器

public class MyMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("这里是对目标类进行增强!!!");//注意这里的方法调用,不是用反射哦!!!Object object = proxy.invokeSuper(obj, args);return object;}  
}

反射源码解析


下面我们来看看 JDK 的 invoke 方法到底做了些什么。

进入 Method 的 invoke 方法我们可以看到,一开始是进行了一些权限的检查,最后是调用了 MethodAccessor 类的 invoke 方法进行进一步处理,如下图红色方框所示。
在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

Method 类的 invoke 方法整个流程可以表示成如下的时序图:
在这里插入图片描述


spring的aop是怎么用到了代理


举个例子,如果我想要写一个注解,在方法执行前后各做一些操作,这时候就可以使用切面变成的方式去进行,这中间一样用到了动态代理,时间原因就不多写了

@Aspect
public class TestAspect {@Pointcut("execution(* *(..))")public void myPointcut() {}// 环绕通知:方法执行前后添加额外功能@Around(value = "myPointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("方法执行前打印~");Object result = joinPoint.proceed();System.out.println("方法执行后打印~");return result;}
}

上一篇:ZPM介绍(3)

下一篇:Redis的AOF持久化

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...