[JAVA] Une histoire à laquelle j'étais accro avec toString () d'Interface qui était proxy avec JdkDynamicAopProxy

Environnement de développement

Contexte

Mise en œuvre du service

Supposons que vous ayez l'interface non conventionnelle ToStringService suivante et sa classe d'implémentation ToStringServiceImpl.

ToStringService.java


public interface SimpleService {

  void foo();

}

ToStringServiceImpl.java


import org.springframework.stereotype.Service;

@Service
public class SimpleServiceImpl implements SimpleService {

  @Override
  public void foo() {
    System.out.println("foo");
  }

  @Override
  public String toString() {
    return this.getClass().getSimpleName();
  }
}

Inutile de dire que lorsque vous exécutez toString () de SimpleServiceImpl," SimpleServiceImpl "est affiché.

Proxyize

Créez une classe qui hérite de ProxyConfig et utilise n'importe quelle classe par proxy. Le nom est approprié.

ToStringProxy.java


import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

public class ToStringProxy<T> extends ProxyConfig {

  private MultiValueMap<Class<?>, Advisor> advisorMap = new LinkedMultiValueMap<>();

  public T apply(T target) {
    List<Advisor> advisors = findAdvisors(target.getClass());
    return createProxy(target, advisors);
  }

  public void addAdvisor(Class<?> target, Advisor... advisors) {
    for (Advisor advisor : advisors) {
      this.advisorMap.add(target, advisor);
    }
  }

  @SuppressWarnings("unchecked")
  private T createProxy(T target, List<Advisor> advisors) {
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setTarget(target);
    proxyFactory.addAdvisors(advisors);
    if (!isProxyTargetClass()) {
      proxyFactory.setInterfaces(ClassUtils.getAllInterfaces(target));
    }
    proxyFactory.setProxyTargetClass(isProxyTargetClass());
    proxyFactory.setExposeProxy(isExposeProxy());
    proxyFactory.setFrozen(isFrozen());
    proxyFactory.setOpaque(isOpaque());
    proxyFactory.setOptimize(isOptimize());
    return (T) proxyFactory.getProxy();
  }

  private List<Advisor> findAdvisors(Class<?> targetClass) {
    for (Map.Entry<Class<?>, List<Advisor>> entry : advisorMap.entrySet()) {
      if (entry.getKey().isAssignableFrom(targetClass)) {
        return entry.getValue();
      }
    }
    return Collections.emptyList();
  }
}

Ensuite, créez une classe de conseiller qui utilise SimpleService.

ToStringAdvisor.java


import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.stereotype.Component;

@Component
public class ToStringAdvisor {

  private ToStringProxy<SimpleService> proxy;

  public ToStringAdvisor() {
    proxy = new ToStringProxy<>();
    //Méthode toString()Cible
    String expression = "execution(* " + SimpleService.class.getName() + ".toString())";
    AspectJExpressionPointcutAdvisor pointcutAdvisor = new AspectJExpressionPointcutAdvisor();
    pointcutAdvisor.setAdvice(new ToStringInterceptor());
    pointcutAdvisor.setExpression(expression);
    proxy.addAdvisor(SimpleService.class, pointcutAdvisor);
  }

  public SimpleService apply(SimpleService target) {
    return proxy.apply(target);
  }

  private static class ToStringInterceptor implements MethodInterceptor {

    private ToStringInterceptor() {}

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
      return "proxied: ".concat(String.valueOf(invocation.proceed()));
    }
  }
}

Il est supposé que "proxy: SimpleServiceImpl" sera affiché quand toString () of SimpleServiceImpl est appelé.

Essayez de tester

Maintenant, testons si le processus écrit dans ToStringInterceptor est appelé.

ProxySampleApplicationTests.java


import static org.hamcrest.CoreMatchers.equalTo;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProxySampleApplicationTests {

  @Autowired
  SimpleService service;

  @Autowired
  ToStringAdvisor adviser;

  @Test
  public void proxiedToStringTest() {
    service = adviser.apply(service);
    Assert.assertThat(service.toString(), equalTo("proxied: SimpleServiceImpl"));
  }
}

Le résultat est ……

errorlog.txt


java.lang.AssertionError: 
Expected: "proxied: SimpleServiceImpl"
     but: was "SimpleServiceImpl"
	at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
	at org.junit.Assert.assertThat(Assert.java:956)
	at org.junit.Assert.assertThat(Assert.java:923)
	at com.neriudon.example.ProxySampleApplicationTests.proxiedToStringTest(ProxySampleApplicationTests.java:27)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
	at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)

Hmm: penser: En passant, lorsque j'ai appliqué un point d'arrêt à service = Adviser.apply (service);, j'ai pu confirmer qu'il était proxy par JdkDynamicAopProxy.

JdkDynamicAopProxy est inutile

