Heim  >  Artikel  >  Java  >  Was ist der Unterschied zwischen statischem Proxy und dynamischem Proxy?

Was ist der Unterschied zwischen statischem Proxy und dynamischem Proxy?

青灯夜游
青灯夜游Original
2020-12-04 10:52:344488Durchsuche

Unterschied: Statische Agenten werden von Programmierern oder Tools erstellt, die den Quellcode der Agentenklasse generieren und dann die Bytecodedatei der Agentenklasse kompilieren, die bereits vor der Ausführung des Programms vorhanden ist, und die Beziehung zwischen der Agentenklasse herstellen und die Delegate-Klasse wird vor der Ausführung bestimmt. Der Quellcode der dynamischen Proxy-Klasse wird von der JVM dynamisch generiert, basierend auf Mechanismen wie der Reflexion während der Programmausführung, sodass für die Proxy-Klasse keine Bytecode-Datei vorhanden ist.

Was ist der Unterschied zwischen statischem Proxy und dynamischem Proxy?

Verwandte Empfehlungen: „Programmiervideokurs

1. Proxy-Konzept

Stellen Sie einen Proxy für ein Objekt bereit, um den Zugriff auf dieses Objekt zu steuern. Die Proxy-Klasse und die Delegate-Klasse verfügen über eine gemeinsame übergeordnete Klasse oder übergeordnete Schnittstelle, sodass das Proxy-Objekt überall dort verwendet werden kann, wo das Delegate-Klassenobjekt verwendet wird. Die Proxy-Klasse ist für die Vorverarbeitung und Filterung von Anforderungen, die Zuweisung von Anforderungen zur Verarbeitung an die Delegate-Klasse und die anschließende Verarbeitung verantwortlich, nachdem die Delegate-Klasse die Anforderung abgeschlossen hat. Verwandte Empfehlungen: „Java Video Tutorial

Abbildung 1: Proxy-Modus

Was ist der Unterschied zwischen statischem Proxy und dynamischem Proxy?

Wie aus der Abbildung ersichtlich ist, sind die Proxy-Schnittstelle (Subject), die Proxy-Klasse (ProxySubject) und der Delegat Klasse (RealSubject) bildet eine „PIN“-Struktur.
Je nach Generierungszeit der Agentenklasse können Agenten in zwei Typen unterteilt werden: statische Agenten und dynamische Agenten.

Das Folgende ist eine Simulationsanforderung zur Veranschaulichung statischer Agenten und dynamischer Agenten: Die Delegate-Klasse muss eine Langzeitaufgabe verarbeiten, und die Client-Klasse muss die für die Aufgabenausführung verbrauchte Zeit ausdrucken. Um dieses Problem zu lösen, müssen Sie die Zeit vor der Aufgabenausführung und die Zeit nach der Aufgabenausführung aufzeichnen. Die Differenz zwischen den beiden Zeiten ist die Zeit, die für die Aufgabenausführung benötigt wird.

2. Statischer Proxy

wird vom Programmierer oder einem Tool erstellt, generiert den Quellcode der Proxy-Klasse und kompiliert dann die Proxy-Klasse. Die sogenannte Statik bedeutet, dass die Bytecode-Datei der Proxy-Klasse bereits vor der Ausführung des Programms vorhanden ist. Die Beziehung zwischen der Proxy-Klasse und der Delegate-Klasse wird vor der Ausführung bestimmt.

Listing 1: Proxy-Schnittstelle

/**  
 * 代理接口。处理给定名字的任务。 
 */  
public interface Subject {  
  /** 
   * 执行给定名字的任务。 
    * @param taskName 任务名 
   */  
   public void dealTask(String taskName);   
}

Listing 2: Delegate-Klasse, insbesondere Geschäftsabwicklung.

/** 
 * 真正执行任务的类,实现了代理接口。 
 */  
public class RealSubject implements Subject {  
  
 /** 
  * 执行给定名字的任务。这里打印出任务名,并休眠500ms模拟任务执行了很长时间 
  * @param taskName  
  */  
   @Override  
   public void dealTask(String taskName) {  
      System.out.println("正在执行任务:"+taskName);  
      try {  
         Thread.sleep(500);  
      } catch (InterruptedException e) {  
         e.printStackTrace();  
      }  
   }  
}

