Spring中的各种Utils(四):ClassUtils详解

原创文章,转载请注明出处

本节中主要介绍ClassUtils,这是关于类级别相关的工具类,虽然只是提供给Spring框架内部使用,但是很多方法还是有一定使用价值,并且理解这些方法的实现,也是有一定价值的。

首先ClassUtils是一个非常大的工具类,提供了很多缓存数据和初始化内容,在涉及到相关方法的时候,再介绍。

  • ClassLoader getDefaultClassLoader()

该方法用于获取默认的类加载器;方法功能很明确,平时也用的比较多,可能平时我们要获取类加载器,就一个class.getClassLoader()就完了,我们来看看spring是如何考虑的:

public static ClassLoader getDefaultClassLoader() {
    ClassLoader cl = null;
    try {
        //获取当前线程的context class loader
        cl = Thread.currentThread().getContextClassLoader();
    }catch (Throwable ex) {
    }
    if (cl == null) {
        // 如果没有context loader,使用当前类的类加载器;
        cl = ClassUtils.class.getClassLoader();
        if (cl == null) {
            // 如果当前类加载器无法获取,获得bootstrap ClassLoader
            try {
                cl = ClassLoader.getSystemClassLoader();
            } catch (Throwable ex) {
            }
        }
    }
    return cl;
}

代码很简单,按照获取当前线程上下文类加载器—>获取当前类类加载器—>获取系统启动类加载器的顺序来获取;

1,通过Thread.getContextClassLoader()方法获取到的是线程绑定的类加载器,这个classloader是父线程在创建子线程的时候,通过Thread.setContextClassLoader()方法设置进去,用于该线程加载类和资源的,如果没有调用这个方法,那么直接使用父线程的classLoader;如果这个方法返回null,代表该线程直接使用的系统class loader或者bootstrap class loader;

2,几种类加载器的层级关系简单说明:
Spring中的各种Utils(四):ClassUtils详解_第1张图片
级别从低到高,分别是:

  • bootstrap class loader:主要负责main方法启动的时候,加载JAVA_HOME/lib下的jar包;
  • extension class loader:主要负责加载JAVA_HOME/ext/lib下的jar包;
  • system class loader:主要负责加载classpath下的jar包或者类;
    这里只需要明白,system class loader是比bootstrap class loader离我们应用更近;而除了bootstrap class loader,其他几个class loader都继承了Classloader类;可以获取:
      @Test
      public void testClassLoader(){
          System.out.println(Thread.currentThread().getContextClassLoader());
          System.out.println(this.getClass().getClassLoader());
          System.out.println(ClassLoader.getSystemClassLoader());
          System.out.println(ClassLoader.getSystemClassLoader().getParent());
      }
    
    打印效果类似:
    sun.misc.Launcher$AppClassLoader@36c51089
    sun.misc.Launcher$AppClassLoader@36c51089
    sun.misc.Launcher$AppClassLoader@36c51089
    sun.misc.Launcher$ExtClassLoader@43c0ae76
    
    可以看到,在一个正常应用中,类获取到的classloader就是system class loader;而extention class loader是不一样的;

类加载器是一个很大的话题,后面还需要再额外介绍;对于我们正常的应用,我们一般得到的system class loader就可以了,除非一些特殊的应用服务器,还会有自定义的user class loader,这些就是后话了。


  • ClassLoader overrideThreadContextClassLoader(ClassLoader classLoaderToUse):这个方法较为简单,使用传入的classloader替换线程的classloader;使用场景,比如一个线程的classloader和spring的classloader不一致的时候,就可以使用这个方法替换;

  • Class forName(String name, ClassLoader classLoader) throws ClassNotFoundException, LinkageError:看名字就知道,是Class.forName的一个增强版本;通过指定的classloader加载对应的类;除了能正常加载普通的类型,还能加载简单类型,数组,或者内部类,测试代码:

