Maison  >  Article  >  Java  >  Quelle est la différence entre un proxy statique et un proxy dynamique ?

Quelle est la différence entre un proxy statique et un proxy dynamique ?

青灯夜游
青灯夜游original
2020-12-04 10:52:344487parcourir

Différence : le proxy statique est créé par le programmeur ou l'outil génère le code source de la classe proxy, puis compile la classe proxy ; le fichier bytecode de la classe proxy existe déjà avant l'exécution du programme, et la relation entre la classe proxy et la classe déléguée est déterminée avant d'exécuter C'est tout. Le code source de la classe proxy dynamique est généré dynamiquement par la JVM sur la base de mécanismes tels que la réflexion pendant l'exécution du programme, il n'y a donc pas de fichier de bytecode pour la classe proxy.

Quelle est la différence entre un proxy statique et un proxy dynamique ?

Recommandations associées : "Cours vidéo de programmation"

1. >

Fournit un proxy pour un objet afin de contrôler l'accès à l'objet. La classe proxy et la classe déléguée ont une classe parent ou une interface parent commune, de sorte que l'objet proxy peut être utilisé partout où l'objet classe délégué est utilisé. La classe proxy est responsable du prétraitement des demandes, du filtrage, de l'attribution des demandes à la classe déléguée pour traitement et du traitement ultérieur une fois que la classe déléguée a terminé la demande.

Recommandations associées : "Tutoriel vidéo Java"
Figure 1 : Mode proxy

Quelle est la différence entre un proxy statique et un proxy dynamique ?

Comme le montre la figure, l'interface proxy (Subject), la classe proxy (ProxySubject) et la classe de délégation (RealSubject) forment une structure « PIN ».

Selon le temps de génération de la classe d'agent, les agents peuvent être divisés en deux types : les agents statiques et les agents dynamiques.

Ce qui suit utilise une exigence de simulation pour illustrer les agents statiques et les agents dynamiques : la classe déléguée doit traiter une tâche de longue durée et la classe client doit imprimer le temps que cela prend pour exécuter la tâche. Pour résoudre ce problème, vous devez enregistrer le temps avant l'exécution de la tâche et le temps après l'exécution de la tâche. La différence entre les deux temps est le temps consommé par l'exécution de la tâche.

2. Proxy statique

Le code source de la classe proxy est créé par le programmeur ou l'outil génère puis la classe proxy est compilée. Ce qu'on appelle statique signifie que le fichier de bytecode de la classe proxy existe déjà avant l'exécution du programme et que la relation entre la classe proxy et la classe déléguée est déterminée avant l'exécution.

Listing 1 : Interface proxy

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

Listing 2 : Classe de délégué , gérer spécifiquement les affaires.

/** 
 * 真正执行任务的类,实现了代理接口。 
 */  
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 : Classe proxy statique

/** 
 * 代理类,实现了代理接口。 
 */  
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 : Génération d'une usine de classe proxy statique

public class SubjectStaticFactory {  
 //客户类调用此工厂方法获得代理对象。  
 //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。  
 public static Subject getInstance(){   
  return new ProxySubject(new RealSubject());  
 }  
}

清单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:动态代理类的继承关系 

Quelle est la différence entre un proxy statique et un proxy dynamique ?

Comme le montre la figure, la classe Proxy est sa classe parent. Cette règle s'applique à toutes les classes proxy dynamiques créées par Proxy. Et cette classe implémente également un ensemble d'interfaces qu'elle proxy, ce qui est la raison fondamentale pour laquelle elle peut être transtypée en toute sécurité vers une interface qu'elle proxy.

Jetons ensuite un coup d'œil à quelques caractéristiques des instances de classe proxy. Chaque instance est associée à un objet gestionnaire d'appel. Vous pouvez obtenir l'objet gestionnaire d'appel de l'instance de classe proxy via la méthode statique getInvocationHandler fournie par Proxy. Lorsque les méthodes déclarées dans l'interface de son proxy sont appelées sur l'instance de classe proxy, ces méthodes seront finalement exécutées par la méthode d'invocation du processeur appelant. De plus, il est à noter qu'il y en a trois dans la classe racine java. lang.Object de la classe proxy. Les méthodes seront également envoyées à la méthode d'invocation du processeur appelant pour exécution, ce sont hashCode, equals et toString. Les raisons possibles sont : premièrement, parce que ces méthodes sont de types publics et non finaux. et peut être remplacé par la classe proxy ; deuxièmement, parce que ces méthodes présentent souvent certains attributs caractéristiques d'une classe et ont donc un certain degré de distinction, afin d'assurer la cohérence externe de la classe proxy et de la classe déléguée. trois méthodes doivent également être attribuées à la classe déléguée pour exécution. Lorsqu'un ensemble d'interfaces d'un proxy a une méthode déclarée à plusieurs reprises et que la méthode est appelée, la classe proxy obtient toujours l'objet méthode de l'interface la plus en avant et le distribue au gestionnaire appelant, que l'instance de classe proxy utilise ou non cette interface. . (ou une sous-interface héritée de cette interface) est référencée en externe car son type référencé actuel ne peut pas être distingué au sein de la classe proxy.