Listing 3: Statische Proxy-Klasse

/** 
 * 代理类,实现了代理接口。 
 */  
public class ProxySubject implements Subject {  
 //代理类持有一个委托类的对象引用  
 private Subject delegate;  
   
 public ProxySubject(Subject delegate) {  
  this.delegate = delegate;  
 }  
  
 /** 
  * 将请求分派给委托类执行,记录任务执行前后的时间,时间差即为任务的处理时间 
  *  
  * @param taskName 
  */  
 @Override  
 public void dealTask(String taskName) {  
  long stime = System.currentTimeMillis();   
  //将请求分派给委托类处理  
  delegate.dealTask(taskName);  
  long ftime = System.currentTimeMillis();   
  System.out.println("执行任务耗时"+(ftime - stime)+"毫秒");  
    
 }  
}

Listing 4: Statische Proxy-Klasse generieren e

清单5:客户类 

public class Client1 {  
  
 public static void main(String[] args) {  
  Subject proxy = SubjectStaticFactory.getInstance();  
  proxy.dealTask("DBQueryTask");  
 }   
  
}

静态代理类优缺点 

优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。 

缺点: 

1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。 

2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。 

三、动态代理 

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。 

1、先看看与动态代理紧密关联的Java API。 

1)java.lang.reflect.Proxy 

这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。 

清单6:Proxy类的静态方法 

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器  
static InvocationHandler getInvocationHandler(Object proxy)   
  
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象  
static Class getProxyClass(ClassLoader loader, Class[] interfaces)   
  
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类  
static boolean isProxyClass(Class cl)   
  
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例  
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

2)java.lang.reflect.InvocationHandler 

这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。 

清单7:InvocationHandler的核心方法 

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象  
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行  
Object invoke(Object proxy, Method method, Object[] args)

3)java.lang.ClassLoader 

这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。 

每次生成动态代理类对象时都需要指定一个类装载器对象 

2、动态代理实现步骤 

具体步骤是: 

a. 实现InvocationHandler接口创建自己的调用处理器 

b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类 

c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数 

d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象 

清单8:分步骤实现动态代理 

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发  
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用  
InvocationHandler handler = new InvocationHandlerImpl(..);   
  
// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象  
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });   
  
// 通过反射从生成的类对象获得构造函数对象  
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });   
  
// 通过构造函数对象创建动态代理类实例  
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

Proxy类的静态方法newProxyInstance对上面具体步骤的后三步做了封装,简化了动态代理对象的获取过程。 
清单9:简化后的动态代理实现 

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发  
InvocationHandler handler = new InvocationHandlerImpl(..);   
  
// 通过 Proxy 直接创建动态代理类实例  
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,   
     new Class[] { Interface.class },  handler );

3、动态代理实现示例 

清单10:创建自己的调用处理器 

/** 
 * 动态代理类对应的调用处理程序类 
 */  
public class SubjectInvocationHandler implements InvocationHandler {  
   
 //代理类持有一个委托类的对象引用  
 private Object delegate;  
   
 public SubjectInvocationHandler(Object delegate) {  
  this.delegate = delegate;  
 }  
   
 @Override  
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  long stime = System.currentTimeMillis();   
  //利用反射机制将请求分派给委托类处理。Method的invoke返回Object对象作为方法执行结果。  
  //因为示例程序没有返回值,所以这里忽略了返回值处理  
  method.invoke(delegate, args);  
  long ftime = System.currentTimeMillis();   
  System.out.println("执行任务耗时"+(ftime - stime)+"毫秒");  
    
  return null;  
 }  
}

清单11:生成动态代理对象的工厂,工厂方法列出了如何生成动态代理类对象的步骤。 

/** 
 * 生成动态代理对象的工厂. 
 */  
public class DynProxyFactory {  
 //客户类调用此工厂方法获得代理对象。  
 //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。  
 public static Subject getInstance(){   
  Subject delegate = new RealSubject();  
  InvocationHandler handler = new SubjectInvocationHandler(delegate);  
  Subject proxy = null;  
  proxy = (Subject)Proxy.newProxyInstance(  
    delegate.getClass().getClassLoader(),   
    delegate.getClass().getInterfaces(),   
    handler);  
  return proxy;  
 }  
}