@Test
public void testClassforName() throws Exception{
    System.out.println(ClassUtils.forName("int",ClassUtils.getDefaultClassLoader()));
    System.out.println(ClassUtils.forName("java.lang.String[]",ClassUtils.getDefaultClassLoader()));
    System.out.println(ClassUtils.forName("java.lang.Thread.State",ClassUtils.getDefaultClassLoader()));
}

打印结果:

int
class [Ljava.lang.String;
class java.lang.Thread$State

可以看到,简单类型int,数组String[]和内部类(内部类通过父类.子类的方式即可获取);

  • Class resolveClassName(String className, ClassLoader classLoader) throws IllegalArgumentException
    和forName方法相同,内部就是直接调用的forName方法,只是抛出的异常不一样而已;

Class resolvePrimitiveClassName(String name):获取简单类型的类;这个方法是用于处理forName方法中简单类型的调用方法;
System.out.println(ClassUtils.resolvePrimitiveClassName(“int”));
打印:int

简单看一下这个方法的实现原理:

public static Class resolvePrimitiveClassName(String name) {
    Class result = null;
    // 简单类型,最长值不要超过8,如果超过8,反而可以忽略了
    if (name != null && name.length() <= 8) {
        // Could be a primitive - likely.
        result = primitiveTypeNameMap.get(name);
    }
    return result;
}

可以看到,实现方式很简单,就是把名字扔给primitiveTypeNameMap去获取,那么primitiveTypeNameMap又是什么呢?继续看代码:

private static final Map> primitiveTypeNameMap =
      new HashMap>(32);

在ClassUtils类中,声明了这样一个静态的map;而他的初始化方法:

static {
    primitiveWrapperTypeMap.put(Boolean.class, boolean.class);
    primitiveWrapperTypeMap.put(Byte.class, byte.class);
    primitiveWrapperTypeMap.put(Character.class, char.class);
    primitiveWrapperTypeMap.put(Double.class, double.class);
    primitiveWrapperTypeMap.put(Float.class, float.class);
    primitiveWrapperTypeMap.put(Integer.class, int.class);
    primitiveWrapperTypeMap.put(Long.class, long.class);
    primitiveWrapperTypeMap.put(Short.class, short.class);
    Set> primitiveTypes = new HashSet>(32);
    primitiveTypes.addAll(primitiveWrapperTypeMap.values());
    primitiveTypes.addAll(Arrays.asList(new Class[] {
            boolean[].class, byte[].class, char[].class, double[].class,
            float[].class, int[].class, long[].class, short[].class}));
    primitiveTypes.add(void.class);
    for (Class primitiveType : primitiveTypes) {
        primitiveTypeNameMap.put(primitiveType.getName(), primitiveType);
    }

}

可以看到,其实就是把简单类型对应的名字和类型直接放到primitiveTypeNameMap中即可;


  • boolean isPresent(String className, ClassLoader classLoader):判断当前class loader中是否存在对应的类型了;

  • Class getUserClass(Class clazz):获取用户定义的本来的类型,大部分情况下就是类型本身,主要针对cglib做了额外的判断,获取cglib代理之后的父类;

  • boolean isCacheSafe(Class clazz, ClassLoader classLoader):判断类是否是可以缓存的,原理很简单,就是判断该类型是否在指定classloader或者其parent classloader中;

  • String getShortName(String className):这个方法也较为简单,得到一个全限定类名的简写,可以处理简单类型和内部类的情况:

System.out.println(ClassUtils.getShortName(getClass()));
System.out.println(ClassUtils.getShortName("java.lang.Thread$State"));

打印结果:

ClassUtilsTest
Thread.State

命名相关方法

  • String getShortName(Class clazz):得到一个类的简写;
  • String getShortNameAsProperty(Class clazz):得到一个类的简写,并按照属性的方式来命名;
    针对这两个方法,写一个测试就明白了:

    System.out.println(ClassUtils.getShortName(getClass()));
    System.out.println(ClassUtils.getShortNameAsProperty(getClass()));
    

    打印输出:

    ClassUtilsTest
    classUtilsTest
    可以看到,第二个打印首字母小写,其实并不是首字母小写,而是按照property的命名方式;这里我们可以看看spring是怎么实现的:

    public static String getShortNameAsProperty(Class clazz) {

      String shortName = ClassUtils.getShortName(clazz);
      int dotIndex = shortName.lastIndexOf(PACKAGE_SEPARATOR);
      shortName = (dotIndex != -1 ? shortName.substring(dotIndex + 1) : shortName);
      return Introspector.decapitalize(shortName);
    

    }
    前面都很简单,去掉包名,得到最后的类名,然后最后一句代码,使用了Introspector的decapitalize,就可以按照正确的属性命名的方式来格式化类名;这是一个比较有意思的内省的工具方法,大家可以了解一下;

  • String getClassFileName(Class clazz):获取类对应的类的字节码文件名称,比如:

//打印:ClassUtilsTest.class
System.out.println(ClassUtils.getClassFileName(getClass()));
  • String getPackageName(String fqClassName):根据类名得到报名;

  • String getPackageName(Class clazz):根据类得到包名,这个方法是交给getPackageName(String fqClassName)方法完成的;

  • String getQualifiedName(Class clazz):根据类得到类的权限定名;这个方法主要特点在于可以正确处理数组的类名;

//输出:java.lang.String[]
System.out.println(ClassUtils.getQualifiedName(String[].class));
//输出:[Ljava.lang.String;
System.out.println(String[].class.getName());
  • String getQualifiedNameForArray(Class clazz):专门用于处理数组的类名,上面getQualifiedName方法,判断如果是数组,就交给该方法处理;

  • String getQualifiedMethodName(Method method):获取方法的全名,包括类的权限定名.方法名;


String getDescriptiveType(Object value):获取一个对象的描述类型;一般来说,就是类名,能够正确处理数组,如果是JDK代理对象,能够正确输出其接口类型:

//java.lang.Class
System.out.println(ClassUtils.getDescriptiveType(getClass()));
//java.lang.String[]
System.out.println(ClassUtils.getDescriptiveType(new String[]{}));

//com.sun.proxy.$Proxy20 implementing cn.wolfcode.springboot.utilstest.IEmployeeService,
//cn.wolfcode.springboot.utilstest.IAddition,
//org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised
System.out.println(ClassUtils.getDescriptiveType(service));

重点在最后一行,在AopUtils中我们的IEmployeeService代理对象,可以清楚的看到实现了4个接口;具体这四个接口是什么意思,可以去看看Spring中的各种Utils(三)文章;


Constructor getConstructorIfAvailable(Class clazz, Class… paramTypes)
得到一个类的构造器方法;该方法其实就是使用了clazz.getConstructor(paramTypes)方法;只是对异常进行了拦截,在没有找到指定构造器的时候,返回null;

boolean hasConstructor(Class clazz, Class… paramTypes)
判定是否存在指定构造器;

Method getMethod(Class clazz, String methodName, Class… paramTypes)
得到类上指定的public方法;其实现也是一个标准的反射使用:

public static Method getMethod(Class clazz, String methodName, Class... paramTypes) {
    Assert.notNull(clazz, "Class must not be null");
    Assert.notNull(methodName, "Method name must not be null");
    if (paramTypes != null) {
        try {
            //如果指定了参数类型,直接使用getMethod方法;
            return clazz.getMethod(methodName, paramTypes);
        }catch (NoSuchMethodException ex) {
            throw new IllegalStateException("Expected method not found: " + ex);
        }
    }else {
        Set candidates = new HashSet(1);
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if (methodName.equals(method.getName())) {
                candidates.add(method);
            }
        }
        if (candidates.size() == 1) {
            return candidates.iterator().next();
        }else if (candidates.isEmpty()) {
            throw new IllegalStateException("Expected method not found: " + clazz + "." + methodName);
        }else {
            throw new IllegalStateException("No unique method found: " + clazz + "." + methodName);
        }
    }
}