Si vous pensez à toString () ici, la définition de la méthode est définie dans la classe ʻObject ... mais cette classe ʻObject n'implémente aucune interface. Donc, si vous effectuez un proxy avec le JdkDynamicAopProxy basé sur l'interface, vous ne pouvez pas interrompre le processus avec toString ().

Résolu par proxy avec CGLIB

Donc, pour proxy avec la classe d'entité, proxy avec CGLIB.

ToStringAdvisor.java


  public ToStringAdvisor() {
    proxy = new ToStringProxy<>();
    //Proxy avec CGLIB
    proxy.setProxyTargetClass(true);
    //Classe d'entité toString()Cible
    String expression = "execution(* " + SimpleServiceImpl.class.getName() + ".toString())";
    AspectJExpressionPointcutAdvisor pointcutAdvisor = new AspectJExpressionPointcutAdvisor();
    pointcutAdvisor.setAdvice(new ToStringInterceptor());
    pointcutAdvisor.setExpression(expression);
    proxy.addAdvisor(SimpleService.class, pointcutAdvisor);

En conséquence, félicitations et félicitations.

Contexte de l'affichage

En fait, j'appelais toString () en envoyant un proxy pour une interface plus compliquée, mais je ne pouvais pas remarquer la cause car je pouvais également appeler toString () sur l'interface. Il n'y a que deux types de proxy, JdkDynamicAopProxy et CGLIB, mais c'est profond: confondu:.

Cependant, si je veux mettre le processus dans toString () d'une interface spécifique, dois-je le proxy avec CGLIB pour toutes les classes qui implémentent cette interface?

Recommended Posts

Une histoire à laquelle j'étais accro avec toString () d'Interface qui était proxy avec JdkDynamicAopProxy
Une histoire accro à EntityNotFoundException de getOne de JpaRepository
Une histoire dans laquelle j'étais accro à la conversion de type implicite d'ActiveRecord lors du test unitaire
Une histoire sur la connexion à un serveur CentOS 8 avec un ancien Ansible
L'histoire de toString () commençant par le passage d'un tableau à System.out.println
Une histoire accro aux espaces réservés des modèles JDBC
L'histoire de la création d'un proxy inverse avec ProxyServlet
Une histoire d'essayer de s'entendre avec Mockito
Une histoire sur la réduction de la consommation de mémoire à 1/100 avec find_in_batches
Histoire de créer une application de gestion de tâches avec Swing, Java
L'histoire du transfert d'un conteneur Docker vers le registre de packages GitHub et Docker Hub avec des actions GitHub
Code du port C avec de nombreux typecasts vers Swift
Une histoire remplie des bases de Spring Boot (résolu)
Une série d'étapes pour créer des livrables pour les portefeuilles avec Rails
Comment déplacer une autre classe avec une action de bouton d'une autre classe.
Une histoire bloquée avec NotSerializableException
[Circle CI] Une histoire à laquelle j'étais accro chez Start Building
L'histoire du refactoring avec un assistant personnel pour la première fois dans une application Rails
Essayez d'imiter l'idée d'un tableau à deux dimensions avec un tableau à une dimension
Une histoire que j'ai eu du mal à défier le pro de la concurrence avec Java
Une histoire sur l'utilisation de l'API League Of Legends avec JAVA
Une histoire qui a eu du mal avec l'introduction de Web Apple Pay
Branchement conditionnel avec une interface fluide
Gestion des exceptions avec une interface fluide
Créez des exceptions avec une interface fluide
L'histoire de rendre possible la construction d'un projet qui a été construit par Maven avec Ant
Je voulais écrire un processus équivalent à une instruction while avec l'API Java 8 Stream
Un mémo sobrement accro à la demande de multipart / form-data
L'histoire de l'oubli de fermer un fichier en Java et de l'échec
L'histoire de la création d'un lanceur de jeu avec une fonction de chargement automatique [Java]
Une histoire à laquelle j'étais accro lors du test de l'API à l'aide de MockMVC
J'étais accro à un simple test de Jedis (bibliothèque Java-> Redis)
Extraire une partie d'une chaîne en Ruby
Une petite histoire addictive avec def initialize
J'ai essayé d'implémenter une fonction équivalente à Felica Lite avec HCE-F d'Android
Comment obtenir l'ID d'un utilisateur qui s'est authentifié avec Firebase dans Swift
Envoyez des notifications à Slack avec la version gratuite de sentry (en utilisant lambda)
Rails6 Je veux créer un tableau de valeurs avec une case à cocher
SpringSecurity J'étais accro à essayer de me connecter avec un mot de passe haché (résolu)
J'ai essayé de cloner une application Web pleine de bugs avec Spring Boot
Je veux obtenir récursivement la superclasse et l'interface d'une certaine classe
Une histoire que les personnes qui ont fait iOS solidement peuvent être accro à la mise en œuvre de Listener lors du passage à Android