Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8253870

(proxy) Support for default methods

    XMLWordPrintable

    Details

    • Type: CSR
    • Status: Closed
    • Priority: P3
    • Resolution: Approved
    • Fix Version/s: 16
    • Component/s: core-libs
    • Labels:
      None
    • Subcomponent:
    • Compatibility Kind:
      behavioral
    • Compatibility Risk:
      medium
    • Compatibility Risk Description:
      Hide
      If all the proxy interfaces are public and in a package that is exported
      or open, the proxy class is no longer defined in an unnamed module
      and open for deep reflective access. Such proxy class will be in an
      unconditionally exported but non-open package. The module of
      the proxy class is a named module in such case. Program that
      assumes private members of such proxy class can be made accessible
      via `setAccessible(true)` will fail with `InaccessibleObjectException`.

      There might be programs that blindly call `setAccessible(true)` on
      all declared methods on a proxy class. Such programs will need
      updates to support modules if not have done so as there are already
      proxy classes defined in a dynamic modules in JDK 9.
      Show
      If all the proxy interfaces are public and in a package that is exported or open, the proxy class is no longer defined in an unnamed module and open for deep reflective access. Such proxy class will be in an unconditionally exported but non-open package. The module of the proxy class is a named module in such case. Program that assumes private members of such proxy class can be made accessible via `setAccessible(true)` will fail with `InaccessibleObjectException`. There might be programs that blindly call `setAccessible(true)` on all declared methods on a proxy class. Such programs will need updates to support modules if not have done so as there are already proxy classes defined in a dynamic modules in JDK 9.
    • Interface Kind:
      Java API
    • Scope:
      SE

      Description

      Summary

      Provide a new static InvocationHandler::invokeDefault API to allow a default method defined in a proxy interface directly or indirectly to be invoked.

      Problem

      Default methods were introduced in Java SE 8. It has been lacking of support in java.lang.reflect.Proxy API of invocation of the default methods defined in proxy interfaces. A proxy class overrides all instance methods defined in the specified proxy interfaces and the implementation forwards to InvocationHandler::invoke. It needs a mechanism equivalent to calling X.super::m to select a default method to be invoked.

      Solution

      Define a static InvocationHandler::invokeDefault(Object proxy, Method method, Object... args) method that allows an invocation handler to specify a default method and invoke it with the specified proxy instance and arguments.

      Specification

      1) New static InvocationHandler::invokeDefault method

           /**
           * 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 
      

      2) Proxy class spec diff:

      --- a/src/java.base/share/classes/java/lang/reflect/Proxy.java
      +++ b/src/java.base/share/classes/java/lang/reflect/Proxy.java
      @@ -144,6 +151,12 @@ 
       * InvocationHandler#invoke invoke} method as described in the
       * documentation for that method.
       *  * * <li>A proxy interface may define a default method or inherit
      + * <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

      A proxy class, used to be defined in an unnamed module i.e. in a exported and open package, is changed to be defined in an unconditionally exported but non-open package. The module is unspecified. Programs that assume it to be open unconditionally may be affected and cannot do deep reflection on such proxy classes.

      Defining a proxy class in an unconditionally exported but non-open package will strengthen encapsulation such that private members of the proxy class cannot be accessed via core reflection. This only applies to the case if all the proxy interfaces are public and in a package that is exported or open. This spec change is for defense-in-depth as the proxy support for default method requires to access the Lookup of a proxy class for the proxy machinery so that it can invoke the default method on behalf of the proxy class.

      Implementation-specific: one dynamic module is created for each class loader that defines proxies. The implementation changes the dynamic module to contain another package (same name as the module) that is unconditionally exported and is qualifiedly opened to java.base.

      There is no change to the package and module of the proxy class for the following cases:

      • if at least one proxy interface is non-public, then the proxy class is defined in the package and module of the non-public interfaces.
      • if at least one proxy is in a package that is non-exported and non-open, if all proxy interfaces are public, then the proxy class is defined in a non-exported, non-open package of a dynamic module.

        Attachments

          Issue Links

            Activity

              People

              Assignee:
              mchung Mandy Chung
              Reporter:
              mchung Mandy Chung
              Reviewed By:
              Alan Bateman
              Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved: