├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── encodings.xml ├── google-java-format.xml ├── hidden_proxy.iml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── libraries │ ├── Maven__org_apiguardian_apiguardian_api_1_1_0.xml │ ├── Maven__org_junit_jupiter_junit_jupiter_api_5_7_0.xml │ ├── Maven__org_junit_jupiter_junit_jupiter_engine_5_7_0.xml │ ├── Maven__org_junit_platform_junit_platform_commons_1_7_0.xml │ ├── Maven__org_junit_platform_junit_platform_engine_1_7_0.xml │ ├── Maven__org_opentest4j_opentest4j_1_2_0.xml │ └── Maven__org_ow2_asm_asm_9_0.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE ├── README.md ├── com.github.forax.proxy ├── com.github.forax.proxy.iml ├── pom.xml └── src │ ├── main │ └── java │ │ ├── com │ │ └── github │ │ │ └── forax │ │ │ └── proxy │ │ │ └── Proxy.java │ │ └── module-info.java │ └── test │ └── java │ └── com │ └── github │ └── forax │ └── proxy │ └── ProxyTest.java └── pom.xml /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | java: [ 16, 17-ea ] 13 | name: Java ${{ matrix.java }} sample 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: setup 17 | uses: actions/setup-java@v1 18 | with: 19 | java-version: ${{ matrix.java }} 20 | - name: install 21 | run: | 22 | mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V 23 | - name: build 24 | run: | 25 | mvn -B package 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # Maven target directory 26 | /com.github.forax.hiddenproxy/target/ 27 | /target/ -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/google-java-format.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/hidden_proxy.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_7_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_7_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_7_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_7_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_ow2_asm_asm_9_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rémi Forax 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hidden_proxy 2 | A simple API that shows how,to use Hidden class to create proxies. 3 | 4 | This is a replacement of `java.lang.reflect.Proxy` API of Java using a more modern design leading to must faster proxies implementation. See the javadoc of `Proxy` for more information. 5 | 6 | This code is using the Hidden Class API [JEP 371](https://openjdk.java.net/jeps/371) that was introduced in java 15, 7 | so it requires Java 16 to run. 8 | 9 | ## Example 10 | 11 | Let say we have an interface `HelloProxy` with an abstract method `hello` and a static method `implementation` somewhere else 12 | ```java 13 | interface HelloProxy { 14 | String hello(String text); 15 | } 16 | static class Impl { 17 | static String impl(int repeated, String text) { 18 | return text.repeat(repeated); 19 | } 20 | } 21 | ``` 22 | 23 | The method `Proxy.defineProxy(lookup, interfaces, overriden, field, linker)` let you dynamically create a class 24 | that implements the interface `HelloProxy` with a field (here an int). 25 | The return value of `defineProxy` is a lookup you can use to find the constructor and 26 | invoke it invoke with the value of the field. 27 | Then the first time an abstract method of the interface is called, here when calling `proxy.hello("proxy")`, 28 | the linker is called to ask how the abstract method should be implemented. 29 | Here the linker will use the method `impl` and discard (using `dropArguments`) the first argument (the proxy) 30 | before calling the method `impl`. 31 | So when calling the method `hello`, the method `impl` will be called with the field stored inside the proxy 32 | as first argument followed by the arguments of the method `hello``. 33 | ```java 34 | Lookup lookup = MethodHandles.lookup(); 35 | MethodHandle impl = lookup.findStatic(Impl.class, "impl", 36 | methodType(String.class, int.class, String.class)); 37 | Proxy.Linker linker = methodInfo -> switch(methodInfo.getName()) { 38 | case "hello" -> MethodHandles.dropArguments(impl, 0, HelloProxy.class); 39 | default -> fail("unknown method " + methodInfo); 40 | }; 41 | Lookup proxyLookup = Proxy.defineProxy(lookup, 42 | new Class[] { HelloProxy.class }, // proxy interfaces 43 | __ -> false, // don't override toString, equals and hashCode 44 | int.class, // proxy field 45 | linker); 46 | MethodHandle constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), 47 | methodType(void.class, int.class)); 48 | HelloProxy proxy = (HelloProxy) constructor.invoke(2); 49 | assertEquals("proxyproxy", proxy.hello("proxy")); 50 | ``` 51 | 52 | If you want more examples, you can take a look to the test class `ProxyTest`. 53 | 54 | -------------------------------------------------------------------------------- /com.github.forax.proxy/com.github.forax.proxy.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /com.github.forax.proxy/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | com.github.forax.proxy 9 | com.github.forax.proxy 10 | 1.0-SNAPSHOT 11 | 12 | 13 | UTF-8 14 | 15 | 16 | 17 | 18 | org.ow2.asm 19 | asm 20 | 9.0 21 | compile 22 | 23 | 24 | org.junit.jupiter 25 | junit-jupiter-api 26 | 5.7.0 27 | test 28 | 29 | 30 | org.junit.jupiter 31 | junit-jupiter-engine 32 | 5.7.0 33 | test 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-compiler-plugin 42 | 3.8.1 43 | 44 | 16 45 | 46 | --enable-preview 47 | 48 | 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-surefire-plugin 54 | 3.0.0-M5 55 | 56 | --enable-preview 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /com.github.forax.proxy/src/main/java/com/github/forax/proxy/Proxy.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.proxy; 2 | 3 | import org.objectweb.asm.ClassWriter; 4 | import org.objectweb.asm.Handle; 5 | import org.objectweb.asm.Type; 6 | 7 | import java.lang.constant.MethodTypeDesc; 8 | import java.lang.invoke.CallSite; 9 | import java.lang.invoke.ConstantCallSite; 10 | import java.lang.invoke.MethodHandle; 11 | import java.lang.invoke.MethodHandleInfo; 12 | import java.lang.invoke.MethodHandles; 13 | import java.lang.invoke.MethodType; 14 | import java.lang.invoke.WrongMethodTypeException; 15 | import java.lang.reflect.Method; 16 | import java.lang.reflect.Modifier; 17 | import java.util.Arrays; 18 | import java.util.LinkedHashMap; 19 | import java.util.List; 20 | import java.util.Objects; 21 | import java.util.function.Predicate; 22 | import java.lang.invoke.MethodHandles.Lookup; 23 | import java.lang.invoke.MethodHandles.Lookup.ClassOption; 24 | import java.util.stream.Stream; 25 | 26 | import static java.lang.constant.ConstantDescs.*; 27 | import static org.objectweb.asm.Opcodes.*; 28 | 29 | /** 30 | * A class that dynamically generates a proxy class that implement several interfaces and 31 | * delegate to a {@link Proxy.Linker} the actual implementation of each method of the proxy. 32 | */ 33 | public class Proxy { 34 | /** 35 | * A linker that resolves the calls to the proxy by providing a method handle for a method of the proxy. 36 | */ 37 | @FunctionalInterface 38 | public interface Linker { 39 | /** 40 | * Returns a method handle that will be installed inside the proxy for a declared method of the proxy. 41 | * @param methodInfo the method of the proxy that should be implemented 42 | * @return a method handle that will be installed inside the proxy for a declared method of the proxy. 43 | */ 44 | MethodHandle resolve(MethodHandleInfo methodInfo) throws Throwable; 45 | } 46 | 47 | private Proxy() { 48 | throw new AssertionError(); 49 | } 50 | 51 | /** 52 | * Defines a proxy class and returns a lookup on that class. 53 | * 54 | * @param lookup the lookup used to define the proxy class. 55 | * @param interfaces the interfaces implemented by the proxy class. 56 | * @param shouldOverride a predicate indicating if methods of java.lang.Object or default method 57 | * should be overridden or not 58 | * @param delegateClass the class of the delegate field inside the proxy or void.class if there is no field. 59 | * @param linker the linker that will resolve the calls to the proxy methods. 60 | * @return a new proxy class that implements the interfaces. 61 | * @throws IllegalAccessException if this Lookup does not have full privilege access 62 | * @throws SecurityException if a security manager is present and it refuses access 63 | * @throws NullPointerException if any parameter is null 64 | */ 65 | public static Lookup defineProxy(Lookup lookup, Class[] interfaces, Predicate shouldOverride, Class delegateClass, Linker linker) throws IllegalAccessException { 66 | Objects.requireNonNull(lookup); 67 | Objects.requireNonNull(interfaces); 68 | Objects.requireNonNull(shouldOverride); 69 | Objects.requireNonNull(delegateClass); 70 | Objects.requireNonNull(linker); 71 | var proxyName = lookup.lookupClass().getPackageName().replace('.', '/') + "/ProxyImpl"; 72 | var bytecode = generateBytecode(proxyName, interfaces, shouldOverride, delegateClass); 73 | return lookup.defineHiddenClassWithClassData(bytecode, linker, true, ClassOption.NESTMATE, ClassOption.STRONG); 74 | } 75 | 76 | private static byte[] generateBytecode(String proxyName, Class[] interfaces, Predicate shouldOverride, Class delegateClass) { 77 | var interfaceList = List.of(interfaces); 78 | record Key(String name, String descriptor) { } 79 | var map = new LinkedHashMap(); 80 | Stream.concat(Stream.of(Object.class), interfaceList.stream()) 81 | .flatMap(type -> Arrays.stream(type.getMethods())) 82 | .filter(m -> (m.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) == 0) 83 | .filter(m -> (m.getDeclaringClass() != Object.class && !m.isDefault()) || shouldOverride.test(m)) 84 | .forEach(m -> map.putIfAbsent(new Key(m.getName(), MethodType.methodType(m.getReturnType(), m.getParameterTypes()).descriptorString()), m)); 85 | var writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); 86 | var interfaceNames = interfaceList.stream().map(itf -> itf.getName().replace('.', '/')).toArray(String[]::new); 87 | writer.visit(V16, ACC_PUBLIC | ACC_SUPER, proxyName, null, "java/lang/Object", interfaceNames); 88 | if (delegateClass != void.class) { 89 | var fv = writer.visitField(ACC_PRIVATE | ACC_FINAL, "delegate", delegateClass.descriptorString(), null, null); 90 | fv.visitEnd(); 91 | } 92 | 93 | var init = writer.visitMethod(ACC_PUBLIC, "", delegateClass == void.class? "()V": "(" + delegateClass.descriptorString() + ")V", null, null); 94 | init.visitCode(); 95 | init.visitVarInsn(ALOAD, 0); 96 | init.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); 97 | if (delegateClass != void.class) { 98 | var delegateType = Type.getType(delegateClass); 99 | init.visitVarInsn(ALOAD, 0); 100 | init.visitVarInsn(delegateType.getOpcode(ILOAD), 1); 101 | init.visitFieldInsn(PUTFIELD, proxyName, "delegate", delegateClass.descriptorString()); 102 | } 103 | init.visitInsn(RETURN); 104 | init.visitMaxs(-1, -1); 105 | init.visitEnd(); 106 | 107 | map.forEach((key, method) -> { 108 | var descriptor = key.descriptor; 109 | var mv = writer.visitMethod(ACC_PUBLIC, key.name, descriptor, null, null); 110 | mv.visitCode(); 111 | mv.visitVarInsn(ALOAD, 0); 112 | if (delegateClass != void.class) { 113 | mv.visitVarInsn(ALOAD, 0); 114 | mv.visitFieldInsn(GETFIELD, proxyName, "delegate", delegateClass.descriptorString()); 115 | } 116 | var parameterSlot = 1; 117 | var parameterTypes = Type.getArgumentTypes(descriptor); 118 | for (var parameterType : parameterTypes) { 119 | mv.visitVarInsn(parameterType.getOpcode(ILOAD), parameterSlot); 120 | parameterSlot += parameterType.getSize(); 121 | } 122 | var isInterface = method.getDeclaringClass().isInterface(); // it can be java/lang/Object 123 | var methodHandle = new Handle(isInterface? H_INVOKEINTERFACE: H_INVOKEVIRTUAL, method.getDeclaringClass().getName().replace('.', '/'), method.getName(), descriptor, isInterface); 124 | var proxyType = Arrays.stream(interfaces).findFirst().map(Class::descriptorString).orElse("Ljava/lang/Object;"); 125 | var indyDesc = '(' + proxyType + (delegateClass == void.class? "": delegateClass.descriptorString()) + descriptor.substring(1); 126 | mv.visitInvokeDynamicInsn(method.getName(), indyDesc, BSM, methodHandle); 127 | mv.visitInsn(Type.getReturnType(descriptor).getOpcode(IRETURN)); 128 | mv.visitMaxs(-1, -1); 129 | mv.visitEnd(); 130 | }); 131 | writer.visitEnd(); 132 | return writer.toByteArray(); 133 | } 134 | 135 | private static final Handle BSM = new Handle(H_INVOKESTATIC, 136 | Proxy.class.getName().replace('.', '/'), 137 | "proxyMetaFactory", 138 | MethodTypeDesc.of(CD_CallSite, CD_MethodHandles_Lookup, CD_String, CD_MethodType, CD_MethodHandle).descriptorString(), 139 | false); 140 | 141 | public static CallSite proxyMetaFactory(Lookup lookup, String name, MethodType methodType, MethodHandle mh) throws Throwable { 142 | Objects.requireNonNull(lookup); 143 | Objects.requireNonNull(name); 144 | Objects.requireNonNull(methodType); 145 | Objects.requireNonNull(mh); 146 | var info = lookup.revealDirect(mh); 147 | var linker = MethodHandles.classData(lookup, "_", Linker.class); 148 | var target = linker.resolve(info); 149 | if (target == null) { 150 | Objects.requireNonNull(target, "linker " + linker.getClass().getSimpleName() + " returned function is null for proxy method " + info); 151 | } 152 | try { 153 | target = target.asType(methodType); 154 | } catch(WrongMethodTypeException e) { 155 | String detail = ""; 156 | if (target.type().parameterCount() != methodType.parameterCount()) { 157 | detail = ", maybe it's because the first parameter is not a type compatible with the proxy type ?"; 158 | } 159 | throw new LinkageError("error for linker " + linker.getClass().getSimpleName() + " while trying to link proxy method " + info + detail, e); 160 | } 161 | return new ConstantCallSite(target); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /com.github.forax.proxy/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.github.forax.proxy { 2 | requires org.objectweb.asm; 3 | 4 | exports com.github.forax.proxy; 5 | } -------------------------------------------------------------------------------- /com.github.forax.proxy/src/test/java/com/github/forax/proxy/ProxyTest.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.proxy; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodHandles; 7 | import java.lang.invoke.MethodHandles.Lookup; 8 | import java.lang.reflect.Method; 9 | import java.util.Arrays; 10 | import java.util.function.DoubleSupplier; 11 | import java.util.function.IntBinaryOperator; 12 | import java.util.function.IntSupplier; 13 | 14 | import static java.lang.invoke.MethodHandles.constant; 15 | import static java.lang.invoke.MethodHandles.dropArguments; 16 | import static java.lang.invoke.MethodHandles.empty; 17 | import static java.lang.invoke.MethodHandles.identity; 18 | import static java.lang.invoke.MethodType.methodType; 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertFalse; 21 | import static org.junit.jupiter.api.Assertions.assertNull; 22 | import static org.junit.jupiter.api.Assertions.assertTrue; 23 | import static org.junit.jupiter.api.Assertions.fail; 24 | 25 | public class ProxyTest { 26 | @Test 27 | public void callSum() throws Throwable { 28 | var lookup = MethodHandles.lookup(); 29 | var target = lookup.findStatic(Integer.class, "sum", methodType(int.class, int.class, int.class)); 30 | 31 | var proxyLookup = Proxy.defineProxy(lookup, 32 | new Class[] { IntBinaryOperator.class }, 33 | method -> false, // Object methods (toString, equals, hashCode and default method are not overridden 34 | void.class, // no field 35 | methodInfo -> dropArguments(target,0, Object.class) // drop the proxy which is the first argument 36 | ); 37 | 38 | var constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class)); 39 | var proxy = (IntBinaryOperator) constructor.invoke(); 40 | assertEquals(5, proxy.applyAsInt(2, 3)); 41 | } 42 | 43 | @Test 44 | public void transparentProxy() throws Throwable { 45 | interface Foo { 46 | double m(int i); 47 | String s(Object o); 48 | } 49 | record FooImpl(int x) implements Foo { 50 | @Override 51 | public double m(int i) { 52 | return x + i; 53 | } 54 | @Override 55 | public String s(Object o) { 56 | return o.toString(); 57 | } 58 | } 59 | 60 | var lookup = MethodHandles.lookup(); 61 | var proxyLookup = Proxy.defineProxy(lookup, 62 | new Class[] { Foo.class }, 63 | method -> method.getDeclaringClass() == Object.class, // override Object methods (toString, equals, hashCode) 64 | Foo.class, // delegate field 65 | methodInfo -> { 66 | var method = methodInfo.reflectAs(Method.class, lookup); 67 | var target = lookup.unreflect(method); 68 | return dropArguments(target,0, Object.class); // drop the proxy which is the first argument 69 | } 70 | ); 71 | 72 | var constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class, Foo.class)); 73 | var impl = new FooImpl(4); 74 | var proxy = (Foo) constructor.invoke(impl); 75 | assertEquals(8.0, proxy.m(4)); 76 | assertEquals("bar", proxy.s("bar")); 77 | assertEquals(impl.toString(), proxy.toString()); 78 | } 79 | 80 | private static boolean same(Object o1, Object o2) { 81 | return o1 == o2; 82 | } 83 | 84 | @Test 85 | public void defineDelegatingProxyPrimitive() throws Throwable { 86 | var lookup = MethodHandles.lookup(); 87 | var same = lookup.findStatic(ProxyTest.class, "same", methodType(boolean.class, Object.class, Object.class)); 88 | var linker = (Proxy.Linker)(methodInfo) -> switch(methodInfo.getName()) { 89 | case "getAsInt", "hashCode" -> dropArguments(MethodHandles.identity(int.class), 0, Object.class); 90 | case "equals" -> dropArguments(same, 1, int.class); 91 | case "toString" -> dropArguments(constant(String.class, "proxy"), 0, Object.class, int.class); 92 | default -> fail("unknown method " + methodInfo); 93 | }; 94 | 95 | var proxyLookup = Proxy.defineProxy(lookup, new Class[] { IntSupplier.class }, __ -> true, int.class, linker); 96 | var constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class, int.class)); 97 | 98 | var proxy = (IntSupplier)constructor.invoke(42); 99 | assertEquals(42, proxy.getAsInt()); 100 | assertTrue(proxy.equals(proxy)); 101 | assertFalse(proxy.equals("foo")); 102 | assertFalse(proxy.equals(null)); 103 | assertEquals(42, proxy.hashCode()); 104 | assertEquals("proxy", proxy.toString()); 105 | } 106 | 107 | @Test 108 | public void defineProxyEmptyObjectMethods() throws Throwable { 109 | var lookup = MethodHandles.lookup(); 110 | var proxyLookup = Proxy.defineProxy(lookup, new Class[] { Runnable.class }, __ -> true, void.class, methodInfo -> { 111 | switch (methodInfo.getName()) { 112 | case "equals", "hashCode", "toString" -> { /* OK */ } 113 | default -> fail(); 114 | } 115 | 116 | // return an empty implementation 117 | return empty(methodInfo.getMethodType().insertParameterTypes(0, Object.class)); 118 | }); 119 | var constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class)); 120 | var proxy = constructor.invoke(); 121 | assertFalse(proxy.equals("not used")); 122 | assertEquals(0, proxy.hashCode()); 123 | assertNull(proxy.toString()); 124 | } 125 | 126 | @Test 127 | public void hello() throws Throwable { 128 | interface HelloProxy { 129 | String hello(String text); 130 | } 131 | class Impl { 132 | static String impl(int repeated, String text) { 133 | return text.repeat(repeated); 134 | } 135 | } 136 | 137 | Lookup lookup = MethodHandles.lookup(); 138 | MethodHandle impl = lookup.findStatic(Impl.class, "impl", methodType(String.class, int.class, String.class)); 139 | Proxy.Linker linker = methodInfo -> switch(methodInfo.getName()) { 140 | case "hello" -> MethodHandles.dropArguments(impl, 0, HelloProxy.class); 141 | default -> fail("unknown method " + methodInfo); 142 | }; 143 | Lookup proxyLookup = Proxy.defineProxy(lookup, new Class[] { HelloProxy.class }, __ -> false, int.class, linker); 144 | MethodHandle constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class, int.class)); 145 | HelloProxy proxy = (HelloProxy) constructor.invoke(2); 146 | assertEquals("proxyproxy", proxy.hello("proxy")); 147 | } 148 | 149 | @Test 150 | public void exactProxyType() throws Throwable { 151 | interface Foo { 152 | int bar(int value); 153 | } 154 | 155 | var lookup = MethodHandles.lookup(); 156 | var linker = (Proxy.Linker) methodInfo -> dropArguments(identity(int.class), 0, Foo.class); 157 | var proxyLookup = Proxy.defineProxy(lookup, new Class[] { Foo.class }, __ -> false, void.class, linker); 158 | var constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class)) 159 | .asType(methodType(Foo.class)); 160 | var proxy = (Foo) constructor.invokeExact(); 161 | assertEquals(42, proxy.bar(42)); 162 | } 163 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 4.0.0 6 | com.github.forax.hiddenproxy 7 | hidden_proxy 8 | pom 9 | 1.0-SNAPSHOT 10 | parent 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | com.github.forax.proxy 19 | 20 | 21 | --------------------------------------------------------------------------------