集册 Java实例教程 查找与给定名称匹配并且具有兼容参数的可访问方法。

查找与给定名称匹配并且具有兼容参数的可访问方法。

欢马劈雪     最近更新时间:2020-01-02 10:19:05

383
查找与给定名称匹配并且具有兼容参数的可访问方法。

/*

 * Licensed to the Apache Software Foundation (ASF) under one or more

 * contributor license agreements.  See the NOTICE file distributed with

 * this work for additional information regarding copyright ownership.

 * The ASF licenses this file to You under the Apache License, Version 2.0

 * (the "License"); you may not use this file except in compliance with

 * the License.  You may obtain a copy of the License at

 *

 *      http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

import java.lang.ref.Reference;

import java.lang.ref.WeakReference;
/*
时   代     Java  公  众  号 - nowjava.com 提供
*/

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.lang.reflect.Modifier;

import java.util.Collections;

import java.util.Map;

import java.util.WeakHashMap;


public class Main{

    /**

     * Only log warning about accessibility work around once.

     * <p>

     * Note that this is broken when this class is deployed via a shared

     * classloader in a container, as the warning message will be emitted

     * only once, not once per webapp. However making the warning appear

     * once per webapp means having a map keyed by context classloader

     * which introduces nasty memory-leak problems. As this warning is

     * really optional we can ignore this problem; only one of the webapps

     * will get the warning in its logs but that should be good enough.

     */

    private static boolean loggedAccessibleWarning = false;

    /**

     * Indicates whether methods should be cached for improved performance.

     * <p>

     * Note that when this class is deployed via a shared classloader in

     * a container, this will affect all webapps. However making this

     * configurable per webapp would mean having a map keyed by context classloader

     * which may introduce memory-leak problems.

     */

    private static boolean CACHE_METHODS = true;
    /*
    来 自*
     N o  w  J a v a . c o m - 时  代  Java
    */

    /**

     * Stores a cache of MethodDescriptor -> Method in a WeakHashMap.

     * <p>

     * The keys into this map only ever exist as temporary variables within

     * methods of this class, and are never exposed to users of this class.

     * This means that the WeakHashMap is used only as a mechanism for 

     * limiting the size of the cache, ie a way to tell the garbage collector

     * that the contents of the cache can be completely garbage-collected 

     * whenever it needs the memory. Whether this is a good approach to

     * this problem is doubtful; something like the commons-collections

     * LRUMap may be more appropriate (though of course selecting an

     * appropriate size is an issue).

     * <p>

     * This static variable is safe even when this code is deployed via a

     * shared classloader because it is keyed via a MethodDescriptor object

     * which has a Class as one of its members and that member is used in

     * the MethodDescriptor.equals method. So two components that load the same

     * class via different classloaders will generate non-equal MethodDescriptor

     * objects and hence end up with different entries in the map.

     */

    private static final Map cache = Collections

            .synchronizedMap(new WeakHashMap());

    /**

     * <p>Find an accessible method that matches the given name and has compatible parameters.

     * Compatible parameters mean that every method parameter is assignable from 

     * the given parameters.

     * In other words, it finds a method with the given name 

     * that will take the parameters given.<p>

     *

     * <p>This method is slightly undeterminstic since it loops 

     * through methods names and return the first matching method.</p>

     *

     * <p>This method is used by 

     * {@link

     * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.

     *

     * <p>This method can match primitive parameter by passing in wrapper classes.

     * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>

     * parameter.

     *

     * @param clazz find method in this class

     * @param methodName find method with this name

     * @param parameterTypes find method with compatible parameters 

     * @return The accessible method

     */

    public static Method getMatchingAccessibleMethod(Class clazz,

            String methodName, Class[] parameterTypes) {

        // trace logging

        MethodDescriptor md = new MethodDescriptor(clazz, methodName,

                parameterTypes, false);


        // see if we can find the method directly

        // most of the time this works and it's much faster

        try {

            // Check the cache first

            Method method = getCachedMethod(md);

            if (method != null) {

                return method;

            }


            method = clazz.getMethod(methodName, parameterTypes);


            setMethodAccessible(method); // Default access superclass workaround


            cacheMethod(md, method);

            return method;


        } catch (NoSuchMethodException e) { /* SWALLOW */

        }


        // search through all methods 

        int paramSize = parameterTypes.length;

        Method bestMatch = null;

        Method[] methods = clazz.getMethods();

        float bestMatchCost = Float.MAX_VALUE;

        float myCost = Float.MAX_VALUE;

        for (int i = 0, size = methods.length; i < size; i++) {

            if (methods[i].getName().equals(methodName)) {

                // log some trace information


                // compare parameters

                Class[] methodsParams = methods[i].getParameterTypes();

                int methodParamSize = methodsParams.length;

                if (methodParamSize == paramSize) {

                    boolean match = true;

                    for (int n = 0; n < methodParamSize; n++) {

                        if (!isAssignmentCompatible(methodsParams[n],

                                parameterTypes[n])) {

                            match = false;

                            break;

                        }

                    }


                    if (match) {

                        // get accessible version of method

                        Method method = getAccessibleMethod(clazz,

                                methods[i]);

                        if (method != null) {

                            setMethodAccessible(method); // Default access superclass workaround

                            myCost = getTotalTransformationCost(

                                    parameterTypes,

                                    method.getParameterTypes());

                            if (myCost < bestMatchCost) {

                                bestMatch = method;

                                bestMatchCost = myCost;

                            }

                        }


                    }

                }

            }

        }

        if (bestMatch != null) {

            cacheMethod(md, bestMatch);

        }


        return bestMatch;

    }

