[Java-Zweig] Ändern und Hinzufügen des Verhaltens der Instanz von außen (Proxy Black Magic)

Einführung

Eine schwarze Magie namens Proxy wird manchmal verwendet, um das Verhalten einer Methode einer Instanz von außen zu ändern. Persönlich habe ich es nicht viel verwendet, aber ich habe zum ersten Mal versucht, es zu verwenden, um das Verhalten einer Anwendung zu ändern, die normalerweise eine JDBC-Verbindung erhält und normalerweise von außen einem verbindungspoolähnlichen Verhalten nahe kommt. Es war einfacher zu bedienen als ich erwartet hatte, also werde ich es als Memo für mich selbst hinterlassen.

Vorbereitung

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_); //Verarbeiten Sie den Argumentteil beim Aufruf der Methode
						Object result = applyModifier(methodReplacer, method, Tuple.of(src, method, args), () -> method.invoke(src, args));//Ersetzen Sie die Methode selbst
						return applyModifier(resultModifier, method, Tuple.of(src, method, args, result), () -> result); //Verarbeiten Sie das Ergebnis der Methode
					}
				}
		);
	}

	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();
	}

Das war alles was ich brauchte um mich vorzubereiten.

Wie benutzt man

Im Folgenden wird der Code gezeigt, der das Verhalten einer Instanz von BiFunction ändert, die zwei Ganzzahlen verwendet und diese mit einem + -Zeichen verbindet, um eine Zeichenfolge zu generieren.

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( //Wenn apply aufgerufen wird, ist das erste Argument+2 für das zweite Argument-2 tun
				hoge, new Class<?>[]{BiFunction.class}, 
				new HashMap<Function<Method, Boolean>, Function<Tuple3<Object, Method, Object[]>, Object[]>>() {{
					put(
							method -> "apply".equals(method.getName()), //Führen Sie Folgendes aus, wenn der Methodenname angewendet wird
							tpl3 -> {
								Object[] args = tpl3.cdr.cdr.car; //Nehmen Sie den Argumentteil heraus, addieren Sie 2 zum ersten Argument und subtrahieren Sie 2 vom nächsten Argument
								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 statt Anzeige b-Zur Anzeige wechseln 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( //Ändern Sie die Ergebniszeichenfolge so, dass sie zweimal wiederholt wird
				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; //Extrahieren Sie das Ergebnis der Ausführung für das ursprüngliche Objekt
								return result + " " + result;        //Wiederholen Sie es zweimal
							}
					);
				}}
		);
	
		System.out.println(hoge.apply(2, 3)); // 2 +Anzeige als 3
		System.out.println(hoge1.apply(2, 3)); // 4 +Anzeige als 1
		System.out.println(hoge2.apply(2, 3)); // 2 -Anzeige als 3
		System.out.println(hoge3.apply(2, 3)); // 2 + 3 2 +Anzeige als 3
	}

Merkmal verspotten

Erst als ich den Code geschrieben habe, ist mir aufgefallen, dass dem Aufrufhandler auch Methoden hinzugefügt werden können, die nicht einmal in der Klasse der ursprünglichen Instanz definiert sind. Mit anderen Worten, wenn Sie die folgenden Vorbereitungen treffen ...

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()); //Ich werde die ursprüngliche Methode ersetzen
						}
					};
					@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);
					}
				}
		);
	}

Du kannst das.

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); //Retrofit Person and Place-Funktionalität für eine Objektinstanz
		
		((Person)o).setName("Ichiro");
		System.out.println(((Person)o).getName()); //Es funktioniert richtig
		((Place)o).setAddress("Tokyo");
		System.out.println(((Place)o).getAddress()); //Es funktioniert richtig

		System.out.println("Hash " + o.hashCode()); //Die ursprüngliche Methode von Object kann aufgerufen werden
	}

Es ist ein Merkmal-ähnlicher Code.

** Ich bin gerade wütend geworden und denke jetzt darüber nach. ** ** **

Recommended Posts

[Java-Zweig] Ändern und Hinzufügen des Verhaltens der Instanz von außen (Proxy Black Magic)
Java, Instanz für Anfänger