8159746: (proxy) Support for default methods
Co-authored-by: Peter Levart <plevart@openjdk.org> Reviewed-by: darcy, alanb, plevart
This commit is contained in:
parent
1433bafb33
commit
56b15fbbcc
src/java.base/share/classes
java/lang
jdk/internal
sun/reflect/misc
test
jdk/java/lang/reflect/Proxy
DefaultMethods.javaProxyClassAccessTest.javaProxyLayerTest.javaProxyModuleMapping.javaProxyTest.java
nonPublicProxy
src
m1/p/one
m2/p/two/internal
m3/p/three/internal
test/jdk/test
langtools/jdk/jshell
micro/org/openjdk/bench/java/lang/reflect/Proxy
@ -2221,6 +2221,9 @@ public final class System {
|
||||
public void addReadsAllUnnamed(Module m) {
|
||||
m.implAddReadsAllUnnamed();
|
||||
}
|
||||
public void addExports(Module m, String pn) {
|
||||
m.implAddExports(pn);
|
||||
}
|
||||
public void addExports(Module m, String pn, Module other) {
|
||||
m.implAddExports(pn, other);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2006, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,6 +25,12 @@
|
||||
|
||||
package java.lang.reflect;
|
||||
|
||||
import jdk.internal.reflect.CallerSensitive;
|
||||
import jdk.internal.reflect.Reflection;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* {@code InvocationHandler} is the interface implemented by
|
||||
* the <i>invocation handler</i> of a proxy instance.
|
||||
@ -92,4 +98,197 @@ public interface InvocationHandler {
|
||||
*/
|
||||
public Object invoke(Object proxy, Method method, Object[] args)
|
||||
throws Throwable;
|
||||
|
||||
/**
|
||||
* Invokes the specified default method on the given {@code proxy} instance with
|
||||
* the given parameters. The given {@code method} must be a default method
|
||||
* declared in a proxy interface of the {@code proxy}'s class or inherited
|
||||
* from its superinterface directly or indirectly.
|
||||
* <p>
|
||||
* Invoking this method behaves as if {@code invokespecial} instruction executed
|
||||
* from the proxy class, targeting the default method in a proxy interface.
|
||||
* This is equivalent to the invocation:
|
||||
* {@code X.super.m(A* a)} where {@code X} is a proxy interface and the call to
|
||||
* {@code X.super::m(A*)} is resolved to the given {@code method}.
|
||||
* <p>
|
||||
* Examples: interface {@code A} and {@code B} both declare a default
|
||||
* implementation of method {@code m}. Interface {@code C} extends {@code A}
|
||||
* and inherits the default method {@code m} from its superinterface {@code A}.
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* interface A {
|
||||
* default T m(A a) { return t1; }
|
||||
* }
|
||||
* interface B {
|
||||
* default T m(A a) { return t2; }
|
||||
* }
|
||||
* interface C extends A {}
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* The following creates a proxy instance that implements {@code A}
|
||||
* and invokes the default method {@code A::m}.
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* Object proxy = Proxy.newProxyInstance(loader, new Class<?>[] { A.class },
|
||||
* (o, m, params) -> {
|
||||
* if (m.isDefault()) {
|
||||
* // if it's a default method, invoke it
|
||||
* return InvocationHandler.invokeDefault(o, m, params);
|
||||
* }
|
||||
* });
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* If a proxy instance implements both {@code A} and {@code B}, both
|
||||
* of which provides the default implementation of method {@code m},
|
||||
* the invocation handler can dispatch the method invocation to
|
||||
* {@code A::m} or {@code B::m} via the {@code invokeDefault} method.
|
||||
* For example, the following code delegates the method invocation
|
||||
* to {@code B::m}.
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* Object proxy = Proxy.newProxyInstance(loader, new Class<?>[] { A.class, B.class },
|
||||
* (o, m, params) -> {
|
||||
* if (m.getName().equals("m")) {
|
||||
* // invoke B::m instead of A::m
|
||||
* Method bMethod = B.class.getMethod(m.getName(), m.getParameterTypes());
|
||||
* return InvocationHandler.invokeDefault(o, bMethod, params);
|
||||
* }
|
||||
* });
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* If a proxy instance implements {@code C} that inherits the default
|
||||
* method {@code m} from its superinterface {@code A}, then
|
||||
* the interface method invocation on {@code "m"} is dispatched to
|
||||
* the invocation handler's {@link #invoke(Object, Method, Object[]) invoke}
|
||||
* method with the {@code Method} object argument representing the
|
||||
* default method {@code A::m}.
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* Object proxy = Proxy.newProxyInstance(loader, new Class<?>[] { C.class },
|
||||
* (o, m, params) -> {
|
||||
* if (m.isDefault()) {
|
||||
* // behaves as if calling C.super.m(params)
|
||||
* return InvocationHandler.invokeDefault(o, m, params);
|
||||
* }
|
||||
* });
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* The invocation of method {@code "m"} on this {@code proxy} will behave
|
||||
* as if {@code C.super::m} is called and that is resolved to invoking
|
||||
* {@code A::m}.
|
||||
* <p>
|
||||
* Adding a default method, or changing a method from abstract to default
|
||||
* may cause an exception if an existing code attempts to call {@code invokeDefault}
|
||||
* to invoke a default method.
|
||||
*
|
||||
* For example, if {@code C} is modified to implement a default method
|
||||
* {@code m}:
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* interface C extends A {
|
||||
* default T m(A a) { return t3; }
|
||||
* }
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* The code above that creates proxy instance {@code proxy} with
|
||||
* the modified {@code C} will run with no exception and it will result in
|
||||
* calling {@code C::m} instead of {@code A::m}.
|
||||
* <p>
|
||||
* The following is another example that creates a proxy instance of {@code C}
|
||||
* and the invocation handler calls the {@code invokeDefault} method
|
||||
* to invoke {@code A::m}:
|
||||
*
|
||||
* <blockquote><pre>{@code
|
||||
* C c = (C) Proxy.newProxyInstance(loader, new Class<?>[] { C.class },
|
||||
* (o, m, params) -> {
|
||||
* if (m.getName().equals("m")) {
|
||||
* // IllegalArgumentException thrown as {@code A::m} is not a method
|
||||
* // inherited from its proxy interface C
|
||||
* Method aMethod = A.class.getMethod(m.getName(), m.getParameterTypes());
|
||||
* return InvocationHandler.invokeDefault(o, aMethod params);
|
||||
* }
|
||||
* });
|
||||
* c.m(...);
|
||||
* }</pre></blockquote>
|
||||
*
|
||||
* The above code runs successfully with the old version of {@code C} and
|
||||
* {@code A::m} is invoked. When running with the new version of {@code C},
|
||||
* the above code will fail with {@code IllegalArgumentException} because
|
||||
* {@code C} overrides the implementation of the same method and
|
||||
* {@code A::m} is not accessible by a proxy instance.
|
||||
*
|
||||
* @apiNote
|
||||
* The {@code proxy} parameter is of type {@code Object} rather than {@code Proxy}
|
||||
* to make it easy for {@link InvocationHandler#invoke(Object, Method, Object[])
|
||||
* InvocationHandler::invoke} implementation to call directly without the need
|
||||
* of casting.
|
||||
*
|
||||
* @param proxy the {@code Proxy} instance on which the default method to be invoked
|
||||
* @param method the {@code Method} instance corresponding to a default method
|
||||
* declared in a proxy interface of the proxy class or inherited
|
||||
* from its superinterface directly or indirectly
|
||||
* @param args the parameters used for the method invocation; can be {@code null}
|
||||
* if the number of formal parameters required by the method is zero.
|
||||
* @return the value returned from the method invocation
|
||||
*
|
||||
* @throws IllegalArgumentException if any of the following conditions is {@code true}:
|
||||
* <ul>
|
||||
* <li>{@code proxy} is not {@linkplain Proxy#isProxyClass(Class)
|
||||
* a proxy instance}; or</li>
|
||||
* <li>the given {@code method} is not a default method declared
|
||||
* in a proxy interface of the proxy class and not inherited from
|
||||
* any of its superinterfaces; or</li>
|
||||
* <li>the given {@code method} is overridden directly or indirectly by
|
||||
* the proxy interfaces and the method reference to the named
|
||||
* method never resolves to the given {@code method}; or</li>
|
||||
* <li>the length of the given {@code args} array does not match the
|
||||
* number of parameters of the method to be invoked; or</li>
|
||||
* <li>any of the {@code args} elements fails the unboxing
|
||||
* conversion if the corresponding method parameter type is
|
||||
* a primitive type; or if, after possible unboxing, any of the
|
||||
* {@code args} elements cannot be assigned to the corresponding
|
||||
* method parameter type.</li>
|
||||
* </ul>
|
||||
* @throws IllegalAccessException if the declaring class of the specified
|
||||
* default method is inaccessible to the caller class
|
||||
* @throws NullPointerException if {@code proxy} or {@code method} is {@code null}
|
||||
* @throws Throwable anything thrown by the default method
|
||||
|
||||
* @since 16
|
||||
* @jvms 5.4.3. Method Resolution
|
||||
*/
|
||||
@CallerSensitive
|
||||
public static Object invokeDefault(Object proxy, Method method, Object... args)
|
||||
throws Throwable {
|
||||
Objects.requireNonNull(proxy);
|
||||
Objects.requireNonNull(method);
|
||||
|
||||
// verify that the object is actually a proxy instance
|
||||
if (!Proxy.isProxyClass(proxy.getClass())) {
|
||||
throw new IllegalArgumentException("'proxy' is not a proxy instance");
|
||||
}
|
||||
if (!method.isDefault()) {
|
||||
throw new IllegalArgumentException("\"" + method + "\" is not a default method");
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends Proxy> proxyClass = (Class<? extends Proxy>)proxy.getClass();
|
||||
|
||||
Class<?> intf = method.getDeclaringClass();
|
||||
// access check on the default method
|
||||
method.checkAccess(Reflection.getCallerClass(), intf, proxyClass, method.getModifiers());
|
||||
|
||||
MethodHandle mh = Proxy.defaultMethodHandle(proxyClass, method);
|
||||
// invoke the super method
|
||||
try {
|
||||
// the args array can be null if the number of formal parameters required by
|
||||
// the method is zero (consistent with Method::invoke)
|
||||
Object[] params = args != null ? args : Proxy.EMPTY_ARGS;
|
||||
return mh.invokeExact(proxy, params);
|
||||
} catch (ClassCastException | NullPointerException e) {
|
||||
throw new IllegalArgumentException(e.getMessage(), e);
|
||||
} catch (Proxy.InvocationException e) {
|
||||
// unwrap and throw the exception thrown by the default method
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import jdk.internal.reflect.MethodAccessor;
|
||||
import jdk.internal.reflect.Reflection;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
import sun.reflect.annotation.ExceptionProxy;
|
||||
import sun.reflect.annotation.TypeNotPresentExceptionProxy;
|
||||
import sun.reflect.generics.repository.MethodRepository;
|
||||
@ -66,6 +67,7 @@ import java.util.StringJoiner;
|
||||
* @since 1.1
|
||||
*/
|
||||
public final class Method extends Executable {
|
||||
@Stable
|
||||
private Class<?> clazz;
|
||||
private int slot;
|
||||
// This is guaranteed to be interned by the VM in the 1.4
|
||||
@ -74,6 +76,7 @@ public final class Method extends Executable {
|
||||
private Class<?> returnType;
|
||||
private Class<?>[] parameterTypes;
|
||||
private Class<?>[] exceptionTypes;
|
||||
@Stable
|
||||
private int modifiers;
|
||||
// Generics and annotations support
|
||||
private transient String signature;
|
||||
|
@ -25,11 +25,17 @@
|
||||
|
||||
package java.lang.reflect;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.invoke.WrongMethodTypeException;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
@ -37,25 +43,26 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import jdk.internal.access.JavaLangAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.loader.BootLoader;
|
||||
import jdk.internal.module.Modules;
|
||||
import jdk.internal.misc.VM;
|
||||
import jdk.internal.reflect.CallerSensitive;
|
||||
import jdk.internal.reflect.Reflection;
|
||||
import jdk.internal.loader.ClassLoaderValue;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
import sun.reflect.misc.ReflectUtil;
|
||||
import sun.security.action.GetBooleanAction;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
import sun.security.util.SecurityConstants;
|
||||
|
||||
import static java.lang.invoke.MethodType.methodType;
|
||||
import static java.lang.module.ModuleDescriptor.Modifier.SYNTHETIC;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* {@code Proxy} provides static methods for creating objects that act like instances
|
||||
@ -144,6 +151,12 @@ import static java.lang.module.ModuleDescriptor.Modifier.SYNTHETIC;
|
||||
* InvocationHandler#invoke invoke} method as described in the
|
||||
* documentation for that method.
|
||||
*
|
||||
* <li>A proxy interface may define a default method or inherit
|
||||
* a default method from its superinterface directly or indirectly.
|
||||
* An invocation handler can invoke a default method of a proxy interface
|
||||
* by calling {@link InvocationHandler#invokeDefault(Object, Method, Object...)
|
||||
* InvocationHandler::invokeDefault}.
|
||||
*
|
||||
* <li>An invocation of the {@code hashCode},
|
||||
* {@code equals}, or {@code toString} methods declared in
|
||||
* {@code java.lang.Object} on a proxy instance will be encoded and
|
||||
@ -172,9 +185,8 @@ import static java.lang.module.ModuleDescriptor.Modifier.SYNTHETIC;
|
||||
* packages:
|
||||
* <ol type="a">
|
||||
* <li>if all the proxy interfaces are <em>public</em>, then the proxy class is
|
||||
* <em>public</em> in a package exported by the
|
||||
* {@linkplain ClassLoader#getUnnamedModule() unnamed module} of the specified
|
||||
* loader. The name of the package is unspecified.</li>
|
||||
* <em>public</em> in an unconditionally exported but non-open package.
|
||||
* The name of the package and the module are unspecified.</li>
|
||||
*
|
||||
* <li>if at least one of all the proxy interfaces is <em>non-public</em>, then
|
||||
* the proxy class is <em>non-public</em> in the package and module of the
|
||||
@ -483,6 +495,7 @@ public class Proxy implements java.io.Serializable {
|
||||
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
|
||||
String proxyPkg = null; // package to define proxy class in
|
||||
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
|
||||
boolean nonExported = false;
|
||||
|
||||
/*
|
||||
* Record the package of a non-public proxy interface so that the
|
||||
@ -500,13 +513,20 @@ public class Proxy implements java.io.Serializable {
|
||||
throw new IllegalArgumentException(
|
||||
"non-public interfaces from different packages");
|
||||
}
|
||||
} else {
|
||||
if (!intf.getModule().isExported(intf.getPackageName())) {
|
||||
// module-private types
|
||||
nonExported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (proxyPkg == null) {
|
||||
// all proxy interfaces are public
|
||||
proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
|
||||
: PROXY_PACKAGE_PREFIX;
|
||||
// all proxy interfaces are public and exported
|
||||
if (!m.isNamed())
|
||||
throw new InternalError("ununamed module: " + m);
|
||||
proxyPkg = nonExported ? PROXY_PACKAGE_PREFIX + "." + m.getName()
|
||||
: m.getName();
|
||||
} else if (proxyPkg.isEmpty() && m.isNamed()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unnamed package cannot be added to " + m);
|
||||
@ -644,6 +664,8 @@ public class Proxy implements java.io.Serializable {
|
||||
*/
|
||||
Constructor<?> build() {
|
||||
Class<?> proxyClass = defineProxyClass(module, interfaces);
|
||||
assert !module.isNamed() || module.isOpen(proxyClass.getPackageName(), Proxy.class.getModule());
|
||||
|
||||
final Constructor<?> cons;
|
||||
try {
|
||||
cons = proxyClass.getConstructor(constructorParams);
|
||||
@ -740,96 +762,89 @@ public class Proxy implements java.io.Serializable {
|
||||
/**
|
||||
* Returns the module that the generated proxy class belongs to.
|
||||
*
|
||||
* If all proxy interfaces are public and in exported packages,
|
||||
* then the proxy class is in unnamed module.
|
||||
*
|
||||
* If any of proxy interface is package-private, then the proxy class
|
||||
* is in the same module of the package-private interface.
|
||||
*
|
||||
* If all proxy interfaces are public and in exported packages,
|
||||
* then the proxy class is in a dynamic module in an unconditionally
|
||||
* exported package.
|
||||
*
|
||||
* If all proxy interfaces are public and at least one in a non-exported
|
||||
* package, then the proxy class is in a dynamic module in a
|
||||
* non-exported package. Reads edge and qualified exports are added
|
||||
* for dynamic module to access.
|
||||
* non-exported package.
|
||||
*
|
||||
* The package of proxy class is open to java.base for deep reflective access.
|
||||
*
|
||||
* Reads edge and qualified exports are added for dynamic module to access.
|
||||
*/
|
||||
private static Module mapToModule(ClassLoader loader,
|
||||
List<Class<?>> interfaces,
|
||||
Set<Class<?>> refTypes) {
|
||||
Map<Class<?>, Module> modulePrivateTypes = new HashMap<>();
|
||||
Map<Class<?>, Module> packagePrivateTypes = new HashMap<>();
|
||||
for (Class<?> intf : interfaces) {
|
||||
Module m = intf.getModule();
|
||||
if (Modifier.isPublic(intf.getModifiers())) {
|
||||
// module-private types
|
||||
if (!m.isExported(intf.getPackageName())) {
|
||||
modulePrivateTypes.put(intf, m);
|
||||
}
|
||||
} else {
|
||||
if (!Modifier.isPublic(intf.getModifiers())) {
|
||||
packagePrivateTypes.put(intf, m);
|
||||
}
|
||||
}
|
||||
|
||||
// all proxy interfaces are public and exported, the proxy class
|
||||
// is in unnamed module. Such proxy class is accessible to
|
||||
// any unnamed module and named module that can read unnamed module
|
||||
if (packagePrivateTypes.isEmpty() && modulePrivateTypes.isEmpty()) {
|
||||
return loader != null ? loader.getUnnamedModule()
|
||||
: BootLoader.getUnnamedModule();
|
||||
}
|
||||
|
||||
if (packagePrivateTypes.size() > 0) {
|
||||
// all package-private types must be in the same runtime package
|
||||
// i.e. same package name and same module (named or unnamed)
|
||||
//
|
||||
// Configuration will fail if M1 and in M2 defined by the same loader
|
||||
// and both have the same package p (so no need to check class loader)
|
||||
if (packagePrivateTypes.size() > 1 &&
|
||||
(packagePrivateTypes.keySet().stream() // more than one package
|
||||
.map(Class::getPackageName).distinct().count() > 1 ||
|
||||
packagePrivateTypes.values().stream() // or more than one module
|
||||
.distinct().count() > 1)) {
|
||||
throw new IllegalArgumentException(
|
||||
"non-public interfaces from different packages");
|
||||
}
|
||||
|
||||
// all package-private types are in the same module (named or unnamed)
|
||||
Module target = null;
|
||||
for (Module m : packagePrivateTypes.values()) {
|
||||
Module targetModule = null;
|
||||
String targetPackageName = null;
|
||||
for (Map.Entry<Class<?>, Module> e : packagePrivateTypes.entrySet()) {
|
||||
Class<?> intf = e.getKey();
|
||||
Module m = e.getValue();
|
||||
if ((targetModule != null && targetModule != m) ||
|
||||
(targetPackageName != null && targetPackageName != intf.getPackageName())) {
|
||||
throw new IllegalArgumentException(
|
||||
"cannot have non-public interfaces in different packages");
|
||||
}
|
||||
if (getLoader(m) != loader) {
|
||||
// the specified loader is not the same class loader
|
||||
// of the non-public interface
|
||||
throw new IllegalArgumentException(
|
||||
"non-public interface is not defined by the given loader");
|
||||
}
|
||||
target = m;
|
||||
|
||||
targetModule = m;
|
||||
targetPackageName = e.getKey().getPackageName();
|
||||
}
|
||||
|
||||
// validate if the target module can access all other interfaces
|
||||
for (Class<?> intf : interfaces) {
|
||||
Module m = intf.getModule();
|
||||
if (m == target) continue;
|
||||
if (m == targetModule) continue;
|
||||
|
||||
if (!target.canRead(m) || !m.isExported(intf.getPackageName(), target)) {
|
||||
throw new IllegalArgumentException(target + " can't access " + intf.getName());
|
||||
if (!targetModule.canRead(m) || !m.isExported(intf.getPackageName(), targetModule)) {
|
||||
throw new IllegalArgumentException(targetModule + " can't access " + intf.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// opens the package of the non-public proxy class for java.base to access
|
||||
if (targetModule.isNamed()) {
|
||||
Modules.addOpens(targetModule, targetPackageName, Proxy.class.getModule());
|
||||
}
|
||||
// return the module of the package-private interface
|
||||
return target;
|
||||
return targetModule;
|
||||
}
|
||||
|
||||
// All proxy interfaces are public and at least one in a non-exported
|
||||
// package. So maps to a dynamic proxy module and add reads edge
|
||||
// and qualified exports, if necessary
|
||||
Module target = getDynamicModule(loader);
|
||||
// All proxy interfaces are public. So maps to a dynamic proxy module
|
||||
// and add reads edge and qualified exports, if necessary
|
||||
Module targetModule = getDynamicModule(loader);
|
||||
|
||||
// set up proxy class access to proxy interfaces and types
|
||||
// referenced in the method signature
|
||||
Set<Class<?>> types = new HashSet<>(interfaces);
|
||||
types.addAll(refTypes);
|
||||
for (Class<?> c : types) {
|
||||
ensureAccess(target, c);
|
||||
ensureAccess(targetModule, c);
|
||||
}
|
||||
return target;
|
||||
return targetModule;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -875,8 +890,9 @@ public class Proxy implements java.io.Serializable {
|
||||
private static final AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
/*
|
||||
* Define a dynamic module for the generated proxy classes in
|
||||
* a non-exported package named com.sun.proxy.$MODULE.
|
||||
* Define a dynamic module with a packge named $MODULE which
|
||||
* is unconditionally exported and another package named
|
||||
* com.sun.proxy.$MODULE which is encapsulated.
|
||||
*
|
||||
* Each class loader will have one dynamic module.
|
||||
*/
|
||||
@ -886,13 +902,16 @@ public class Proxy implements java.io.Serializable {
|
||||
String mn = "jdk.proxy" + counter.incrementAndGet();
|
||||
String pn = PROXY_PACKAGE_PREFIX + "." + mn;
|
||||
ModuleDescriptor descriptor =
|
||||
ModuleDescriptor.newModule(mn, Set.of(SYNTHETIC))
|
||||
.packages(Set.of(pn))
|
||||
.build();
|
||||
ModuleDescriptor.newModule(mn, Set.of(SYNTHETIC))
|
||||
.packages(Set.of(pn, mn))
|
||||
.exports(mn)
|
||||
.build();
|
||||
Module m = Modules.defineModule(ld, descriptor, null);
|
||||
Modules.addReads(m, Proxy.class.getModule());
|
||||
// java.base to create proxy instance
|
||||
Modules.addExports(m, pn, Object.class.getModule());
|
||||
Modules.addExports(m, mn);
|
||||
// java.base to create proxy instance and access its Lookup instance
|
||||
Modules.addOpens(m, pn, Proxy.class.getModule());
|
||||
Modules.addOpens(m, mn, Proxy.class.getModule());
|
||||
return m;
|
||||
});
|
||||
}
|
||||
@ -1120,4 +1139,205 @@ public class Proxy implements java.io.Serializable {
|
||||
}
|
||||
|
||||
private static final String PROXY_PACKAGE_PREFIX = ReflectUtil.PROXY_PACKAGE;
|
||||
|
||||
/**
|
||||
* A cache of Method -> MethodHandle for default methods.
|
||||
*/
|
||||
private static final ClassValue<ConcurrentHashMap<Method, MethodHandle>>
|
||||
DEFAULT_METHODS_MAP = new ClassValue<>() {
|
||||
@Override
|
||||
protected ConcurrentHashMap<Method, MethodHandle> computeValue(Class<?> type) {
|
||||
return new ConcurrentHashMap<>(4);
|
||||
}
|
||||
};
|
||||
|
||||
private static ConcurrentHashMap<Method, MethodHandle> defaultMethodMap(Class<?> proxyClass) {
|
||||
assert isProxyClass(proxyClass);
|
||||
return DEFAULT_METHODS_MAP.get(proxyClass);
|
||||
}
|
||||
|
||||
static final Object[] EMPTY_ARGS = new Object[0];
|
||||
|
||||
static MethodHandle defaultMethodHandle(Class<? extends Proxy> proxyClass, Method method) {
|
||||
// lookup the cached method handle
|
||||
ConcurrentHashMap<Method, MethodHandle> methods = defaultMethodMap(proxyClass);
|
||||
MethodHandle superMH = methods.get(method);
|
||||
if (superMH == null) {
|
||||
MethodType type = methodType(method.getReturnType(), method.getParameterTypes());
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
Class<?> proxyInterface = findProxyInterfaceOrElseThrow(proxyClass, method);
|
||||
MethodHandle dmh;
|
||||
try {
|
||||
dmh = proxyClassLookup(lookup, proxyClass)
|
||||
.findSpecial(proxyInterface, method.getName(), type, proxyClass)
|
||||
.withVarargs(false);
|
||||
} catch (IllegalAccessException | NoSuchMethodException e) {
|
||||
// should not reach here
|
||||
throw new InternalError(e);
|
||||
}
|
||||
// this check can be turned into assertion as it is guaranteed to succeed by the virtue of
|
||||
// looking up a default (instance) method declared or inherited by proxyInterface
|
||||
// while proxyClass implements (is a subtype of) proxyInterface ...
|
||||
assert ((BooleanSupplier) () -> {
|
||||
try {
|
||||
// make sure that the method type matches
|
||||
dmh.asType(type.insertParameterTypes(0, proxyClass));
|
||||
return true;
|
||||
} catch (WrongMethodTypeException e) {
|
||||
return false;
|
||||
}
|
||||
}).getAsBoolean() : "Wrong method type";
|
||||
// change return type to Object
|
||||
MethodHandle mh = dmh.asType(dmh.type().changeReturnType(Object.class));
|
||||
// wrap any exception thrown with InvocationTargetException
|
||||
mh = MethodHandles.catchException(mh, Throwable.class, InvocationException.wrapMH());
|
||||
// spread array of arguments among parameters (skipping 1st parameter - target)
|
||||
mh = mh.asSpreader(1, Object[].class, type.parameterCount());
|
||||
// change target type to Object
|
||||
mh = mh.asType(MethodType.methodType(Object.class, Object.class, Object[].class));
|
||||
|
||||
// push MH into cache
|
||||
MethodHandle cached = methods.putIfAbsent(method, mh);
|
||||
if (cached != null) {
|
||||
superMH = cached;
|
||||
} else {
|
||||
superMH = mh;
|
||||
}
|
||||
}
|
||||
return superMH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first proxy interface that declares the given method
|
||||
* directly or indirectly.
|
||||
*
|
||||
* @throws IllegalArgumentException if not found
|
||||
*/
|
||||
private static Class<?> findProxyInterfaceOrElseThrow(Class<?> proxyClass, Method method) {
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
if (!declaringClass.isInterface()) {
|
||||
throw new IllegalArgumentException("\"" + method +
|
||||
"\" is not a method declared in the proxy class");
|
||||
}
|
||||
|
||||
List<Class<?>> proxyInterfaces = Arrays.asList(proxyClass.getInterfaces());
|
||||
// the method's declaring class is a proxy interface
|
||||
if (proxyInterfaces.contains(declaringClass))
|
||||
return declaringClass;
|
||||
|
||||
// find the first proxy interface that inherits the default method
|
||||
// i.e. the declaring class of the default method is a superinterface
|
||||
// of the proxy interface
|
||||
Deque<Class<?>> deque = new ArrayDeque<>();
|
||||
Set<Class<?>> visited = new HashSet<>();
|
||||
boolean indirectMethodRef = false;
|
||||
for (Class<?> proxyIntf : proxyInterfaces) {
|
||||
assert proxyIntf != declaringClass;
|
||||
visited.add(proxyIntf);
|
||||
deque.add(proxyIntf);
|
||||
|
||||
// for each proxy interface, traverse its subinterfaces with
|
||||
// breadth-first search to find a subinterface declaring the
|
||||
// default method
|
||||
Class<?> c;
|
||||
while ((c = deque.poll()) != null) {
|
||||
if (c == declaringClass) {
|
||||
try {
|
||||
// check if this method is the resolved method if referenced from
|
||||
// this proxy interface (i.e. this method is not implemented
|
||||
// by any other superinterface)
|
||||
Method m = proxyIntf.getMethod(method.getName(), method.getParameterTypes());
|
||||
if (m.getDeclaringClass() == declaringClass) {
|
||||
return proxyIntf;
|
||||
}
|
||||
indirectMethodRef = true;
|
||||
} catch (NoSuchMethodException e) {}
|
||||
|
||||
// skip traversing its superinterfaces
|
||||
// another proxy interface may extend it and so
|
||||
// the method's declaring class is left unvisited.
|
||||
continue;
|
||||
}
|
||||
// visit all superinteraces of one proxy interface to find if
|
||||
// this proxy interface inherits the method directly or indirectly
|
||||
visited.add(c);
|
||||
for (Class<?> superIntf : c.getInterfaces()) {
|
||||
if (!visited.contains(superIntf) && !deque.contains(superIntf)) {
|
||||
if (superIntf == declaringClass) {
|
||||
// fast-path as the matching subinterface is found
|
||||
deque.addFirst(superIntf);
|
||||
} else {
|
||||
deque.add(superIntf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("\"" + method + (indirectMethodRef
|
||||
? "\" is overridden directly or indirectly by the proxy interfaces"
|
||||
: "\" is not a method declared in the proxy class"));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method invokes the proxy's proxyClassLookup method to get a
|
||||
* Lookup on the proxy class.
|
||||
*
|
||||
* @return a lookup for proxy class of this proxy instance
|
||||
*/
|
||||
private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup caller, Class<?> proxyClass) {
|
||||
return AccessController.doPrivileged(new PrivilegedAction<>() {
|
||||
@Override
|
||||
public MethodHandles.Lookup run() {
|
||||
try {
|
||||
Method m = proxyClass.getDeclaredMethod("proxyClassLookup", MethodHandles.Lookup.class);
|
||||
m.setAccessible(true);
|
||||
return (MethodHandles.Lookup) m.invoke(null, caller);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal exception type to wrap the exception thrown by the default method
|
||||
* so that it can distinguish CCE and NPE thrown due to the arguments
|
||||
* incompatible with the method signature.
|
||||
*/
|
||||
static class InvocationException extends ReflectiveOperationException {
|
||||
@java.io.Serial
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
InvocationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps given cause with InvocationException and throws it.
|
||||
*/
|
||||
static Object wrap(Throwable cause) throws InvocationException {
|
||||
throw new InvocationException(cause);
|
||||
}
|
||||
|
||||
@Stable
|
||||
static MethodHandle wrapMethodHandle;
|
||||
|
||||
static MethodHandle wrapMH() {
|
||||
MethodHandle mh = wrapMethodHandle;
|
||||
if (mh == null) {
|
||||
try {
|
||||
wrapMethodHandle = mh = MethodHandles.lookup().findStatic(
|
||||
InvocationException.class,
|
||||
"wrap",
|
||||
MethodType.methodType(Object.class, Throwable.class)
|
||||
);
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
return mh;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import jdk.internal.org.objectweb.asm.ClassWriter;
|
||||
import jdk.internal.org.objectweb.asm.Label;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.internal.org.objectweb.asm.Opcodes;
|
||||
import jdk.internal.org.objectweb.asm.Type;
|
||||
import sun.security.action.GetBooleanAction;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -59,9 +60,13 @@ final class ProxyGenerator extends ClassWriter {
|
||||
private static final String JL_OBJECT = "java/lang/Object";
|
||||
private static final String JL_THROWABLE = "java/lang/Throwable";
|
||||
private static final String JL_CLASS_NOT_FOUND_EX = "java/lang/ClassNotFoundException";
|
||||
private static final String JL_ILLEGAL_ACCESS_EX = "java/lang/IllegalAccessException";
|
||||
|
||||
private static final String JL_NO_CLASS_DEF_FOUND_ERROR = "java/lang/NoClassDefFoundError";
|
||||
private static final String JL_NO_SUCH_METHOD_EX = "java/lang/NoSuchMethodException";
|
||||
private static final String JL_NO_SUCH_METHOD_ERROR = "java/lang/NoSuchMethodError";
|
||||
private static final String JLI_LOOKUP = "java/lang/invoke/MethodHandles$Lookup";
|
||||
private static final String JLI_METHODHANDLES = "java/lang/invoke/MethodHandles";
|
||||
|
||||
private static final String JLR_INVOCATION_HANDLER = "java/lang/reflect/InvocationHandler";
|
||||
private static final String JLR_PROXY = "java/lang/reflect/Proxy";
|
||||
@ -75,6 +80,7 @@ final class ProxyGenerator extends ClassWriter {
|
||||
|
||||
private static final String NAME_CTOR = "<init>";
|
||||
private static final String NAME_CLINIT = "<clinit>";
|
||||
private static final String NAME_LOOKUP_ACCESSOR = "proxyClassLookup";
|
||||
|
||||
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
|
||||
|
||||
@ -484,7 +490,7 @@ final class ProxyGenerator extends ClassWriter {
|
||||
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
|
||||
for (ProxyMethod pm : sigmethods) {
|
||||
// add static field for the Method object
|
||||
visitField(Modifier.PRIVATE | Modifier.STATIC, pm.methodFieldName,
|
||||
visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, pm.methodFieldName,
|
||||
LJLR_METHOD, null, null);
|
||||
|
||||
// Generate code for proxy method
|
||||
@ -493,7 +499,7 @@ final class ProxyGenerator extends ClassWriter {
|
||||
}
|
||||
|
||||
generateStaticInitializer();
|
||||
|
||||
generateLookupAccessor();
|
||||
return toByteArray();
|
||||
}
|
||||
|
||||
@ -625,6 +631,46 @@ final class ProxyGenerator extends ClassWriter {
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the static lookup accessor method that returns the Lookup
|
||||
* on this proxy class if the caller's lookup class is java.lang.reflect.Proxy;
|
||||
* otherwise, IllegalAccessException is thrown
|
||||
*/
|
||||
private void generateLookupAccessor() {
|
||||
MethodVisitor mv = visitMethod(ACC_PRIVATE | ACC_STATIC, NAME_LOOKUP_ACCESSOR,
|
||||
"(Ljava/lang/invoke/MethodHandles$Lookup;)Ljava/lang/invoke/MethodHandles$Lookup;", null,
|
||||
new String[] { JL_ILLEGAL_ACCESS_EX });
|
||||
mv.visitCode();
|
||||
Label L_illegalAccess = new Label();
|
||||
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "lookupClass",
|
||||
"()Ljava/lang/Class;", false);
|
||||
mv.visitLdcInsn(Type.getType(Proxy.class));
|
||||
mv.visitJumpInsn(IF_ACMPNE, L_illegalAccess);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "hasFullPrivilegeAccess",
|
||||
"()Z", false);
|
||||
mv.visitJumpInsn(IFEQ, L_illegalAccess);
|
||||
mv.visitMethodInsn(INVOKESTATIC, JLI_METHODHANDLES, "lookup",
|
||||
"()Ljava/lang/invoke/MethodHandles$Lookup;", false);
|
||||
mv.visitInsn(ARETURN);
|
||||
|
||||
mv.visitLabel(L_illegalAccess);
|
||||
mv.visitTypeInsn(Opcodes.NEW, JL_ILLEGAL_ACCESS_EX);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "toString",
|
||||
"()Ljava/lang/String;", false);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, JL_ILLEGAL_ACCESS_EX,
|
||||
"<init>", "(Ljava/lang/String;)V", false);
|
||||
mv.visitInsn(ATHROW);
|
||||
|
||||
// Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored
|
||||
mv.visitMaxs(-1, -1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* A ProxyMethod object represents a proxy method in the proxy class
|
||||
* being generated: a method whose implementation will encode and
|
||||
|
@ -209,6 +209,11 @@ public interface JavaLangAccess {
|
||||
*/
|
||||
void addReadsAllUnnamed(Module m);
|
||||
|
||||
/**
|
||||
* Updates module m1 to export a package unconditionally.
|
||||
*/
|
||||
void addExports(Module m1, String pkg);
|
||||
|
||||
/**
|
||||
* Updates module m1 to export a package to module m2. The export does
|
||||
* not result in a strong reference to m2 (m2 can be GC'ed).
|
||||
|
@ -105,6 +105,13 @@ public class Modules {
|
||||
JLA.addExports(m1, pn, m2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates module m to export a package unconditionally.
|
||||
*/
|
||||
public static void addExports(Module m, String pn) {
|
||||
JLA.addExports(m, pn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates module m to export a package to all unnamed modules.
|
||||
*/
|
||||
|
@ -263,8 +263,7 @@ public final class ReflectUtil {
|
||||
if (!Proxy.isProxyClass(cls)) {
|
||||
return false;
|
||||
}
|
||||
String pkg = cls.getPackageName();
|
||||
return pkg == null || !pkg.startsWith(PROXY_PACKAGE);
|
||||
return !Modifier.isPublic(cls.getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
|
386
test/jdk/java/lang/reflect/Proxy/DefaultMethods.java
Normal file
386
test/jdk/java/lang/reflect/Proxy/DefaultMethods.java
Normal file
@ -0,0 +1,386 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8159746
|
||||
* @run testng DefaultMethods
|
||||
* @summary Basic tests for Proxy::invokeSuper default method
|
||||
*/
|
||||
|
||||
public class DefaultMethods {
|
||||
public interface I1 {
|
||||
default int m() {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
public interface I2 {
|
||||
default int m() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
private void privateMethod() {
|
||||
throw new Error("should not reach here");
|
||||
}
|
||||
}
|
||||
|
||||
// I3::m inherits from I2:m
|
||||
public interface I3 extends I2 {
|
||||
default int m3(String... s) {
|
||||
return Arrays.stream(s).mapToInt(String::length).sum();
|
||||
}
|
||||
}
|
||||
|
||||
public interface I4 extends I1, I2 {
|
||||
default int m() {
|
||||
return 40;
|
||||
}
|
||||
|
||||
default int mix(int a, String b) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public interface I12 extends I1, I2 {
|
||||
@Override
|
||||
int m();
|
||||
|
||||
default int sum(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
default Object[] concat(Object first, Object... rest) {
|
||||
Object[] result = new Object[1 + rest.length];
|
||||
result[0] = first;
|
||||
System.arraycopy(rest, 0, result, 1, rest.length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IX {
|
||||
default void doThrow(Throwable exception) throws Throwable {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private static Method findDefaultMethod(Class<?> refc, Method m) {
|
||||
try {
|
||||
assertTrue(refc.isInterface());
|
||||
|
||||
Method method = refc.getMethod(m.getName(), m.getParameterTypes());
|
||||
assertTrue(method.isDefault());
|
||||
return method;
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
||||
Object proxy = Proxy.newProxyInstance(loader, new Class<?>[] { I1.class, I2.class},
|
||||
(o, method, params) -> {
|
||||
return InvocationHandler.invokeDefault(o, findDefaultMethod(I2.class, method), params);
|
||||
});
|
||||
I1 i1 = (I1) proxy;
|
||||
assertEquals(i1.m(), 20);
|
||||
}
|
||||
|
||||
// a default method is declared in one of the proxy interfaces
|
||||
@DataProvider(name = "defaultMethods")
|
||||
private Object[][] defaultMethods() {
|
||||
return new Object[][]{
|
||||
new Object[]{new Class<?>[]{I1.class, I2.class}, true, 10},
|
||||
new Object[]{new Class<?>[]{I1.class, I3.class}, true, 10},
|
||||
new Object[]{new Class<?>[]{I1.class, I12.class}, true, 10},
|
||||
new Object[]{new Class<?>[]{I2.class, I12.class}, true, 20},
|
||||
new Object[]{new Class<?>[]{I4.class}, true, 40},
|
||||
new Object[]{new Class<?>[]{I4.class, I3.class}, true, 40},
|
||||
new Object[]{new Class<?>[]{I12.class}, false, -1},
|
||||
new Object[]{new Class<?>[]{I12.class, I1.class, I2.class}, false, -1}
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "defaultMethods")
|
||||
public void testDefaultMethod(Class<?>[] intfs, boolean isDefault, int expected) throws Throwable {
|
||||
InvocationHandler ih = (proxy, method, params) -> {
|
||||
System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
|
||||
switch (method.getName()) {
|
||||
case "m":
|
||||
assertTrue(method.isDefault() == isDefault);
|
||||
assertTrue(Arrays.stream(proxy.getClass().getInterfaces())
|
||||
.anyMatch(intf -> method.getDeclaringClass() == intf),
|
||||
Arrays.toString(proxy.getClass().getInterfaces()));
|
||||
if (method.isDefault()) {
|
||||
return InvocationHandler.invokeDefault(proxy, method, params);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedOperationException(method.toString());
|
||||
}
|
||||
};
|
||||
|
||||
Object proxy = Proxy.newProxyInstance(DefaultMethods.class.getClassLoader(), intfs, ih);
|
||||
Method m = proxy.getClass().getMethod("m");
|
||||
int result = (int)m.invoke(proxy);
|
||||
assertEquals(result, expected);
|
||||
}
|
||||
|
||||
// a default method may be declared in a proxy interface or
|
||||
// inherited from a superinterface of a proxy interface
|
||||
@DataProvider(name = "supers")
|
||||
private Object[][] supers() {
|
||||
return new Object[][]{
|
||||
// invoke "m" implemented in the first proxy interface
|
||||
// same as the method passed to InvocationHandler::invoke
|
||||
new Object[]{new Class<?>[]{I1.class}, I1.class, 10},
|
||||
new Object[]{new Class<?>[]{I2.class}, I2.class, 20},
|
||||
new Object[]{new Class<?>[]{I1.class, I2.class}, I1.class, 10},
|
||||
// "m" is implemented in I2, an indirect superinterface of I3
|
||||
new Object[]{new Class<?>[]{I3.class}, I3.class, 20},
|
||||
// "m" is implemented in I1, I2 and overridden in I4
|
||||
new Object[]{new Class<?>[]{I4.class}, I4.class, 40},
|
||||
// invoke "m" implemented in the second proxy interface
|
||||
// different from the method passed to InvocationHandler::invoke
|
||||
new Object[]{new Class<?>[]{I1.class, I2.class}, I2.class, 20},
|
||||
new Object[]{new Class<?>[]{I1.class, I3.class}, I3.class, 20},
|
||||
// I2::m is implemented in more than one proxy interface directly or indirectly
|
||||
// I3::m resolves to I2::m (indirect superinterface)
|
||||
// I2 is the superinterface of I4 and I4 overrides m
|
||||
// the proxy class can invoke I4::m and I2::m
|
||||
new Object[]{new Class<?>[]{I3.class, I4.class}, I3.class, 20},
|
||||
new Object[]{new Class<?>[]{I3.class, I4.class}, I4.class, 40},
|
||||
new Object[]{new Class<?>[]{I4.class, I3.class}, I3.class, 20},
|
||||
new Object[]{new Class<?>[]{I4.class, I3.class}, I4.class, 40}
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "supers")
|
||||
public void testSuper(Class<?>[] intfs, Class<?> proxyInterface, int expected) throws Throwable {
|
||||
final InvocationHandler ih = (proxy, method, params) -> {
|
||||
switch (method.getName()) {
|
||||
case "m":
|
||||
assertTrue(method.isDefault());
|
||||
return InvocationHandler.invokeDefault(proxy, findDefaultMethod(proxyInterface, method), params);
|
||||
default:
|
||||
throw new UnsupportedOperationException(method.toString());
|
||||
}
|
||||
};
|
||||
ClassLoader loader = proxyInterface.getClassLoader();
|
||||
Object proxy = Proxy.newProxyInstance(loader, intfs, ih);
|
||||
if (proxyInterface == I1.class) {
|
||||
I1 i1 = (I1) proxy;
|
||||
assertEquals(i1.m(), expected);
|
||||
} else if (proxyInterface == I2.class) {
|
||||
I2 i2 = (I2) proxy;
|
||||
assertEquals(i2.m(), expected);
|
||||
} else if (proxyInterface == I3.class) {
|
||||
I3 i3 = (I3) proxy;
|
||||
assertEquals(i3.m(), expected);
|
||||
} else if (proxyInterface == I4.class) {
|
||||
I4 i4 = (I4) proxy;
|
||||
assertEquals(i4.m(), expected);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(proxyInterface.toString());
|
||||
}
|
||||
// invoke via InvocationHandler.invokeDefaultMethod directly
|
||||
assertEquals(InvocationHandler.invokeDefault(proxy, proxyInterface.getMethod("m")), expected);
|
||||
}
|
||||
|
||||
// invoke I12 default methods with parameters and var args
|
||||
@Test
|
||||
public void testI12() throws Throwable {
|
||||
final InvocationHandler ih = (proxy, method, params) -> {
|
||||
System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
|
||||
switch (method.getName()) {
|
||||
case "sum":
|
||||
case "concat":
|
||||
assertTrue(method.isDefault());
|
||||
return InvocationHandler.invokeDefault(proxy, method, params);
|
||||
default:
|
||||
throw new UnsupportedOperationException(method.toString());
|
||||
}
|
||||
};
|
||||
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
||||
I12 i12 = (I12) Proxy.newProxyInstance(loader, new Class<?>[] { I12.class }, ih);
|
||||
assertEquals(i12.sum(1, 2), 3);
|
||||
assertEquals(i12.concat(1, 2, 3, 4), new Object[]{1, 2, 3, 4});
|
||||
Method m = I12.class.getMethod("concat", Object.class, Object[].class);
|
||||
assertTrue(m.isDefault());
|
||||
assertEquals(InvocationHandler.invokeDefault(i12, m, 100, new Object[] {"foo", true, "bar"}),
|
||||
new Object[] {100, "foo", true, "bar"});
|
||||
}
|
||||
|
||||
// test a no-arg default method with and without arguments passed in the invocation
|
||||
@Test
|
||||
public void testEmptyArgument() throws Throwable {
|
||||
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
||||
Object proxy = Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER);
|
||||
Method m1 = I4.class.getMethod("m");
|
||||
assertTrue(m1.getDeclaringClass() == I4.class);
|
||||
assertTrue(m1.isDefault());
|
||||
InvocationHandler.invokeDefault(proxy, m1);
|
||||
InvocationHandler.invokeDefault(proxy, m1, new Object[0]);
|
||||
|
||||
Method m2 = I4.class.getMethod("mix", int.class, String.class);
|
||||
assertTrue(m1.getDeclaringClass() == I4.class);
|
||||
assertTrue(m1.isDefault());
|
||||
InvocationHandler.invokeDefault(proxy, m2, Integer.valueOf(100), "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVarArgs() throws Throwable {
|
||||
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
||||
I3 proxy = (I3)Proxy.newProxyInstance(loader, new Class<?>[]{I3.class}, HANDLER);
|
||||
Method m = I3.class.getMethod("m3", String[].class);
|
||||
assertTrue(m.isVarArgs() && m.isDefault());
|
||||
assertEquals(proxy.m3("a", "b", "cde"), 5);
|
||||
assertEquals(InvocationHandler.invokeDefault(proxy, m, (Object)new String[] { "a", "bc" }), 3);
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoke I12::m which is an abstract method
|
||||
*/
|
||||
@Test(expectedExceptions = {IllegalArgumentException.class})
|
||||
public void invokeAbstractMethod() throws Exception {
|
||||
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
||||
I12 proxy = (I12) Proxy.newProxyInstance(loader, new Class<?>[]{I12.class}, HANDLER);
|
||||
Method method = I12.class.getMethod("m");
|
||||
assertTrue(method.getDeclaringClass() == I12.class);
|
||||
assertFalse(method.isDefault());
|
||||
proxy.m();
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoke a non proxy (default) method with parameters
|
||||
*/
|
||||
@Test(expectedExceptions = {IllegalArgumentException.class})
|
||||
public void invokeNonProxyMethod() throws Throwable {
|
||||
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
||||
I3 proxy = (I3) Proxy.newProxyInstance(loader, new Class<?>[]{I3.class}, HANDLER);
|
||||
Method m = I4.class.getMethod("mix", int.class, String.class);
|
||||
assertTrue(m.isDefault());
|
||||
InvocationHandler.invokeDefault(proxy, m);
|
||||
}
|
||||
|
||||
// negative cases
|
||||
@DataProvider(name = "negativeCases")
|
||||
private Object[][] negativeCases() {
|
||||
return new Object[][]{
|
||||
// I4::m overrides I1::m and I2::m
|
||||
new Object[] { new Class<?>[]{I4.class}, I1.class, "m" },
|
||||
new Object[] { new Class<?>[]{I4.class}, I2.class, "m" },
|
||||
// I12::m is not a default method
|
||||
new Object[] { new Class<?>[]{I12.class}, I12.class, "m" },
|
||||
// non-proxy default method
|
||||
new Object[] { new Class<?>[]{I3.class}, I1.class, "m" },
|
||||
// not a default method and not a proxy interface
|
||||
new Object[] { new Class<?>[]{I12.class}, DefaultMethods.class, "test" },
|
||||
new Object[] { new Class<?>[]{I12.class}, Runnable.class, "run" },
|
||||
// I2::privateMethod is a private method
|
||||
new Object[] { new Class<?>[]{I3.class}, I2.class, "privateMethod" }
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "negativeCases", expectedExceptions = {IllegalArgumentException.class})
|
||||
public void testNegativeCase(Class<?>[] interfaces, Class<?> defc, String name)
|
||||
throws Throwable {
|
||||
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
||||
Object proxy = Proxy.newProxyInstance(loader, interfaces, HANDLER);
|
||||
try {
|
||||
Method method = defc.getDeclaredMethod(name);
|
||||
InvocationHandler.invokeDefault(proxy, method);
|
||||
} catch (Throwable e) {
|
||||
System.out.format("%s method %s::%s exception thrown: %s%n",
|
||||
Arrays.toString(interfaces), defc.getName(), name, e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@DataProvider(name = "illegalArguments")
|
||||
private Object[][] illegalArguments() {
|
||||
return new Object[][] {
|
||||
new Object[] {},
|
||||
new Object[] { 100 },
|
||||
new Object[] { 100, "foo", 100 },
|
||||
new Object[] { 100L, "foo" },
|
||||
new Object[] { "foo", 100},
|
||||
new Object[] { null, "foo" }
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "illegalArguments", expectedExceptions = {IllegalArgumentException.class})
|
||||
public void testIllegalArgument(Object... args) throws Throwable {
|
||||
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
||||
I4 proxy = (I4)Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER);
|
||||
Method m = I4.class.getMethod("mix", int.class, String.class);
|
||||
assertTrue(m.isDefault());
|
||||
if (args.length == 0) {
|
||||
// substitute empty args with null since @DataProvider doesn't allow null array
|
||||
args = null;
|
||||
}
|
||||
InvocationHandler.invokeDefault(proxy, m, args);
|
||||
}
|
||||
|
||||
@DataProvider(name = "throwables")
|
||||
private Object[][] throwables() {
|
||||
return new Object[][] {
|
||||
new Object[] { new IOException() },
|
||||
new Object[] { new IllegalArgumentException() },
|
||||
new Object[] { new ClassCastException() },
|
||||
new Object[] { new NullPointerException() },
|
||||
new Object[] { new AssertionError() },
|
||||
new Object[] { new Throwable() }
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "throwables")
|
||||
public void testInvocationException(Throwable exception) throws Throwable {
|
||||
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
||||
IX proxy = (IX)Proxy.newProxyInstance(loader, new Class<?>[]{IX.class}, HANDLER);
|
||||
Method m = IX.class.getMethod("doThrow", Throwable.class);
|
||||
try {
|
||||
InvocationHandler.invokeDefault(proxy, m, exception);
|
||||
} catch (Throwable e) {
|
||||
assertEquals(e, exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static final InvocationHandler HANDLER = (proxy, method, params) -> {
|
||||
System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
|
||||
return InvocationHandler.invokeDefault(proxy, method, params);
|
||||
};
|
||||
}
|
@ -87,7 +87,7 @@ public class ProxyClassAccessTest {
|
||||
* Test unnamed module has no access to other proxy interface
|
||||
*/
|
||||
@Test
|
||||
public void testNoReadAccess() throws Exception {
|
||||
public void testNoReadAccess() throws Throwable {
|
||||
ModuleFinder finder = ModuleFinder.of(MODS_DIR);
|
||||
ModuleLayer bootLayer = ModuleLayer.boot();
|
||||
Configuration cf = bootLayer
|
||||
@ -104,7 +104,7 @@ public class ProxyClassAccessTest {
|
||||
checkIAE(loader, interfaces);
|
||||
}
|
||||
|
||||
private void checkIAE(ClassLoader loader, Class<?>[] interfaces) {
|
||||
private void checkIAE(ClassLoader loader, Class<?>[] interfaces) throws Throwable {
|
||||
try {
|
||||
Proxy.getProxyClass(loader, interfaces);
|
||||
throw new RuntimeException("Expected IllegalArgumentException thrown");
|
||||
|
@ -99,8 +99,9 @@ public class ProxyLayerTest {
|
||||
|
||||
Class<?> proxyClass = o.getClass();
|
||||
Package pkg = proxyClass.getPackage();
|
||||
assertFalse(proxyClass.getModule().isNamed());
|
||||
assertFalse(pkg.isSealed());
|
||||
assertTrue(proxyClass.getModule().isNamed());
|
||||
assertTrue(pkg.isSealed());
|
||||
assertTrue(proxyClass.getModule().isExported(pkg.getName()));
|
||||
assertEquals(proxyClass.getModule().getLayer(), null);
|
||||
}
|
||||
|
||||
@ -132,6 +133,7 @@ public class ProxyLayerTest {
|
||||
Package pkg = proxyClass.getPackage();
|
||||
assertTrue(proxyClass.getModule().isNamed());
|
||||
assertTrue(pkg.isSealed());
|
||||
assertFalse(proxyClass.getModule().isExported(pkg.getName()));
|
||||
assertEquals(proxyClass.getModule().getLayer(), null);
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ public class ProxyModuleMapping {
|
||||
public static void main(String... args) throws Exception {
|
||||
ClassLoader ld = ProxyModuleMapping.class.getClassLoader();
|
||||
Module unnamed = ld.getUnnamedModule();
|
||||
new ProxyModuleMapping(unnamed, Runnable.class).test();
|
||||
new ProxyModuleMapping(Runnable.class).test();
|
||||
|
||||
// unnamed module gets access to sun.invoke package (e.g. via --add-exports)
|
||||
new ProxyModuleMapping(sun.invoke.WrapperInstance.class).test();
|
||||
@ -79,11 +79,13 @@ public class ProxyModuleMapping {
|
||||
try {
|
||||
Constructor<?> cons = c.getConstructor(InvocationHandler.class);
|
||||
cons.newInstance(ih);
|
||||
if (module.isNamed()) {
|
||||
// the exported package name is same as the module name
|
||||
if (!c.getPackageName().equals(module.getName())) {
|
||||
throw new RuntimeException("expected IAE not thrown");
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
if (!module.isNamed()) {
|
||||
// non-exported package from the dynamic module
|
||||
if (c.getPackageName().equals(module.getName())) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -81,4 +81,20 @@ public class ProxyTest {
|
||||
|
||||
assertTrue(exitValue == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests invocation of default methods in exported and non-exported types
|
||||
* in a named module
|
||||
*/
|
||||
@Test
|
||||
public void runDefaultMethodsTest() throws Exception {
|
||||
int exitValue = executeTestJava("-cp", CPATH_DIR.toString(),
|
||||
"--module-path", MODS_DIR.toString(),
|
||||
"-m", "test/jdk.test.DefaultMethods")
|
||||
.outputTo(System.out)
|
||||
.errorTo(System.out)
|
||||
.getExitValue();
|
||||
|
||||
assertTrue(exitValue == 0);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8159746
|
||||
* @summary Test invoking a default method in a non-public proxy interface
|
||||
* @build p.Foo p.Bar p.ProxyMaker
|
||||
* @run testng DefaultMethodProxy
|
||||
*/
|
||||
public class DefaultMethodProxy {
|
||||
public interface I {
|
||||
default String m() { return "I"; }
|
||||
}
|
||||
|
||||
@Test
|
||||
public static void publicInterface() throws ReflectiveOperationException {
|
||||
// create a proxy instance of a public proxy interface should succeed
|
||||
Proxy proxy = (Proxy)Proxy.newProxyInstance(DefaultMethodProxy.class.getClassLoader(),
|
||||
new Class<?>[] { I.class }, IH);
|
||||
|
||||
testDefaultMethod(proxy, "I");
|
||||
|
||||
// can get the invocation handler
|
||||
assertTrue(Proxy.getInvocationHandler(proxy) == IH);
|
||||
}
|
||||
|
||||
|
||||
@DataProvider(name = "nonPublicIntfs")
|
||||
private static Object[][] nonPublicIntfs() throws ClassNotFoundException {
|
||||
Class<?> fooClass = Class.forName("p.Foo");
|
||||
Class<?> barClass = Class.forName("p.Bar");
|
||||
return new Object[][]{
|
||||
new Object[]{new Class<?>[]{ fooClass }, "foo"},
|
||||
new Object[]{new Class<?>[]{ barClass, fooClass }, "bar"},
|
||||
new Object[]{new Class<?>[]{ barClass }, "bar"},
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "nonPublicIntfs")
|
||||
public static void hasPackageAccess(Class<?>[] intfs, String expected) throws ReflectiveOperationException {
|
||||
Proxy proxy = (Proxy)Proxy.newProxyInstance(DefaultMethodProxy.class.getClassLoader(), intfs, IH);
|
||||
testDefaultMethod(proxy, expected);
|
||||
|
||||
// proxy instance is created successfully even invocation handler has no access
|
||||
Proxy.newProxyInstance(DefaultMethodProxy.class.getClassLoader(), intfs, IH_NO_ACCESS);
|
||||
}
|
||||
|
||||
// IAE thrown at invocation time
|
||||
@Test(dataProvider = "nonPublicIntfs", expectedExceptions = {IllegalAccessException.class})
|
||||
public static void noPackageAccess(Class<?>[] intfs, String ignored) throws Throwable {
|
||||
Proxy proxy = (Proxy)Proxy.newProxyInstance(DefaultMethodProxy.class.getClassLoader(), intfs, IH_NO_ACCESS);
|
||||
try {
|
||||
testDefaultMethod(proxy, "dummy");
|
||||
} catch (InvocationTargetException e) {
|
||||
// unwrap the exception
|
||||
if (e.getCause() instanceof UndeclaredThrowableException) {
|
||||
Throwable cause = e.getCause();
|
||||
throw cause.getCause();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify if a default method "m" can be invoked successfully
|
||||
*/
|
||||
static void testDefaultMethod(Proxy proxy, String expected) throws ReflectiveOperationException {
|
||||
Method m = proxy.getClass().getDeclaredMethod("m");
|
||||
m.setAccessible(true);
|
||||
String name = (String) m.invoke(proxy);
|
||||
if (!expected.equals(name)) {
|
||||
throw new RuntimeException("return value: " + name + " expected: " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
// invocation handler with access to the non-public interface in package p
|
||||
private static final InvocationHandler IH = (proxy, method, params) -> {
|
||||
System.out.format("Proxy for %s: invoking %s%n",
|
||||
Arrays.stream(proxy.getClass().getInterfaces())
|
||||
.map(Class::getName)
|
||||
.collect(Collectors.joining(", ")), method.getName());
|
||||
if (method.isDefault()) {
|
||||
return p.ProxyMaker.invoke(proxy, method, params);
|
||||
}
|
||||
throw new UnsupportedOperationException(method.toString());
|
||||
};
|
||||
|
||||
// invocation handler with no access to the non-public interface in package p
|
||||
// expect IllegalAccessException thrown
|
||||
private static final InvocationHandler IH_NO_ACCESS = (proxy, method, params) -> {
|
||||
System.out.format("Proxy for %s: invoking %s%n",
|
||||
Arrays.stream(proxy.getClass().getInterfaces())
|
||||
.map(Class::getName)
|
||||
.collect(Collectors.joining(", ")), method.getName());
|
||||
if (method.isDefault()) {
|
||||
InvocationHandler.invokeDefault(proxy, method, params);
|
||||
}
|
||||
throw new UnsupportedOperationException(method.toString());
|
||||
};
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,4 +23,5 @@
|
||||
|
||||
package p;
|
||||
interface Bar {
|
||||
default String m() { return "bar"; }
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,4 +23,5 @@
|
||||
|
||||
package p;
|
||||
interface Foo {
|
||||
default String m() { return "foo"; }
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package p;
|
||||
import java.lang.reflect.*;
|
||||
|
||||
public class ProxyMaker {
|
||||
public static Object invoke(Object proxy, Method method, Object... args)
|
||||
throws Throwable {
|
||||
return InvocationHandler.invokeDefault(proxy, method, args);
|
||||
}
|
||||
|
||||
// get the invocation handler associated with the proxy
|
||||
public static InvocationHandler getInvocationHandler(Object proxy) {
|
||||
return Proxy.getInvocationHandler(proxy);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,4 +25,6 @@ package p.one;
|
||||
|
||||
public interface I {
|
||||
void run();
|
||||
|
||||
default int m() { return 1; }
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -24,4 +24,5 @@
|
||||
package p.two.internal;
|
||||
|
||||
public interface C {
|
||||
default int m() { return 2; }
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -24,4 +24,7 @@
|
||||
package p.three.internal;
|
||||
|
||||
public interface Q {
|
||||
default int m() {
|
||||
throw new UnsupportedOperationException("Q::m is in a non-exported package");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.test;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
|
||||
/**
|
||||
* Tests invocation of default methods in exported types and inaccessible types
|
||||
* in a named module
|
||||
*/
|
||||
public class DefaultMethods {
|
||||
private final static Module TEST_MODULE = DefaultMethods.class.getModule();
|
||||
private final static InvocationHandler IH = (proxy, method, params) -> {
|
||||
return InvocationHandler.invokeDefault(proxy, method, params);
|
||||
};
|
||||
|
||||
public static void main(String... args) throws Throwable {
|
||||
// exported types from m1
|
||||
testDefaultMethod(new Class<?>[] { p.one.I.class, p.two.A.class}, 1);
|
||||
// qualified-exported type from m2
|
||||
testDefaultMethod(new Class<?>[] { p.two.internal.C.class, p.two.A.class }, 2);
|
||||
// module-private type from test module
|
||||
testDefaultMethod(new Class<?>[] { jdk.test.internal.R.class }, 10);
|
||||
// non-public interface in the same runtime package
|
||||
testDefaultMethod(new Class<?>[] { Class.forName("jdk.test.NP") }, 100);
|
||||
|
||||
// inaccessible type - not exported to test module
|
||||
Class<?> qType = Class.forName("p.three.internal.Q");
|
||||
inaccessibleDefaultMethod(qType);
|
||||
// non-public interface in the same runtime package
|
||||
Class<?> nonPublicType = Class.forName("jdk.test.internal.NP");
|
||||
inaccessibleDefaultMethod(nonPublicType);
|
||||
}
|
||||
|
||||
static void testDefaultMethod(Class<?>[] intfs, int expected) throws Exception {
|
||||
Object proxy = Proxy.newProxyInstance(TEST_MODULE.getClassLoader(), intfs, IH);
|
||||
if (!proxy.getClass().getModule().isNamed()) {
|
||||
throw new RuntimeException(proxy.getClass() + " expected to be in a named module");
|
||||
}
|
||||
Method m = intfs[0].getMethod("m");
|
||||
int result = (int)m.invoke(proxy);
|
||||
if (result != expected) {
|
||||
throw new RuntimeException("return value: " + result + " expected: " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
static void inaccessibleDefaultMethod(Class<?> intf) throws Throwable {
|
||||
Object proxy = Proxy.newProxyInstance(TEST_MODULE.getClassLoader(), new Class<?>[] { intf }, IH);
|
||||
if (!proxy.getClass().getModule().isNamed()) {
|
||||
throw new RuntimeException(proxy.getClass() + " expected to be in a named module");
|
||||
}
|
||||
Method m = intf.getMethod("m");
|
||||
try {
|
||||
InvocationHandler.invokeDefault(proxy, m, null);
|
||||
throw new RuntimeException("IAE not thrown invoking: " + m);
|
||||
} catch (IllegalAccessException e) {}
|
||||
|
||||
if (m.trySetAccessible()) {
|
||||
try {
|
||||
m.invoke(proxy);
|
||||
throw new RuntimeException("IAE not thrown invoking: " + m);
|
||||
} catch (InvocationTargetException e) {
|
||||
// IAE wrapped by InvocationHandler::invoke with UndeclaredThrowableException
|
||||
// then wrapped by Method::invoke with InvocationTargetException
|
||||
assert e.getCause() instanceof UndeclaredThrowableException;
|
||||
Throwable cause = e.getCause().getCause();
|
||||
if (!(cause instanceof IllegalAccessException))
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -66,22 +66,20 @@ public class Main {
|
||||
*/
|
||||
static Data[] proxiesForExportedTypes() {
|
||||
ClassLoader ld = Main.class.getClassLoader();
|
||||
Module unnamed = ld.getUnnamedModule();
|
||||
ClassLoader ld2 = new URLClassLoader(new URL[0], ld);
|
||||
Module unnamed2 = ld2.getUnnamedModule();
|
||||
|
||||
return new Data[] {
|
||||
new Data(unnamed, ld, Runnable.class),
|
||||
new Data(unnamed, ld, p.one.I.class),
|
||||
new Data(unnamed, ld, p.one.I.class, p.two.A.class),
|
||||
new Data(unnamed, ld, p.one.I.class, unnamedModuleClass),
|
||||
new Data(unnamed2, ld2, Runnable.class),
|
||||
new Data(unnamed2, ld2, p.one.I.class),
|
||||
new Data(unnamed2, ld2, p.one.I.class, p.two.A.class),
|
||||
new Data(unnamed2, ld2, p.one.I.class, unnamedModuleClass),
|
||||
new Data(unnamed, m1.getClassLoader(), p.one.I.class),
|
||||
new Data(unnamed, m2.getClassLoader(), p.two.A.class),
|
||||
new Data(unnamed, m3.getClassLoader(), p.three.P.class),
|
||||
new Data(ld, Runnable.class),
|
||||
new Data(ld, p.one.I.class),
|
||||
new Data(ld, p.one.I.class, p.two.A.class),
|
||||
new Data(ld, p.one.I.class, unnamedModuleClass),
|
||||
new Data(ld2, Runnable.class),
|
||||
new Data(ld2, p.one.I.class),
|
||||
new Data(ld2, p.one.I.class, p.two.A.class),
|
||||
new Data(ld2, p.one.I.class, unnamedModuleClass),
|
||||
new Data(m1.getClassLoader(), p.one.I.class),
|
||||
new Data(m2.getClassLoader(), p.two.A.class),
|
||||
new Data(m3.getClassLoader(), p.three.P.class),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -28,4 +28,8 @@ package jdk.test;
|
||||
*/
|
||||
interface NP {
|
||||
void test();
|
||||
|
||||
default int m() {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -96,7 +96,10 @@ public class ProxyTest {
|
||||
try {
|
||||
Constructor<?> cons = proxyClass.getConstructor(InvocationHandler.class);
|
||||
cons.newInstance(handler);
|
||||
throw new RuntimeException("Expected IllegalAccessException: " + proxyClass);
|
||||
// the exported package has the same name as the dynamic module
|
||||
if (!proxyClass.getPackageName().equals(m.getName())) {
|
||||
throw new RuntimeException("Expected IllegalAccessException: " + proxyClass);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
// expected
|
||||
} catch (NoSuchMethodException|InstantiationException|InvocationTargetException e) {
|
||||
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.test.internal;
|
||||
|
||||
/*
|
||||
* Non-public interface
|
||||
*/
|
||||
interface NP {
|
||||
default int m() {
|
||||
throw new UnsupportedOperationException("non-public interface: " + NP.class.getName());
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -31,4 +31,6 @@ public interface R {
|
||||
public void throwException() throws FooException;
|
||||
|
||||
public void setBarArray(Bar[][] array);
|
||||
|
||||
default int m() { return 10; }
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -227,7 +227,7 @@ public class ExceptionsTest extends KullaTesting {
|
||||
assertExceptionMatch(se,
|
||||
new ExceptionInfo(IllegalStateException.class, message,
|
||||
newStackTraceElement("", "lambda$do_it$$0", se.snippet(), 1),
|
||||
new StackTraceElement("com.sun.proxy.$Proxy0", "hashCode", null, -1),
|
||||
new StackTraceElement("jdk.proxy1.$Proxy0", "hashCode", null, -1),
|
||||
newStackTraceElement("", "", se.snippet(), 1)));
|
||||
}
|
||||
|
||||
|
@ -24,73 +24,106 @@ package org.openjdk.bench.java.lang.reflect.proxy;
|
||||
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Level;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Warmup(iterations = 5)
|
||||
@Measurement(iterations = 10)
|
||||
@Fork(value = 1)
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Fork(1)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@State(Scope.Thread)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@Measurement(iterations = 5, time = 2)
|
||||
@State(Scope.Benchmark)
|
||||
public class ProxyBench {
|
||||
Interfaze implClass, implProxy;
|
||||
PpInterfaze ppImplClass, ppImplProxy;
|
||||
int a, b, c;
|
||||
|
||||
/**
|
||||
* On Dell T7610:
|
||||
*
|
||||
* Benchmark w/ the old ProxyGenerator
|
||||
* Benchmark Mode Cnt Score Error Units
|
||||
* ProxyBench.getProxyClass1i avgt 10 20.472 +/- 0.209 ns/op
|
||||
* ProxyBench.getProxyClass4i avgt 10 57.353 +/- 0.461 ns/op
|
||||
* ProxyBench.newProxyInstance1i avgt 10 31.459 +/- 0.516 ns/op
|
||||
* ProxyBench.newProxyInstance4i avgt 10 66.580 +/- 0.983 ns/op
|
||||
*
|
||||
* Benchmark w/ the new ProxyGenerator using ASM
|
||||
* Benchmark Mode Cnt Score Error Units
|
||||
* ProxyBench.getProxyClass1i avgt 10 21.291 +/- 0.475 ns/op
|
||||
* ProxyBench.getProxyClass4i avgt 10 61.481 +/- 4.709 ns/op
|
||||
* ProxyBench.newProxyInstance1i avgt 10 30.177 +/- 0.761 ns/op
|
||||
* ProxyBench.newProxyInstance4i avgt 10 68.302 +/- 1.344 ns/op
|
||||
*/
|
||||
@Setup(Level.Trial)
|
||||
public void setup() {
|
||||
implClass = new Clazz();
|
||||
implProxy = (Interfaze) Proxy.newProxyInstance(
|
||||
Interfaze.class.getClassLoader(),
|
||||
new Class<?>[]{Interfaze.class},
|
||||
new IHandler()
|
||||
);
|
||||
ppImplClass = new PpClazz();
|
||||
ppImplProxy = (PpInterfaze) Proxy.newProxyInstance(
|
||||
PpInterfaze.class.getClassLoader(),
|
||||
new Class<?>[]{PpInterfaze.class},
|
||||
new IHandler()
|
||||
);
|
||||
|
||||
interface PkgPrivate1 {
|
||||
void m1();
|
||||
}
|
||||
|
||||
interface PkgPrivate2 {
|
||||
void m2();
|
||||
}
|
||||
|
||||
static final InvocationHandler handler = (proxy, method, args) -> null;
|
||||
|
||||
static final ClassLoader loader1 = null;
|
||||
static final Class<?>[] interfaces1 = {Runnable.class};
|
||||
|
||||
static final ClassLoader loader4 = PkgPrivate1.class.getClassLoader();
|
||||
static final Class<?>[] interfaces4 = {Runnable.class, Callable.class,
|
||||
PkgPrivate1.class, PkgPrivate2.class};
|
||||
|
||||
@Benchmark
|
||||
@SuppressWarnings("deprecation")
|
||||
public Class<?> getProxyClass1i() {
|
||||
return Proxy.getProxyClass(loader1, interfaces1);
|
||||
ThreadLocalRandom tlr = ThreadLocalRandom.current();
|
||||
a = tlr.nextInt();
|
||||
b = tlr.nextInt();
|
||||
c = tlr.nextInt();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@SuppressWarnings("deprecation")
|
||||
public Class<?> getProxyClass4i() {
|
||||
return Proxy.getProxyClass(loader4, interfaces4);
|
||||
public int implClass() {
|
||||
return implClass.sum(a, b, c);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public Object newProxyInstance1i() {
|
||||
return Proxy.newProxyInstance(loader1, interfaces1, handler);
|
||||
public int implProxy() {
|
||||
return implProxy.sum(a, b, c);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public Object newProxyInstance4i() {
|
||||
return Proxy.newProxyInstance(loader4, interfaces4, handler);
|
||||
public int ppImplClass() {
|
||||
return ppImplClass.sum(a, b, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int ppImplProxy() {
|
||||
return ppImplProxy.sum(a, b, c);
|
||||
}
|
||||
|
||||
public interface Interfaze {
|
||||
default int sum(int a, int b, int c) {
|
||||
return a + b + c;
|
||||
}
|
||||
}
|
||||
|
||||
static class Clazz implements Interfaze {
|
||||
@Override
|
||||
public int sum(int a, int b, int c) {
|
||||
return Interfaze.super.sum(a, b, c);
|
||||
}
|
||||
}
|
||||
|
||||
interface PpInterfaze {
|
||||
default int sum(int a, int b, int c) {
|
||||
return a + b + c;
|
||||
}
|
||||
}
|
||||
|
||||
static class PpClazz implements PpInterfaze {
|
||||
@Override
|
||||
public int sum(int a, int b, int c) {
|
||||
return PpInterfaze.super.sum(a, b, c);
|
||||
}
|
||||
}
|
||||
|
||||
static class IHandler implements InvocationHandler {
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
return InvocationHandler.invokeDefault(proxy, method, args);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user