    /**

     * Return the method from the cache, if present.

     *

     * @param md The method descriptor

     * @return The cached method

     */

    private static Method getCachedMethod(MethodDescriptor md) {

        if (CACHE_METHODS) {

            Reference methodRef = (Reference) cache.get(md);

            if (methodRef != null) {

                return (Method) methodRef.get();

            }

        }

        return null;

    }

    /**

     * Try to make the method accessible

     * @param method The source arguments

     */

    private static void setMethodAccessible(Method method) {

        try {

            //

            // XXX Default access superclass workaround

            //

            // When a public class has a default access superclass

            // with public methods, these methods are accessible.

            // Calling them from compiled code works fine.

            //

            // Unfortunately, using reflection to invoke these methods

            // seems to (wrongly) to prevent access even when the method

            // modifer is public.

            //

            // The following workaround solves the problem but will only

            // work from sufficiently privilages code. 

            //

            // Better workarounds would be greatfully accepted.

            //

            if (!method.isAccessible()) {

                method.setAccessible(true);

            }


        } catch (SecurityException se) {

            // log but continue just in case the method.invoke works anyway

            if (!loggedAccessibleWarning) {

                boolean vulnerableJVM = false;

                try {

                    String specVersion = System

                            .getProperty("java.specification.version");

                    if (specVersion.charAt(0) == '1'

                            && (specVersion.charAt(2) == '0'

                                    || specVersion.charAt(2) == '1'

                                    || specVersion.charAt(2) == '2' || specVersion

                                    .charAt(2) == '3')) {


                        vulnerableJVM = true;

                    }

                } catch (SecurityException e) {

                    // don't know - so display warning

                    vulnerableJVM = true;

                }

                loggedAccessibleWarning = true;

            }

        }

    }

    /**

     * Add a method to the cache.

     *

     * @param md The method descriptor

     * @param method The method to cache

     */

    private static void cacheMethod(MethodDescriptor md, Method method) {

        if (CACHE_METHODS) {

            if (method != null) {

                cache.put(md, new WeakReference(method));

            }

        }

    }

    /**

     * <p>Determine whether a type can be used as a parameter in a method invocation.

     * This method handles primitive conversions correctly.</p>

     *

     * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>,

     * a <code>Long</code> to a <code>long</code>,

     * a <code>Float</code> to a <code>float</code>,

     * a <code>Integer</code> to a <code>int</code>,

     * and a <code>Double</code> to a <code>double</code>.

     * Now logic widening matches are allowed.

     * For example, a <code>Long</code> will not match a <code>int</code>.

     *

     * @param parameterType the type of parameter accepted by the method

     * @param parameterization the type of parameter being tested 

     *

     * @return true if the assignement is compatible.

     */

    public static final boolean isAssignmentCompatible(Class parameterType,

            Class parameterization) {

        // try plain assignment

        if (parameterType.isAssignableFrom(parameterization)) {

            return true;

        }


        if (parameterType.isPrimitive()) {

            // this method does *not* do widening - you must specify exactly

            // is this the right behaviour?

            Class parameterWrapperClazz = getPrimitiveWrapper(parameterType);

            if (parameterWrapperClazz != null) {

                return parameterWrapperClazz.equals(parameterization);

            }

        }


        return false;

    }

    /**

     * <p>Return an accessible method (that is, one that can be invoked via

     * reflection) with given name and a single parameter.  If no such method

     * can be found, return <code>null</code>.

     * Basically, a convenience wrapper that constructs a <code>Class</code>

     * array for you.</p>

     *

     * @param clazz get method from this class

     * @param methodName get method with this name

     * @param parameterType taking this type of parameter

     * @return The accessible method

     */

    public static Method getAccessibleMethod(Class clazz,

            String methodName, Class parameterType) {


        Class[] parameterTypes = { parameterType };

        return getAccessibleMethod(clazz, methodName, parameterTypes);


    }

    /**

     * <p>Return an accessible method (that is, one that can be invoked via

     * reflection) with given name and parameters.  If no such method

     * can be found, return <code>null</code>.

     * This is just a convenient wrapper for

     * {@link #getAccessibleMethod(Method method)}.</p>

     *

     * @param clazz get method from this class

     * @param methodName get method with this name

     * @param parameterTypes with these parameters types

     * @return The accessible method

     */

    public static Method getAccessibleMethod(Class clazz,

            String methodName, Class[] parameterTypes) {


        try {

            MethodDescriptor md = new MethodDescriptor(clazz, methodName,

                    parameterTypes, true);

            // Check the cache first

            Method method = getCachedMethod(md);

            if (method != null) {

                return method;

            }


            method = getAccessibleMethod(clazz,

                    clazz.getMethod(methodName, parameterTypes));

            cacheMethod(md, method);

            return method;

        } catch (NoSuchMethodException e) {

            return (null);

        }


    }

    /**

     * <p>Return an accessible method (that is, one that can be invoked via

     * reflection) that implements the specified Method.  If no such method

     * can be found, return <code>null</code>.</p>

     *

     * @param method The method that we wish to call

     * @return The accessible method

     */

    public static Method getAccessibleMethod(Method method) {


        // Make sure we have a method to check

        if (method == null) {

            return (null);

        }


        return getAccessibleMethod(method.getDeclaringClass(), method);


    }

    /**

     * <p>Return an accessible method (that is, one that can be invoked via

     * reflection) that implements the specified Method.  If no such method

     * can be found, return <code>null</code>.</p>

     *

     * @param clazz The class of the object

     * @param method The method that we wish to call

     * @return The accessible method

     * @since 1.8.0

     */

    public static Method getAccessibleMethod(Class clazz, Method method) {


        // Make sure we have a method to check

        if (method == null) {

            return (null);

        }


        // If the requested method is not public we cannot call it

        if (!Modifier.isPublic(method.getModifiers())) {

            return (null);

        }


        boolean sameClass = true;

        if (clazz == null) {

            clazz = method.getDeclaringClass();

        } else {

            sameClass = clazz.equals(method.getDeclaringClass());

            if (!method.getDeclaringClass().isAssignableFrom(clazz)) {

                throw new IllegalArgumentException(clazz.getName()

                        + " is not assignable from "

                        + method.getDeclaringClass().getName());

            }

        }


        // If the class is public, we are done

        if (Modifier.isPublic(clazz.getModifiers())) {

            if (!sameClass

                    && !Modifier.isPublic(method.getDeclaringClass()

                            .getModifiers())) {

                setMethodAccessible(method); // Default access superclass workaround

            }

            return (method);

        }


        String methodName = method.getName();

        Class[] parameterTypes = method.getParameterTypes();


        // Check the implemented interfaces and subinterfaces

        method = getAccessibleMethodFromInterfaceNest(clazz, methodName,

                parameterTypes);


        // Check the superclass chain

        if (method == null) {

            method = getAccessibleMethodFromSuperclass(clazz, methodName,

                    parameterTypes);

        }


        return (method);


    }

    /**

     * Returns the sum of the object transformation cost for each class in the source

     * argument list.

     * @param srcArgs The source arguments

     * @param destArgs The destination arguments

     * @return The total transformation cost

     */

    private static float getTotalTransformationCost(Class[] srcArgs,

            Class[] destArgs) {


        float totalCost = 0.0f;

        for (int i = 0; i < srcArgs.length; i++) {

            Class srcClass, destClass;

            srcClass = srcArgs[i];

            destClass = destArgs[i];

            totalCost += getObjectTransformationCost(srcClass, destClass);

        }


        return totalCost;

    }

    /**

     * Gets the wrapper object class for the given primitive type class.

     * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code>

     * @param primitiveType the primitive type class for which a match is to be found

     * @return the wrapper type associated with the given primitive 

     * or null if no match is found

     */

    public static Class getPrimitiveWrapper(Class primitiveType) {

        // does anyone know a better strategy than comparing names?

        if (boolean.class.equals(primitiveType)) {

            return Boolean.class;

        } else if (float.class.equals(primitiveType)) {

            return Float.class;

        } else if (long.class.equals(primitiveType)) {

            return Long.class;

        } else if (int.class.equals(primitiveType)) {

            return Integer.class;

        } else if (short.class.equals(primitiveType)) {

            return Short.class;

        } else if (byte.class.equals(primitiveType)) {

            return Byte.class;

        } else if (double.class.equals(primitiveType)) {

            return Double.class;

        } else if (char.class.equals(primitiveType)) {

            return Character.class;

        } else {


            return null;

        }

    }

    /**

     * <p>Return an accessible method (that is, one that can be invoked via

     * reflection) that implements the specified method, by scanning through

     * all implemented interfaces and subinterfaces.  If no such method

     * can be found, return <code>null</code>.</p>

     *

     * <p> There isn't any good reason why this method must be private.

     * It is because there doesn't seem any reason why other classes should

     * call this rather than the higher level methods.</p>

     *

     * @param clazz Parent class for the interfaces to be checked

     * @param methodName Method name of the method we wish to call

     * @param parameterTypes The parameter type signatures

     */

    private static Method getAccessibleMethodFromInterfaceNest(Class clazz,

            String methodName, Class[] parameterTypes) {


        Method method = null;


        // Search up the superclass chain

        for (; clazz != null; clazz = clazz.getSuperclass()) {


            // Check the implemented interfaces of the parent class

            Class
展开阅读全文