Thématiques principales

lundi 2 avril 2018

InvocationHandler

Nous avions parlé des annotations dans un articles precedents mais nous nous etions arreter au traitements des annotations utilisé dans le code ou dans les class mais pas celles utiliser en runtime.

Je vous propose dans cet article de réemployer l’exemple traité en gardant l’annotation Relation mais de la compléter par exemple d’une annotation Tuple qui permet d'insérer des données dans la table construite par l’annotation Relation. (Au passage on adaptera un peu cette dernière de façon à ce que la Relation ait les attribut qui vont bien…)

Commençons par l’ajout de l’attribut dans notre précédente annotation, au passage on la passe en mode RUNTIME:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Relation {
 
 String value();
}


Ensuite on va adapter le processeur. On va externaliser la partie en interface avec la BD dans une classe utilitaire permettant de faire la construction de la table avec l’attribut qui va bien:


public void createRelation(Element e) {
  Relation relation = e.getAnnotation(Relation.class);
  String rqt = this.CREATE + e.getSimpleName().toString() + "( " + relation.value() + ATTRIBUT + ");";
  System.out.println("Requete :" + rqt);
  try {
   PreparedStatement statement = DriverManager.getConnection(DB_URL, USER, PASS).prepareStatement(rqt);
   System.out.println("Mon element : " + e.getSimpleName().toString());
   statement.executeQuery();
  } catch (SQLException e1) {
   e1.printStackTrace();
  }
 }

Voila, normalement on aura un attribut name dans notre table lors du build.

Maintenant on veut qu'à l'exécution, pour chaque objet de type Helloworld on insere dans la table le nom passé en parametre du setter. .

Pour cela on va creer l’annotation AddValue (ok c’est pas original) de façon a pouvoir la placer sur cette methode:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AddValue {

}


Ainsi on l’utilisera ainsi:


@Relation(value = "name")
public class HelloWorld {
 private String name;
 
 @AddValue
 public void setName(String name)
 {
  this.name=name;
 }
}


Mais me direz vous, cela ne permet pas vraiment de mapper un quelconque processus d’insertion en base de la donnée! Effectivement car il manque la partie processeur de cette annotation.

Le problème ici c’est que l’annotation doit être traité pendant l'exécution. Alors comment faire?

Et bien tout simplement en utilisant deux patterns, la factory et le pattern proxy. Le premier va nous permettre de masquer l’utilisation du second qui sera couplé à l’invocation handler! Le voila celui la!

Alors avant cela voici ce que nous espérons pouvoir faire:


IHelloWorld o=MyAnnotFactory.mapObject(IHelloWorld.class,HelloWorld.class);
o.setName("Thomas");


Et ceci doit nous permettre de l'insertion de Thomas dans la base lors de l’affectation. Pour cela, la factory va utiliser une interface et le prototype de la classe pour construire une instance qui sera en réalité un proxy.


 public static  T mapObject(Class i,Class t) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException
 {
  T proxy=(T) Proxy.newProxyInstance(MyAnnotFactory.class.getClassLoader(),new Class[]{i}, new MyInvocationHandler(t));
  return proxy;
 }


Ainsi on utilise l’api Java Proxy permettant de construire ce proxy a partir de d’une interface identifiant au proxy quels sont les méthodes a wrapper et pour lesquelles, le proxy devra appeler l’InvocationHandler à la rescousse. Voyons comment, ce n’est pas tres compliqué:


public class MyInvocationHandler implements InvocationHandler {

 private Object object;

 public MyInvocationHandler(Class t) {
  try {
   this.object = t.newInstance();
  } catch (InstantiationException | IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

 @Override
 public Object invoke(Object arg0, Method method, Object[] arg2) throws Throwable {
  System.out.println("Methode :" + method.getName());
  Method m=object.getClass().getMethod(method.getName(),method.getParameterTypes());
  for (Annotation annot : m.getAnnotations()) {
   System.out.println(annot); 
   if (annot.annotationType().equals(AddValue.class)) {
    System.out.println("je pousse en base");
    AnnotDBUtils.getInstance().pushData(object,arg2);
    return object;
   }
  }
  return method.invoke(object, arg2);
 }
}


Alors tout d’abord, le proxy doit continuer à faire croire que l’on manipule l’objet en question. (la factory etant la pour le tour de passe passe…) Du coup, a peine appelé, l’invocation handler va se charger de se construire un objet du type masqué par le proxy afin de l’utiliser lors de l'appelle effectif des méthodes sur le proxy.



Ceci va permettre de tromper l’utilisateur mais en plus cela va nous permettre de compléter ce comportement. Ainsi le contenu de la méthode invoke décrit la validation de la présence de l’annotation AddValue qui lorsqu’elle est présente va appeler une méthode push Data de notre utilitaire de BD que voici:


 public void pushData(Object object, Object[] arg2) {
  Relation relation = object.getClass().getAnnotation(Relation.class);
  String rqt = this.INSERT + "\"" + object.getClass().getSimpleName().toLowerCase() + "\" (" + relation.value()
    + ")" + VALUES + "('" + arg2[0] + "');";
  System.out.println("Requete :" + rqt);
  try {
   PreparedStatement statement = DriverManager.getConnection(DB_URL, USER, PASS).prepareStatement(rqt);
   statement.executeUpdate();
  } catch (SQLException e) {
   e.printStackTrace();
  }
 }


Voilà alors bien sur a chaque appele de la méthode setName, l’Invocation Handler va tenter de pousser la nouvelle donnée en BD sans chercher à savoir si c’est cohérent logique ou autre. Pour aller plus loin, il faudrait donner des moyens de vérification si la donnée n’est pas déjà en base ou ajouter un attribut servant de clef primaire…. mais la ca complique beaucoup les chose, et bon on va pas reimplementer JPA… même si l’idée est la même!

Du coup on va s'arrêter là mais j’imagine que vous avez compris l'intérêt de ce pattern surtout lorsqu’il est couplé aux annotations!

Références:

[1] http://alain-defrance.developpez.com/articles/Java/J2SE/implementation-dynamique-java-proxy-invocationhandler/
[2] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/11/design-pattern-proxy.html
[3] http://un-est-tout-et-tout-est-un.blogspot.com/2017/11/design-pattern-factory.html

Aucun commentaire:

Enregistrer un commentaire