Une magie noire appelée Proxy est parfois utilisée pour modifier le comportement d'une méthode d'une instance de l'extérieur. Personnellement, je ne l'ai pas beaucoup utilisé, mais j'ai essayé de l'utiliser pour la première fois pour modifier de force le comportement d'une application qui acquiert normalement une connexion JDBC et se ferme normalement à un comportement de type pool de connexions de l'extérieur. C'était plus facile à utiliser que ce à quoi je m'attendais, je vais donc le laisser comme mémo pour moi-même.
ProxyUtils.java
@SuppressWarnings("unchecked")
public static <T> T makeProxy(
T src,
Class<?>[] classes,
Map<Function<Method, Boolean>, Function<Tuple3<Object, Method, Object[]>, Object[]>> argumentModifier,
Map<Function<Method, Boolean>, Function<Tuple3<Object, Method, Object[]>, Object>> methodReplacer,
Map<Function<Method, Boolean>, Function<Tuple4<Object, Method, Object[], Object>, Object>> resultModifier
) {
return (T) Proxy.newProxyInstance(
src.getClass().getClassLoader(),
classes,
new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args_) throws Exception {
Object[] args = applyModifier(argumentModifier, method, Tuple.of(src, method, args_), () -> args_); //Traiter la partie argument lors de l'appel de la méthode
Object result = applyModifier(methodReplacer, method, Tuple.of(src, method, args), () -> method.invoke(src, args));//Remplacez la méthode elle-même
return applyModifier(resultModifier, method, Tuple.of(src, method, args, result), () -> result); //Traiter le résultat de la méthode
}
}
);
}
private static interface SupplierThrowsException<A> {public A get() throws Exception;}
private static <C, A, R> R applyModifier(Map<Function<C, Boolean>, Function<A, R>> map, C cond, A arg, SupplierThrowsException<R> defaultResult) throws Exception {
if (map != null) {
for (Entry<Function<C, Boolean>, Function<A, R>> e : map.entrySet()) {
if (e.getKey().apply(cond)) return e.getValue().apply(arg);
}
}
return defaultResult.get();
}
C'était tout ce dont j'avais besoin pour me préparer.
À titre d'exemple, le code qui modifie le comportement d'une instance de BiFunction qui prend deux entiers et les connecte avec un signe + pour générer une chaîne de caractères est illustré ci-dessous.
Main.java
public static void main(String[] args) {
final BiFunction<Integer, Integer, String> hoge = new BiFunction<Integer, Integer, String>() {
@Override public String apply(Integer t, Integer u) {return "" + t + " + " + u;}
};
BiFunction<Integer, Integer, String> hoge1 = ProxyUtils.makeProxy( //Lorsque apply est appelé, le premier argument+2, pour le deuxième argument-2 faire
hoge, new Class<?>[]{BiFunction.class},
new HashMap<Function<Method, Boolean>, Function<Tuple3<Object, Method, Object[]>, Object[]>>() {{
put(
method -> "apply".equals(method.getName()), //Exécutez ce qui suit lorsque le nom de la méthode est appliqué
tpl3 -> {
Object[] args = tpl3.cdr.cdr.car; //Retirez la partie argument, ajoutez 2 au premier argument et soustrayez 2 de l'argument suivant
args[0] = (Integer)args[0] + 2;
args[1] = (Integer)args[1] - 2;
return args;
}
);
}},
null,
null
);
BiFunction<Integer, Integer, String> hoge2 = ProxyUtils.makeProxy( // a +a au lieu d'afficher b-Changer pour afficher b
hoge, new Class<?>[]{BiFunction.class},
null,
new HashMap<Function<Method, Boolean>, Function<Tuple3<Object, Method, Object[]>, Object>>() {{
put(
method -> "apply".equals(method.getName()),
tpl3 -> {
Object[] args = tpl3.cdr.cdr.car;
return "" + args[0] + " - " + args[1];
}
);
}},
null
);
BiFunction<Integer, Integer, String> hoge3 = ProxyUtils.makeProxy( //Changer la chaîne de résultat pour répéter deux fois
hoge, new Class<?>[]{BiFunction.class},
null,
null,
new HashMap<Function<Method, Boolean>, Function<Tuple4<Object, Method, Object[], Object>, Object>>() {{
put(
method -> "apply".equals(method.getName()),
tpl4 -> {
Object result = tpl4.cdr.cdr.cdr.car; //Extraire le résultat de l'exécution sur l'objet d'origine
return result + " " + result; //Répétez-le deux fois
}
);
}}
);
System.out.println(hoge.apply(2, 3)); // 2 +Afficher comme 3
System.out.println(hoge1.apply(2, 3)); // 4 +Afficher comme 1
System.out.println(hoge2.apply(2, 3)); // 2 -Afficher comme 3
System.out.println(hoge3.apply(2, 3)); // 2 + 3 2 +Afficher comme 3
}
Ce n'est que lorsque j'ai écrit le code jusqu'à présent que j'ai remarqué que même les méthodes qui ne sont même pas définies dans la classe de l'instance d'origine peuvent être ajoutées au gestionnaire d'appel. En d'autres termes, si vous faites les préparatifs suivants ...
ProxyUtils.java
public static interface TraitInterface<T extends TraitInterface<T>> {public Class<T> interfaceClass();}
private static interface FunctionThrowsException<A, B> {public B apply(A a) throws Exception;}
@SuppressWarnings("unchecked")
public static <S> S trait(S src, TraitInterface<?>... trait) {
return (S) Proxy.newProxyInstance(
trait.getClass().getClassLoader(),
Arrays.asList(trait).stream().map(t -> t.interfaceClass()).collect(Collectors.toList()).toArray(new Class<?>[0]),
new InvocationHandler() {
Map<String, FunctionThrowsException<Object[], Object>> handler = new HashMap<String, FunctionThrowsException<Object[], Object>>() {
private static final long serialVersionUID = 6154196791279856396L;
{
for (TraitInterface<?> t : trait) for (Method m : t.getClass().getMethods()) put(m.getName(), args -> m.invoke(t, args));
for (Method m : src.getClass().getMethods()) remove(m.getName()); //Je remplacerai la méthode originale
}
};
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (handler.containsKey(method.getName())) return handler.get(method.getName()).apply(args); else return method.invoke(src, args);
}
}
);
}
Tu peux le faire.
Main.java
public static interface Person extends TraitInterface<Person> {
public default Class<Person> interfaceClass() {return Person.class;}
public void setName(String name_);
public String getName();
}
public static interface Place extends TraitInterface<Place> {
public default Class<Place> interfaceClass() {return Place.class;}
public void setAddress(String address_);
public String getAddress();
}
public static void main(String[] args) {
Object o = new Object();
Person m = new Person() {
private String name;
@Override public void setName(String name_) {name = name_;}
@Override public String getName() {return name;}
};
Place p = new Place() {
private String address;
@Override public void setAddress(String address_) {address = address_;}
@Override public String getAddress() {return address;}
};
System.out.println("Hash " + o.hashCode());
o = trait(o, m, p); //Rénover la fonctionnalité Personne et Placer dans une instance d'objet
((Person)o).setName("Ichiro");
System.out.println(((Person)o).getName()); //Ça marche correctement
((Place)o).setAddress("Tokyo");
System.out.println(((Place)o).getAddress()); //Ça marche correctement
System.out.println("Hash " + o.hashCode()); //La méthode originale d'Object peut être appelée
}
C'est un code semblable à un trait.
** Je viens de me mettre en colère, et maintenant j'y réfléchis. ** **