清单12:动态代理客户类 

public class Client {  
  
 public static void main(String[] args) {  
  
  Subject proxy = DynProxyFactory.getInstance();  
  proxy.dealTask("DBQueryTask");  
 }   
  
}

4、动态代理机制特点  

首先是动态生成的代理类本身的一些特点。1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。4)类继承关系:该类的继承关系如图: 

图2:动态代理类的继承关系 

Was ist der Unterschied zwischen statischem Proxy und dynamischem Proxy?

Wie aus der Abbildung ersichtlich ist, ist die Proxy-Klasse ihre übergeordnete Klasse. Diese Regel gilt für alle von Proxy erstellten dynamischen Proxy-Klassen. Und diese Klasse implementiert auch eine Reihe von Schnittstellen, die sie als Proxy ausgibt, was der Hauptgrund dafür ist, dass sie sicher in eine Schnittstelle umgewandelt werden kann, die sie als Proxy ausgibt.

Als nächstes werfen wir einen Blick auf einige Merkmale von Proxy-Klasseninstanzen. Jeder Instanz ist ein Aufrufhandlerobjekt zugeordnet. Sie können das Aufrufhandlerobjekt der Proxy-Klasseninstanz über die von Proxy bereitgestellte statische Methode getInvocationHandler abrufen. Wenn die in der Schnittstelle seines Proxys deklarierten Methoden für die Proxy-Klasseninstanz aufgerufen werden, werden diese Methoden schließlich von der Aufrufmethode des aufrufenden Prozessors ausgeführt. Darüber hinaus ist zu beachten, dass es drei java.lang.Object-Roots gibt Methoden der Proxy-Klasse werden auch zur Ausführung an die Aufrufmethode gesendet. Dies kann folgende Gründe haben: erstens, weil diese Methoden öffentliche und nicht endgültige Typen sind und von überschrieben werden können die Proxy-Klasse; zweitens, weil diese Methoden häufig bestimmte charakteristische Attribute einer Klasse darstellen und einen gewissen Grad an Unterscheidung aufweisen. Um die externe Konsistenz der Proxy-Klasse und der Delegate-Klasse sicherzustellen, sollten daher auch diese drei Methoden vorhanden sein der Delegate-Klasse zur Ausführung zugewiesen. Wenn eine Reihe von Schnittstellen eines Proxys über eine wiederholt deklarierte Methode verfügt und die Methode aufgerufen wird, ruft die Proxy-Klasse das Methodenobjekt immer von der vordersten Schnittstelle ab und sendet es an den aufrufenden Handler, unabhängig davon, ob die Proxy-Klasseninstanz diese Schnittstelle verwendet . (oder eine von dieser Schnittstelle geerbte Unterschnittstelle) wird extern referenziert, da der aktuell referenzierte Typ innerhalb der Proxy-Klasse nicht unterschieden werden kann.

Als nächstes lernen wir die Eigenschaften einer Gruppe von Schnittstellen kennen, die als Proxy verwendet werden. Achten Sie zunächst darauf, keine doppelten Schnittstellen zu haben, um Kompilierungsfehler beim Generieren von dynamischem Proxy-Klassencode zu vermeiden. Zweitens müssen diese Schnittstellen für den Klassenlader sichtbar sein, da der Klassenlader sie sonst nicht verknüpfen kann und die Klassendefinition fehlschlägt. Drittens müssen sich alle nicht öffentlichen Schnittstellen, die als Proxy benötigt werden, im selben Paket befinden, andernfalls schlägt auch die Generierung der Proxy-Klasse fehl. Schließlich darf die Anzahl der Schnittstellen 65535 nicht überschreiten, was ein von der JVM festgelegter Grenzwert ist.

