静态代理和动态代理的区别是什么呢,静态代理和动态代理的区别是什么和什么
如何解决写爬虫IP受阻的问题?立即使用。
相关:《编程视频课程》
首先,代理的概念
为对象提供代理以控制对此对象的访问。代理类和委托类有一个共同的父类或父接口,因此只要使用委托类对象,就可以替换代理对象。代理负责预处理和过滤请求,将请求分派给委托类进行处理,以及委托类执行请求后的后续处理。相关:《Java视频教程》
图1:代理模式
从图中可以看出,代理接口(Subject)、代理类(ProxySubject)和委托类(RealSubject)组成了一个“货”字结构。根据代理类的产生时间,代理可以分为静态代理和动态代理。静态代理和动态代理通过下面的模拟需求来说明:delegate类需要处理一个长时间的任务,client类需要打印出执行任务所花费的时间。要解决这个问题,需要记录任务执行前后的时间,两个时间差就是任务执行所消耗的时间。第二,静态代理
程序员创建或工具来生成代理类的源代码,然后编译代理类。Static是程序运行前就存在的代理类的字节码文件,代理类和委托类的关系是在程序运行前确定的。
清单1:代理接口
/**
*代理接口。用给定的名称处理任务。
*/
公共接口主题{
/**
*用给定的名称执行任务。
* @param taskName任务名称
*/
public void deal task(String taskName);
}清单2:委托类,具体处理业务。
/**
*实际执行任务并实现代理接口的类。
*/
公共类RealSubject实现Subject {
/**
*用给定的名称执行任务。在这里打印出任务的名称,睡眠500ms,模拟任务执行了很长时间。
* @param任务名
*/
@覆盖
public void deal task(String taskName){
System.out.println(执行任务: taskName );
尝试{
thread . sleep(500);
} catch (InterruptedException e) {
e . printstacktrace();
}
}
}清单3:静态代理类
/**
*代理类,实现代理接口。
*/
公共类ProxySubject实现Subject {
//代理类保存委托类的对象引用。
私人主题代表;
公共代理Subject(Subject delegate) {
this.delegate=delegate
}
/**
*将请求分配给委托类执行,并记录任务执行前后的时间。时间差就是任务的处理时间。
*
* @param任务名
*/
@覆盖
public void deal task(String taskName){
long stime=system . current time millis();
//将请求分派给委托类进行处理
delegate . deal task(taskName);
long ftime=system . current time millis();
System.out.println(耗时(ftime-stime)毫秒);
}
}清单4:生成静态代理类工厂
公共类SubjectStaticFactory {
//客户端类调用这个工厂方法来获取代理对象。
//对于customer类,不知道返回的是代理类对象还是委托类对象。
公共静态主题getInstance(){
返回新的proxy subject(new real subject());
}
}
清单5:客户类
公共类Client1 {
公共静态void main(String[] args) {
subject proxy=subjectstaticfactory . getinstance();
proxy . deal task( DBQueryTask );
}
}静态代理类的优点和缺点
优点:业务类只需要关注业务逻辑本身,保证了业务类的可重用性。这是代理的共同优势。
缺点:
1)代理对象的接口只服务于一种类型的对象。如果有很多方法要代理,就需要为每个方法代理。静态代理在程序规模稍大的情况下是无法胜任的。
2)如果一个方法被添加到接口,所有的代理类需要实现这个方法以及所有的实现类。这增加了代码维护的复杂性。
第三,动态代理
动态代理类的源代码是JVM在程序运行过程中根据反射机制动态生成的,所以不存在代理类的字节码文件。代理类和委托类之间的关系是在程序运行时确定的。
1.我们先来看看与动态代理密切相关的Java API。
1)java.lang.reflect.Proxy
这是Java动态代理机制生成的所有动态代理类的父类。它提供了一组静态方法来为一组接口动态生成代理类及其对象。
清单6:Proxy类的静态方法
//方法1:该方法用于获取与指定代理对象关联的调用处理器。
静态InvocationHandler getInvocationHandler(对象代理)
//方法2:该方法用于获取与指定的类加载器和一组接口相关联的动态代理类的类对象。
静态类getProxyClass(Class loader loader,Class[]接口)
//方法3:该方法用于判断指定的类对象是否为动态代理类。
静态布尔is proxy Class(cl类)
//方法4:该方法用于为指定的类加载器、一组接口和调用处理器生成动态代理类实例。
静态对象new proxy instance(Class loader loader,Class[] interfaces,invocation handler h)2)Java . lang . reflect . invocation handler
这是调用处理程序接口,它定义了一个invoke方法来集中处理动态代理类对象上的方法调用,通常在这个方法中实现代理访问来委托类。每次生成动态代理类对象时,都应该指定相应的调用处理程序对象。
清单7:InvocationHandler的核心方法
//此方法负责集中处理动态代理类上的所有方法调用。第一个参数是代理类实例,第二个参数是被调用的方法对象。
//第三种方法是调用参数。根据这三个参数,调用处理器将它们预处理或分派给委托类实例,以便反射执行。
对象调用(对象代理、方法方法、对象[]参数)3)java.lang.ClassLoader
这是一个类加载器类,它负责将类的字节码加载到Java虚拟机(JVM)中,并在使用类之前为其定义类对象。静态方法生成的动态代理类也需要由类加载器加载后才能使用。它与普通类的唯一区别是,它的字节码是由JVM在运行时动态生成的,而不是预先存储在任何。类文件。
每次生成动态代理类对象时,都需要指定一个类加载器对象。
2.动态代理的实现步骤
具体步骤是:
A.实现InvocationHandler接口来创建自己的调用处理程序
b .为代理类提供类加载器和代理接口类型数组,以创建动态代理类
c .以调用处理器类型为参数,使用反射机制获取动态代理类的构造函数。
d .以调用处理器对象为参数,使用动态代理类的构造函数创建动态代理类对象。
清单8:分步骤实现动态代理
//InvocationHandlerImpl实现了InvokionHandler接口,可以实现代理类对委托类的方法调用的调度和转发。
//它通常包含对委托类实例的引用,用于实际执行调度转发的方法调用。
InvocationHandler=new InvocationHandler impl(.);
//通过代理为包括接口接口的一组接口动态创建代理类的类对象。
Class clazz=proxy . getproxyclass(Class loader,new Class[] { Interface.class,});
//通过反射从生成的类对象中获取构造函数对象
constructor constructor=clazz . get constructor(new Class[]{ invocation handler . Class });
//通过构造函数对象创建动态代理类实例
Interface Proxy=(Interface)constructor . new instance(新对象[]{ handler });proxy类的静态方法newProxyinstance封装了上述具体步骤的后三步,简化了获取动态代理对象的过程。清单9:简化后的动态代理实现
//InvocationHandlerImpl实现了InvokionHandler接口,可以实现代理类对委托类的方法调用的调度和转发。
InvocationHandler=new InvocationHandler impl(.);
//直接通过代理创建动态代理类实例
Interface proxy=(Interface)proxy . newproxyinstance(class loader,
new Class[] { Interface.class },handler);3.动态代理实现的示例
清单10:创建自己的调用处理器
/**
*对应于动态代理类的调用处理程序类
*/
公共类SubjectInvocationHandler实现InvocationHandler {
//代理类保存委托类的对象引用。
私有对象委托;
公共SubjectInvocationHandler(对象委托){
this.delegate=delegate
}
@覆盖
公共对象调用(对象代理、方法方法、对象[]参数)抛出Throwable {
long stime=system . current time millis();
//使用反射机制将请求分派给委托类进行处理。方法的调用返回对象对象作为方法执行的结果。
//因为示例程序没有返回值,所以这里忽略返回值处理。
method.invoke(delegate,args);
long ftime=system . current time millis();
System.out.println(耗时(ftime-stime)毫秒);
返回null
}
}清单11:生成动态代理对象的工厂,工厂方法列出了如何生成动态代理类对象的步骤。
/**
*生成动态代理对象的工厂。
*/
公共类DynProxyFactory {
//客户端类调用这个工厂方法来获取代理对象。
//对于customer类,不知道返回的是代理类对象还是委托类对象。
公共静态主题getInstance(){
subject delegate=new real subject();
invocation handler=new SubjectInvocationHandler(delegate);
主题代理=null
proxy=(Subject)proxy . new proxy instance(
delegate.getClass()。getClassLoader(),
delegate.getClass()。getInterfaces(),
处理者);
返回代理;
}
}清单12:动态代理客户类
公共类客户端{
公共静态void main(String[] args) {
subject proxy=dynproxyfactory . getinstance();
proxy . deal task( DBQueryTask );
}
}4.动态主体机制的特征
首先是动态生成的代理类本身的一些特性。1)包:如果所有被代理的接口都是公共的,那么它将被定义在顶层包中(即包路径为空)。如果被代理的接口之间存在非公共接口(因为接口不能定义为protect或private,默认包访问级别为public),那么将在接口所在的包中定义(假设表示com.ibm.developerworks包中的一个非公共接口A,那么新生成的代理类所在的包就是com.ibm.developerworks)。这种设计的目的是确保动态代理类不会因为包管理问题而被成功定义和访问;2)类修饰符:这个代理类有final和public修饰符,这意味着它可以被所有类访问,但是不能被再次继承;3)类名:格式为“$ProxyN”,其中n为递增1的阿拉伯数字,代表代理类第n次生成的动态代理类。值得注意的是,并不是每次调用proxy的静态方法创建动态代理类,n的值都会增加。原因是如果你试图为同一组接口(包括相同顺序的接口)重复创建一个动态代理类,它会智能返回之前创建的代理类的类对象,而不是试图创建一个全新的代理类,这样可以节省不必要的代码生成,提高代理类创建的效率。4)类继承关系:该类的继承关系如下图所示:
图2:动态代理类的继承关系
从图中可以看出,代理类是它的父类,这个规则适用于所有由Proxy创建的动态代理类。此外,这个类还实现了它所代理的一组接口,这是它能够被安全地类型化到它所代理的接口的根本原因。接下来,让我们看看代理类实例的一些特征。每个实例都会关联一个调用处理器对象,代理类实例的调用处理器对象可以通过代理提供的静态方法getInvocationHandler获得。当调用代理类实例的接口中声明的方法时,这些方法最终将由调用处理器的invoke方法执行。此外,值得注意的是,代理类的根类java.lang.Object中有三个方法也将被调度给调用处理器的invoke方法来执行。它们是hashCode、equals和toString。可能的原因如下:第一,因为这些方法是公共的而不是最终的,所以它们可以被代理类覆盖;第二,由于这些方法往往表现出一个类的一些特征属性,具有一定的区分度,所以为了保证代理类和委托类的外部一致性,这三个方法也要赋给委托类执行。当代理的一组接口具有重复声明的方法并且该方法被调用时,代理类总是从前端接口获取方法对象并将它分派给调用处理器,而不管代理类实例是否以接口(或从接口继承的子接口)的形式被外部引用,因为其当前引用的类型在代理类中无法区分。让我们来看看所代表的一组接口的特征。首先需要注意的是,不能有重复的接口,以免生成动态代理代码时出现编译错误。其次,这些接口必须对类加载器可见,否则类加载器将无法链接它们,从而导致类定义失败。第三,所有要代理的非公共接口必须在同一个包中,否则代理类生成会失败。最后,接口数量不能超过65535,这是JVM设定的限制。最后,我们来看看异常处理的特点。从调用处理器接口声明的方法可以看出,理论上它可以抛出任何一种异常,因为所有的异常都继承自Throwable接口,但事实是这样吗?答案是否定的,因为我们必须遵守一个继承原则:即当子类重写父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表中。因此,尽管调用处理器在理论上是可行的,但在实践中往往受到限制,除非父接口中的方法支持引发可抛出异常。那么如果invoke方法中确实发生了接口方法声明中不支持的异常呢?放心,Java动态代理类为我们设计了一个解决方案:它会抛出UndeclaredThrowableException异常。此异常是RuntimeException类型,因此不会导致编译错误。通过这个异常的getCause方法,还可以得到原来不支持的异常对象,方便错误诊断。5.动态代理的优缺点。
优势:
与静态代理相比,动态代理最大的优点是接口中声明的所有方法都转移到调用处理器的一个集中式方法中进行处理(InvocationHandler.invoke)。这样在有大量接口方法的时候,我们就可以灵活处理,而不是像静态代理一样,把每个方法都转移过来。在这个例子中看不到,因为invoke方法嵌入了特定的外围服务(记录任务处理前后的时间,计算时差)。事实上,外围服务可以像Spring AOP一样配置。美中不足的是:
诚然,代理设计得很漂亮,但还是有一点遗憾,就是无法摆脱只支持接口代理的束缚,因为它的设计注定了这个遗憾。回想一下动态生成的代理类的继承图。它们注定有一个共同的父类,叫做proxy。Java的继承机制注定了这些动态代理类无法实现类的动态代理,因为多重继承在Java中本质上是不可行的。人们可以否认类代理必要性的原因有很多,但也有一些支持类动态代理更好的原因。和接口类,这并不明显,只是在Java中变得如此详细。如果只考虑方法声明和是否定义,有一种是两者的混合体,名字叫抽象类。认为实现抽象类的动态代理也有其内在价值。另外,还有一些历史遗留的类,因为没有实现任何接口,所以永远不会与动态代理相关联。这一切不得不说是一个小小的遗憾。
更多相关文章,请访问PHP中文网!也就是静态代理和动态代理有什么区别?更多详情请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。