为什么把这段代码贴出来,其实技术含量不高,但是我们在看这段代码的时候,会比较奇怪,为什么不能使用clazz.getMethod(methodName)来获取没有参数列表的方法呢?而要选择通过clazz.getMethods()先得到所有方法,然后再去对比方法名的方式来获取没有参数列表的方法呢?其实就是为了处理重载的异常而已;这些细节我觉得都展示出了在写一个框架或者只是一个工具类的时候的严谨思路;

boolean hasMethod(Class clazz, String methodName, Class… paramTypes)
判断类是否有指定的public方法;

Method getMethodIfAvailable(Class clazz, String methodName, Class… paramTypes)
该方法类似Method getMethod(Class clazz, String methodName, Class… paramTypes) 方法,只是当出现重载的情况,不会抛出异常,返回找到的第一个方法返回;

int getMethodCountForName(Class clazz, String methodName)
获取指定类中匹配该方法名称的方法个数,包括非public方法;

boolean hasAtLeastOneMethodWithName(Class clazz, String methodName)
判定指定的类及其父类中是否包含指定方法名称的方法,包括非public方法;我们又来看看这个方法的实现:

public static boolean hasAtLeastOneMethodWithName(Class clazz, String methodName) {
    Assert.notNull(clazz, "Class must not be null");
    Assert.notNull(methodName, "Method name must not be null");
    Method[] declaredMethods = clazz.getDeclaredMethods();
    //判断当前类是否包含指定方法;
    for (Method method : declaredMethods) {
        if (method.getName().equals(methodName)) {
            return true;
        }
    }
    Class[] ifcs = clazz.getInterfaces();
    //递归判断当前类实现的接口上是否有该方法;
    for (Class ifc : ifcs) {
        if (hasAtLeastOneMethodWithName(ifc, methodName)) {
            return true;
        }
    }
    //递归判定当前类的父类上是否有该方法;
    return (clazz.getSuperclass() != null 
        && hasAtLeastOneMethodWithName(clazz.getSuperclass(), methodName));
}