Abschließend werfen wir einen Blick auf die Merkmale der Ausnahmebehandlung. Anhand der von der aufrufenden Prozessorschnittstelle deklarierten Methode können wir erkennen, dass sie theoretisch jede Art von Ausnahme auslösen kann, da alle Ausnahmen von der Throwable-Schnittstelle erben. Aber ist das der Fall? Die Antwort lautet „Nein“, denn wir müssen uns an ein Vererbungsprinzip halten: Wenn eine Unterklasse eine Methode einer übergeordneten Klasse überschreibt oder eine übergeordnete Schnittstelle implementiert, muss die ausgelöste Ausnahme innerhalb der von der ursprünglichen Methode unterstützten Ausnahmeliste liegen. Obwohl der Aufruf des Handlers theoretisch möglich ist, ist er in der Praxis oft eingeschränkt, es sei denn, die Methode in der übergeordneten Schnittstelle unterstützt das Auslösen von auslösbaren Ausnahmen. Was passiert also, wenn in der Aufrufmethode eine Ausnahme auftritt, die in der Deklaration der Schnittstellenmethode nicht unterstützt wird? Keine Sorge, die dynamische Java-Proxy-Klasse hat bereits eine Lösung für uns entwickelt: Sie löst eine UndeclaredThrowableException-Ausnahme aus. Diese Ausnahme ist vom Typ RuntimeException und verursacht daher keine Kompilierungsfehler. Über die getCause-Methode der Ausnahme können Sie auch das ursprüngliche nicht unterstützte Ausnahmeobjekt abrufen, um die Fehlerdiagnose zu erleichtern.

5. Vor- und Nachteile des dynamischen Proxys

Vorteile:

Im Vergleich zu statischen Proxys besteht der größte Vorteil dynamischer Proxys darin, dass alle in der Schnittstelle deklarierten Methoden an eine zentrale Methode des Aufrufprozessors (InvocationHandler.invoke) übergeben werden. Auf diese Weise können wir bei einer großen Anzahl von Schnittstellenmethoden flexibel damit umgehen, ohne jede Methode wie einen statischen Proxy übertragen zu müssen. Dies ist in diesem Beispiel nicht zu sehen, da bestimmte periphere Dienste in den Aufrufmethodenkörper eingebettet sind (Aufzeichnung der Zeit vor und nach der Aufgabenverarbeitung und Berechnung der Zeitdifferenz). In der Praxis können periphere Dienste ähnlich wie Spring AOP konfiguriert werden.

Fehler in der Suppe:

Proxy wurde zwar sehr schön gestaltet, aber es gibt immer noch ein wenig Bedauern, das heißt, es kann nie die Fesseln loswerden, die nur die Unterstützung von Schnittstellen-Proxy mit sich bringt, weil sein Design ist zum Scheitern verurteilt. Das ist schade. Erinnern Sie sich an das Vererbungsdiagramm dieser dynamisch generierten Proxy-Klassen. Sie sind dazu bestimmt, eine gemeinsame übergeordnete Klasse namens Proxy zu haben. Der Vererbungsmechanismus von Java ist darauf ausgelegt, dass diese dynamischen Proxy-Klassen keinen dynamischen Proxy für Klassen implementieren können. Der Grund dafür ist, dass die Mehrfachvererbung in Java grundsätzlich nicht funktioniert.

Es gibt viele Gründe, warum Menschen die Notwendigkeit von Klassen-Proxys leugnen können, aber es gibt auch einige Gründe zu der Annahme, dass die Unterstützung dynamischer Klassen-Proxys besser wäre. Die Trennung zwischen Schnittstellen und Klassen ist zunächst nicht sehr offensichtlich. Erst in Java wird sie so detailliert. Wenn wir nur die Methodendeklaration und ihre Definition berücksichtigen, liegt eine Mischung aus beiden vor, und ihr Name ist abstrakte Klasse. Ich glaube, dass die Implementierung eines dynamischen Proxys für abstrakte Klassen auch einen inhärenten Wert hat. Darüber hinaus sind einige Klassen aus der Historie übrig geblieben, die niemals dynamischen Agenten zugeordnet werden, da sie keine Schnittstellen implementieren. Bei all dem muss man sagen, dass es ein kleines Bedauern ist.                

Wenn Sie weitere verwandte Artikel lesen möchten, besuchen Sie bitte die Chinesische PHP-Website! !

Das obige ist der detaillierte Inhalt vonWas ist der Unterschied zwischen statischem Proxy und dynamischem Proxy?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn