Introduction à cglib

Introduction à cglib

1. Vue d'ensemble

Dans cet article, nous examinerons la bibliothèquecglib (Code Generation Library). Il s'agit d'une bibliothèque d'instrumentation d'octets utilisée dans de nombreux frameworks Java tels queHibernate ouSpring. L'instrumentation de bytecode permet de manipuler ou de créer des classes après la phase de compilation d'un programme.

2. Dépendance Maven

Pour utilisercglib dans votre projet, ajoutez simplement une dépendance Maven (la dernière version peut être trouvéehere):


    cglib
    cglib
    3.2.4

3. Cglib

Les classes en Java sont chargées dynamiquement à l'exécution. Cglib utilise cette fonctionnalité du langage Java pour permettre d'ajouter de nouvelles classes à un programme Java déjà en cours d'exécution.

Hibernate utilise cglib pour la génération de proxys dynamiques. Par exemple, il ne retournera pas l'objet complet stocké dans une base de données, mais une version instrumentée de la classe stockée qui charge paresseusement les valeurs de la base de données à la demande.

Les frameworks de simulation populaires, commeMockito,, utilisentcglib pour les méthodes de simulation. Le mock est une classe instrumentée où les méthodes sont remplacées par des implémentations vides.

Nous examinerons les constructions les plus utiles decglib.

4. Implémentation du proxy à l'aide decglib

Disons que nous avons une classePersonService qui a deux méthodes:

public class PersonService {
    public String sayHello(String name) {
        return "Hello " + name;
    }

    public Integer lengthOfName(String name) {
        return name.length();
    }
}

Notez que la première méthode renvoieString et la secondeInteger.

4.1. Renvoyer la même valeur

Nous voulons créer une classe proxy simple qui interceptera un appel à une méthodesayHello(). La classeEnhancer nous permet de créer un proxy en étendant dynamiquement une classePersonService en utilisant une méthodesetSuperclass() de la classeEnhancer:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
enhancer.setCallback((FixedValue) () -> "Hello Tom!");
PersonService proxy = (PersonService) enhancer.create();

String res = proxy.sayHello(null);

assertEquals("Hello Tom!", res);

LeFixedValue est une interface de rappel qui renvoie simplement la valeur de la méthode mandatée. L'exécution de la méthodesayHello() sur un proxy a renvoyé une valeur spécifiée dans une méthode proxy.

4.2. Valeur de retour en fonction d'une signature de méthode

La première version de notre proxy présente des inconvénients car nous ne sommes pas en mesure de décider quelle méthode un proxy doit intercepter et quelle méthode doit être appelée à partir d'une superclasse. Nous pouvons utiliser une interfaceMethodInterceptor pour intercepter tous les appels au proxy et décider si vous souhaitez effectuer un appel spécifique ou exécuter une méthode à partir d'une superclasse:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
        return "Hello Tom!";
    } else {
        return proxy.invokeSuper(obj, args);
    }
});

PersonService proxy = (PersonService) enhancer.create();

assertEquals("Hello Tom!", proxy.sayHello(null));
int lengthOfName = proxy.lengthOfName("Mary");

assertEquals(4, lengthOfName);

Dans cet exemple, nous interceptons tous les appels lorsque la signature de méthode ne provient pas de la classeObject, ce qui signifie que i.e. Les méthodestoString() ouhashCode() ne seront pas interceptées. En plus de cela, nous n'interceptons que les méthodes d'unPersonService qui renvoie unString. L'appel à une méthodelengthOfName() ne sera pas intercepté car son type de retour est unInteger.

5. Créateur de haricots

Une autre construction utile descglib est une classeBeanGenerator. Cela nous permet de créer dynamiquement des beans et d’ajouter des champs avec les méthodes setter et getter. Il peut être utilisé par les outils de génération de code pour générer des objets POJO simples:

BeanGenerator beanGenerator = new BeanGenerator();

beanGenerator.addProperty("name", String.class);
Object myBean = beanGenerator.create();
Method setter = myBean.getClass().getMethod("setName", String.class);
setter.invoke(myBean, "some string value set by a cglib");

Method getter = myBean.getClass().getMethod("getName");
assertEquals("some string value set by a cglib", getter.invoke(myBean));

6. Créer un mixin

Unmixin est une construction qui permet de combiner plusieurs objets en un seul. Nous pouvons inclure un comportement de deux classes et exposer ce comportement en tant que classe ou interface unique. Les Mixinscglib permettent de combiner plusieurs objets en un seul objet. Cependant, pour ce faire, tous les objets inclus dans un mixin doivent être sauvegardés par des interfaces.

Disons que nous voulons créer un mixin de deux interfaces. Nous devons définir les deux interfaces et leurs implémentations:

public interface Interface1 {
    String first();
}

public interface Interface2 {
    String second();
}

public class Class1 implements Interface1 {
    @Override
    public String first() {
        return "first behaviour";
    }
}

public class Class2 implements Interface2 {
    @Override
    public String second() {
        return "second behaviour";
    }
}

Pour composer des implémentations deInterface1 etInterface2, nous devons créer une interface qui les étend tous les deux:

public interface MixinInterface extends Interface1, Interface2 { }

En utilisant une méthodecreate() de la classeMixin, nous pouvons inclure les comportements deClass1 etClass2 dans unMixinInterface:

Mixin mixin = Mixin.create(
  new Class[]{ Interface1.class, Interface2.class, MixinInterface.class },
  new Object[]{ new Class1(), new Class2() }
);
MixinInterface mixinDelegate = (MixinInterface) mixin;

assertEquals("first behaviour", mixinDelegate.first());
assertEquals("second behaviour", mixinDelegate.second());

L'appel de méthodes sur lesmixinDelegate appellera les implémentations deClass1 etClass2.

7. Conclusion

Dans cet article, nous examinons lescglib et ses constructions les plus utiles. Nous avons créé un proxy en utilisant une classeEnhancer. Nous avons utilisé unBeanCreator et enfin, nous avons créé unMixin qui incluait les comportements d'autres classes.

Cglib est largement utilisé par le framework Spring. Un exemple d'utilisation d'un proxy cglib par Spring consiste à ajouter des contraintes de sécurité aux appels de méthodes. Au lieu d'appeler directement une méthode, Spring Security vérifie d'abord (via un proxy) si une vérification de sécurité spécifiée passe et délègue à la méthode réelle que si cette vérification a réussi. Dans cet article, nous avons vu comment créer un tel proxy pour nos propres besoins.

L'implémentation de tous ces exemples et extraits de code peut être trouvée dans leGitHub project - il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.