同样,代码非常简单,但是也看出了细节的完整性;


Method getMostSpecificMethod(Method method, Class targetClass)
获得最匹配的一个可以执行的方法;比如传入IEmployeeService.someLogic方法,在EmployeeServiceImpl类上找到匹配的EmployeeServiceImpl.someLogic方法,这个方法是一个可以执行的方法;

Method interfaceMethod=ClassUtils.getMethod(IEmployeeService.class, "someLogic");
System.out.println(ClassUtils.getQualifiedMethodName(interfaceMethod));
Method targetMethod=ClassUtils.getMostSpecificMethod(interfaceMethod, EmployeeServiceImpl.class);
System.out.println(ClassUtils.getQualifiedMethodName(targetMethod));

打印效果:

cn.wolfcode.springboot.utilstest.IEmployeeService.someLogic
cn.wolfcode.springboot.utilstest.EmployeeServiceImpl.someLogic

boolean isUserLevelMethod(Method method)
该方法用于判定一个方法是否是用户可用的方法,我们先简单来看看这个方法的实现:

public static boolean isUserLevelMethod(Method method) {
        Assert.notNull(method, "Method must not be null");
        return (method.isBridge() || (!method.isSynthetic() && !isGroovyObjectMethod(method)));
    }

方法实现很简单,就是三个判定,但这三个判定是什么意思?
1,method.isBridge:判定一个方法是否是桥接方法。什么是bridge方法?这个是JDK1.5引入了泛型之后的概念。为了让1.5泛型方法和1.5之前的字节码保持兼容,编译器会自动的生成桥接方法;
比如,我们有这样一个接口:

public interface IGenericInterface {

    T get(T param);
}

//加入一个实现:
public class ConcreateClass implements IGenericInterface {

    @Override
    public String get(String param) {
        return "hello";
    }
}

那实际上,在字节码级别,是怎么处理这个方法调用的呢?我们这里就不去展示具体的字节码,其实,JVM在ConcreateClass中生成了一个额外的方法:

public Object get(Object param){
    return this.get((String)param);
}

那么这个get方法就可以称为桥接(bridge method)方法;

2,method. isSynthetic方法:判定一个方法是否是虚构方法(synthetic method);什么是synthetic方法?由编译器创建的,非默认构造方法(我们知道,类都有默认构造方法,当然重载了默认构造方法的除外,编译器都会生成一个默认构造方法的实现)在源码中没有对应的方法实现的方法都是虚构方法。比如上面介绍的bridge方法就是一个典型的synthetic方法;

3,isGroovyObjectMethod:判定一个方法是否是Groovy的方法,因为Spring支持Groovy,而Groovy的类都实现了groovy.lang.GroovyObject类;关于Groovy可以自己去了解一下;


Method getStaticMethod(Class clazz, String methodName, Class… args)
针对给定的类和方法名字,参数类型列表,得到一个对应的static方法,这个方法很简单,但是我又忍不住给大家看一下这个方法的实现:

public static Method getStaticMethod(Class clazz, String methodName, Class... args) {
    try {
        Method method = clazz.getMethod(methodName, args);
        return Modifier.isStatic(method.getModifiers()) ? method : null;
    }
    catch (NoSuchMethodException ex) {
        return null;
    }
}

其中,Modifier是一个我们很少接触的类;这个类可以用来判定修饰符:
Spring中的各种Utils(四):ClassUtils详解_第2张图片
这些方法都只需要传入通过Method.getModifiers或者Class.getModifiers等获取的修饰符,即可进行各种判定;


boolean isPrimitiveWrapper(Class clazz)
判定一个类是否是简单类型的包装类;

boolean isPrimitiveOrWrapper(Class clazz)
判定一个类是否是简单类型,或者是简单类型的包装类型;

boolean isPrimitiveArray(Class clazz)
判定一个类是否是简单类型的数组;

boolean isPrimitiveWrapperArray(Class clazz)
判定一个类是否是简单类型的包装类型数组;

Class resolvePrimitiveIfNecessary(Class clazz)
如果传入的类型是一个简单类型,返回这个简单类型的包装类型;

String convertResourcePathToClassName(String resourcePath)
把文件路径形式变成包路径形式;

String convertClassNameToResourcePath(String className)
把类路径形式变成文件路径形式;

String classPackageAsResourcePath(Class clazz)
把指定类的包从包路径形式变为文件路径形式;

String addResourcePathToPackagePath(Class clazz, String resourceName)
在指定类的所属包下面,寻找一个资源文件,并返回该资源文件的文件路径;

//打印:cn/wolfcode/springboot/utilstest/someResource.xml
System.out.println(ClassUtils.addResourcePathToPackagePath(IEmployeeService.class,
    "someResource.xml"));

String classNamesToString(Collection> classes)
把一组指定的类类名连成一个字符串;主要用于日志相关处理;

Class[] toClassArray(Collection> collection)
将类集合变成类型数组;

boolean isVisible(Class clazz, ClassLoader classLoader)
判断一个类型是否在指定类加载器中可见。

Class[] getAllInterfaces(Object instance)
获取一个对象的所有接口;
Class[] getAllInterfacesForClass(Class clazz)
获取一个类型的所有接口;
Class[] getAllInterfacesForClass(Class clazz, ClassLoader classLoader)
获取指定类加载器下的指定类型的所有接口;
Set> getAllInterfacesAsSet(Object instance)
获取一个对象的所有接口,返回Set;
Set> getAllInterfacesForClassAsSet(Class clazz)
获取一个类的所有接口,返回Set;
Set> getAllInterfacesForClassAsSet(Class clazz, ClassLoader classLoader)
获取指定类加载器下的指定类型的所有接口;返回Set,这个方法才是上面5个方法的调用方法;简单来看一下代码实现:

public static Set> getAllInterfacesForClassAsSet(Class clazz, ClassLoader classLoader) {
    Assert.notNull(clazz, "Class must not be null");
    //如果本身就是接口,返回自己
    if (clazz.isInterface() && isVisible(clazz, classLoader)) {
        return Collections.>singleton(clazz);
    }
    Set> interfaces = new LinkedHashSet>();
    //使用递归获取类继承体系上所有接口;
    while (clazz != null) {
        Class[] ifcs = clazz.getInterfaces();
        for (Class ifc : ifcs) {
            interfaces.addAll(getAllInterfacesForClassAsSet(ifc, classLoader));
        }
        clazz = clazz.getSuperclass();
    }
    return interfaces;
}

Class createCompositeInterface(Class[] interfaces, ClassLoader classLoader) 
返回一个类型,该类型实现了所有给定的接口;这个方法可能乍一看很难理解,我们先来看看他的实现:

public static Class createCompositeInterface(Class[] interfaces, ClassLoader classLoader) {
    Assert.notEmpty(interfaces, "Interfaces must not be empty");
    Assert.notNull(classLoader, "ClassLoader must not be null");
    return Proxy.getProxyClass(classLoader, interfaces);
}

可以看到,实际上,该方法只是调用了Proxy.getProxyClass方法。那么getProxyClass方法又是干什么的呢?

Class getProxyClass(ClassLoader loader,
                                     Class... interfaces)

该方法返回一个代理类型,并且该类型实现了指定的接口。注意,如果这组接口在对应的classloader中已经被代理过,返回的是相同的代理类型。意思就是如果A,B,C三个接口已经通过Proxy$A实现了动态代理,以后通过getProxyClass(A,B,C)获取到的是相同的Proxy$A这个类型;
那这个方法有什么用呢?我们先不说Spring里面使用他来干什么,我们知道,JDK的动态代理就是使用Proxy来完成的,以前我们使用最多的方法就是Proxy.newInstance(ClassLoader classloader,Class[] interfaces,InvocationHandler ih)来创建一个动态代理对象,而所有的动态代理对象都继承了Proxy类:

public class Proxy implements java.io.Serializable {
    private static final Class[] constructorParams =
        { InvocationHandler.class };
    protected InvocationHandler h;
    private Proxy() {
    }

    /**
     * Constructs a new {@code Proxy} instance from a subclass
     * (typically, a dynamic proxy class) with the specified value
     * for its invocation handler.
     *
     * @param   h the invocation handler for this proxy instance
     */
    protected Proxy(InvocationHandler h) {
        doNewInstanceCheck();
        this.h = h;
    }
    //...
}

所以,其实我们还可以这样来写:

@Test
public void testProxys() throws Exception {
    Class clzz = ClassUtils.createCompositeInterface(
            new Class[] { IEmployeeService.class, IAddition.class },
            ClassUtils.getDefaultClassLoader());
    Object proxyObj = clzz
            .getConstructor(new Class[] { InvocationHandler.class })
            .newInstance(new MyInvocationHandler());
}

这段代码等价于:

Object proxyObj=Proxy.newInstance(
    ClassUtils.getDefaultClassLoader(),
    new Class[]{IEmployeeService.class,IAddition.class},
    new MyInvocationHandler());

在Spring中,该方法用于返回代理类的固定类型;比如,ProxyFactoryBean中,因为实现了FactoryBean接口,那么其getObjectType方法,就应该使用createCompositeInterface方法,固定返回代理的类型;


小结

ClassUtils中包含了大量的方法,虽然很多方法在平时的开发中使用不到,但是其中涉及到的类加载器,代理等内容,当去看每一个方法的实现的时候,又会收获很多;

Spring中的各种Utils(四):ClassUtils详解_第3张图片

你可能感兴趣的:(Spring生态)