Jetons ensuite un coup d'œil aux caractéristiques d'un groupe d'interfaces proxy. Tout d’abord, veillez à ne pas avoir d’interfaces en double pour éviter les erreurs de compilation lors de la génération du code de classe proxy dynamique. Deuxièmement, ces interfaces doivent être visibles pour le chargeur de classe, sinon le chargeur de classe ne pourra pas les lier, provoquant l'échec de la définition de classe. Troisièmement, toutes les interfaces non publiques qui doivent être proxy doivent être dans le même package, sinon la génération de classe proxy échouera également. Enfin, le nombre d'interfaces ne peut pas dépasser 65 535, ce qui est une limite fixée par la JVM.

Enfin, examinons les caractéristiques de la gestion des exceptions. D'après la méthode déclarée par l'interface du processeur appelant, nous pouvons voir qu'en théorie elle peut lever n'importe quel type d'exception, car toutes les exceptions héritent de l'interface Throwable, mais est-ce le cas ? La réponse est non, car nous devons respecter un principe d'héritage : lorsqu'une sous-classe remplace une méthode d'une classe parent ou implémente une interface parent, l'exception levée doit être dans la liste d'exceptions prise en charge par la méthode d'origine. Ainsi, bien que l'appel du gestionnaire soit théoriquement possible, en pratique, il est souvent restreint à moins que la méthode de l'interface parent ne prenne en charge la levée d'exceptions Throwable. Alors, que se passe-t-il si une exception non prise en charge dans la déclaration de la méthode d'interface se produit dans la méthode d'invocation ? Ne vous inquiétez pas, la classe proxy dynamique Java a déjà conçu une solution pour nous : elle lèvera une exception UndeclaredThrowableException. Cette exception est de type RuntimeException, elle ne provoquera donc pas d'erreurs de compilation. Grâce à la méthode getCause de l'exception, vous pouvez également obtenir l'objet d'exception d'origine non pris en charge pour faciliter le diagnostic des erreurs.

5. Avantages et inconvénients du proxy dynamique

Avantages :

Par rapport aux proxys statiques, le plus grand avantage des proxys dynamiques est que toutes les méthodes déclarées dans l'interface sont transférées vers une méthode centralisée du processeur d'invocation (InvocationHandler.invoke). De cette façon, lorsqu'il existe un grand nombre de méthodes d'interface, nous pouvons les gérer de manière flexible sans avoir à transférer chaque méthode comme un proxy statique. Cela n'est pas visible dans cet exemple car des services périphériques spécifiques sont intégrés dans le corps de la méthode d'invocation (enregistrant le temps avant et après le traitement de la tâche et calculant le décalage horaire). En pratique, les services périphériques peuvent être configurés de la même manière que Spring AOP.

Défauts :

C'est vrai que Proxy a été très joliment conçu, mais il y a quand même un petit regret, c'est qu'il n'a jamais pu se débarrasser des contraintes liées à la prise en charge uniquement du proxy d'interface, car sa conception est vouée à ce regret. Rappelez-vous le diagramme d'héritage de ces classes proxy générées dynamiquement. Elles sont destinées à avoir une classe parent commune appelée Proxy. Le mécanisme d'héritage de Java est destiné à ce que ces classes proxy dynamiques ne puissent pas implémenter de proxy dynamique pour les classes. La raison en est que l'héritage multiple ne fonctionne essentiellement pas en Java.

Il existe de nombreuses raisons pour lesquelles les gens peuvent nier la nécessité des proxys de classe, mais il y a aussi des raisons de croire qu'il serait préférable de soutenir les proxys dynamiques de classe. La division entre interfaces et classes n’est pas très évidente au départ. Ce n’est qu’en Java qu’elle devient aussi détaillée. Si l'on considère uniquement la déclaration de méthode et si elle est définie, il y a un mélange des deux et son nom est classe abstraite. Je pense que l'implémentation d'un proxy dynamique pour les classes abstraites a également sa valeur inhérente. De plus, certaines classes restent de l'histoire, qui ne seront jamais associées aux agents dynamiques car elles n'implémentent aucune interface. Avec tout cela, il faut dire que c'est un petit regret.

Pour plus d'articles connexes, veuillez visiter

le site Web PHP chinois  ! !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn