解析反射 顶

反射离不开Class.forName(),我们先从Class.forName说起。

上一篇我们说要得到一个类的实例有4个方法:new,反射,克隆,反序列化。

反射可以跟new一个对象有相同的效果。例如

public class Company {
    private String a;
    private String b;

    @Override
    public String toString() {
        return "Company{" +
                "a='" + a + '\'' +
                ", b='" + b + '\'' +
                '}';
    }

    public Company() {
        this.a = "A";
        this.b = "B";
    }
}
public class CompanyInstance {
    private static Company company = new Company();

    public static void main(String[] args) {
        System.out.println(company);
    }
}

运行结果

Company{a='A', b='B'}

又可以写成如下

public class CompanyInstance {

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        System.out.println(Class.forName("com.guanjian.Company").newInstance());
    }
}

运行结果

Company{a='A', b='B'}

虽然效果一样,但他们的过程并不一样。 首先,newInstance( )是一个方法,而new是一个关键字;其次,Class下的newInstance()的使用有局限,因为它生成对象只能调用无参的构造函数,而使用 new关键字生成对象没有这个限制。

newInstance()的时候是使用的上篇说的类装载机制的,它会走完全部过程。具体可以看 浅析类装载 ,而new一个实例的时候,走的流程不太一样,它会先在JVM内部先去寻找该类的Class实例,然后依照该Class实例的定义,依葫芦画瓢,把该类的实例给生成出来。但如果找不到该类的Class实例,则会走上篇说的装载流程。 其中JDK的Class实例一般是在jvm启动时用启动类加载器完成加载,用户的Class实例则是在用到的时候再加载。

Class.forName()被重载为有一个参数和三个参数的,我们来看一下其源码

@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
                               ClassLoader loader)
    throws ClassNotFoundException
{
    Class<?> caller = null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        // Reflective call to get caller class is only needed if a security manager
        // is present.  Avoid the overhead of making this call otherwise.
        caller = Reflection.getCallerClass();
        if (sun.misc.VM.isSystemDomainLoader(loader)) {
            ClassLoader ccl = ClassLoader.getClassLoader(caller);
            if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                sm.checkPermission(
                    SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
    }
    return forName0(name, initialize, loader, caller);
}

其中Reflection.getCallerClass()源码如下

@CallerSensitive
public static native Class<?> getCallerClass();

这是一个跟C语言交互的,用户无权限调用的方法,只能被 bootstrap class loader 和 extension class loader 调用的,这两个加载类后面再说。意思是返回调用者的Class实例。

private static native Class<?> forName0(String name, boolean initialize,
                                        ClassLoader loader,
                                        Class<?> caller)
    throws ClassNotFoundException;

它的第二个参数boolean initialize表示是否要初始化该类,单参Class.forName()默认true是要初始化的,三参的Class.forName()由你自己选择。一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化。当然如果你使用了三个参数的Class.forName(),并调用了newInstance()以后,是肯定会初始化的。

public class CompanyInstance {

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        System.out.println(Class.forName("com.guanjian.Company",false,Thread.currentThread().getContextClassLoader()).newInstance());
    }
}

运行结果

Company{a='A', b='B'}

现在我们重点要说的是它的ClassLoader,这个才是真正装载类的核心组件。所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制字节码数据流读入系统,然后交给JVM虚拟机进行连接、初始化等操作。ClassLoader是一个抽象类,我们来看一下它的部分源码。

public abstract class ClassLoader {

    private static native void registerNatives();
    static {
        registerNatives();
    }

    // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;

我们来看一下它部分对外公开的public方法。

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}
static ClassLoader getClassLoader(Class<?> caller) {
    // This can be null if the VM is requesting it
    if (caller == null) {
        return null;
    }
    // Circumvent security check since this is package-private
    return caller.getClassLoader0();
}

public loadClass方法的作用为给定一个类名,加载一个类,返回代表这个类的Class实例,如果找不到类,则返回ClassNotFoundException异常。它调用了protected loadClass方法。源码如下(加了注释)

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    //对这个名称产生一个锁对象,并进行加锁处理
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        //findLoadedClass的底层也是C语言交互实现的,应该是在JVM内存中查找该类的Class实例
        Class<?> c = findLoadedClass(name);
        //如果在JVM内存中找不到该类的Class实例
        if (c == null) {
            long t0 = System.nanoTime();
            try {//parent为该加载器的双亲,具体会在后面介绍,如果双亲对象不为null,使用双亲加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    //如果找不到双亲,启用最高权限的BootstrapClassLoader加载,BootstrapClassLoader在Java中没有对象,是用C语言实现的
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            //如果找不到最高权限的加载器
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                //直接抛出异常
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        //如果在JVM内存中找到该类的Class实例,当前加载器自己处理
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

加锁处理片段代码

private final ConcurrentHashMap<String, Object> parallelLockMap;
protected Object getClassLoadingLock(String className) {
    //加载器对象赋给一个锁对象
    Object lock = this;
    //如果该hashmap不为空
    if (parallelLockMap != null) {
        //产生一把新锁
        Object newLock = new Object();
        //如果该hashmap中存在className的key,则返回key的value,如果不存在,则将className和newLock作为key,value放入hashmap中,返回null
        lock = parallelLockMap.putIfAbsent(className, newLock);
        if (lock == null) {
            lock = newLock;
        }
    }
    return lock;
}

查找最高权限加载器源码

private Class<?> findBootstrapClassOrNull(String name)
{
    if (!checkName(name)) return null;

    return findBootstrapClass(name);
}

// return null if not found
private native Class<?> findBootstrapClass(String name);

findClass源码

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

resolveClass源码

protected final void resolveClass(Class<?> c) {
    resolveClass0(c);
}

private native void resolveClass0(Class<?> c);

ClassLoader的分类

在标准的Java程序中,Java虚拟机会创建3类ClassLoader为整个应用程序服务。它们分别是:BootStrap ClassLoader(启动类加载器),Extension ClassLoader(扩展类加载器),App ClassLoader(应用类加载器,也称为系统类加载器)。此外,每一个应用程序还可以拥有自定义的ClassLoader,扩展Java虚拟机获取Class数据的能力。其中,应用类加载器的双亲为扩展类加载器,扩展类加载器的双亲为启动类加载器。当系统需要使用一个类时,在判断类是否已经被加载时,会先从当前底层类加载器进行判断。当系统需要加载一个类时,会从顶层类开始加载,依次向下尝试,直到成功。

这里根类加载器即为启动类加载器。通过代码验证

public class PrintClassLoaderTree {
    public static void main(String[] args) {
        ClassLoader cl = PrintClassLoaderTree.class.getClassLoader();
        while (cl != null) {
            System.out.println(cl);
            cl = cl.getParent();
        }
        System.out.println(String.class.getClassLoader());
    }
}

运行结果

sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@4554617c null

由此可知,PrintClassLoaderTree用户类加载于AppClassLoader中,而AppClassLoader的双亲为ExtClassLoader.而从ExtClassLoader无法再取得启动类加载器,因为这是一个纯C实现。因此,任何加载在启动类加载器中的类时无法获得其ClassLoader实例的,比如String属于Java核心类,因此会被启动类加载器加载,所以最后一条打印为null.

因为后面还有一些双亲委托的东西,这个不是我的重点,就不重点写了。重点是结合我之前的一篇文章,做一个解析,见 @Compenent,@Autowired,@PostConstruct自实现

程序主入口

public class Test {
    public static void main(String[] args) {
        Manager.scanAndImp("com.guanjian.test");
        Test2 test2 = (Test2)Manager.getBean(Test2.class);
        test2.show();
    }
}

Manager.scanAndImp("com.guanjian.test")代码如下

public static void scanAndImp(String basePackage) {
    ClassHelper.setClassSet(basePackage);
    BeanHelper.setBeanMap();
    IocHelper.ioc();
}
/**
 * 定义类集合(用于存放所加载的类)
 * @param basePackage
 */
private static  Set<Class<?>> CLASS_SET;

/**
 * 扫描所有的包,获取类集合放入CLASS_SET
 * @param basePackage
 */
public static void setClassSet(String basePackage) {
    CLASS_SET = ClassUtil.getClassSet(basePackage);
}

很明显,第一步是扫描包,获取所有的Class实例,我们根据前面的介绍知道,要装载类,就必须要有一个类装载器,而这个类装载器就是

/**
 * 获取类加载器
 * @return
 */
public static ClassLoader getClassLoader() {
    return Thread.currentThread().getContextClassLoader();
}

因为要装载的类都是我们自己写的类,而不是系统类,所以此时在JVM内存中是肯定没有它们的Class实例的,而装载它们的肯定也就是应用类装载器。

/**
 * 获取制定包名下的所有类
 * @param packageName
 * @return
 */
public static Set<Class<?>> getClassSet(String packageName) {
    ...

代码就不详细分析了,只要知道这里使用了加载器装载了这些类,并产生了Class实例,但并未初始化。其中调用了方法

private static void doAddClass(Set<Class<?>> classSet,String className) {
    Class<?> cls = loadClass(className,false);
    classSet.add(cls);
}
/**
 * 加载类
 * @param className
 * @param isInitialized
 * @return
 */
public static Class<?> loadClass(String className,boolean isInitialized) {
    Class<?> cls;
    try {
        cls = Class.forName(className,isInitialized,getClassLoader());
    } catch (ClassNotFoundException e) {
        LOGGER.error("load class failure",e);
        throw new RuntimeException(e);
    }
    return cls;
}

即为我们之前说的Class.forName()的三参形式。这些所有的Class实例被放入了一个HashSet集合中,即为private static Set<Class<?>> CLASS_SET;

这样第一条语句ClassHelper.setClassSet(basePackage);就分析完了。

--------------------------------------------------------------------------------------------------

然后是第二条语句BeanHelper.setBeanMap();

/**
 * 获取所有Class实例跟类本身实例的映射关系
 */
public static void setBeanMap() {
    Set<Class<?>> beanClassSet = ClassHelper.getBeanClassSet();
    for (Class<?> beanClass:beanClassSet) {
        Object obj = ReflectionUtil.newInstance(beanClass);
        BEAN_MAP.put(beanClass,obj);
    }
}
/**
 * 定义Bean映射(用于存放Bean类与Bean实例的映射关系)
 */
private static final Map<Class<?>,Object> BEAN_MAP = new HashMap<Class<?>, Object>();
/**
 * 获取应用包名下所有Bean类
 * @return
 */
public static Set<Class<?>> getBeanClassSet() {
    Set<Class<?>> beanClassSet = new HashSet<Class<?>>();
    beanClassSet.addAll(getComponentClassSet());
    return beanClassSet;
}
/**
 * 获取应用包名下所有Comonent类
 * @return
 */
public static Set<Class<?>> getComponentClassSet() {
    Set<Class<?>> classSet = new HashSet<Class<?>>();
    for (Class<?> cls:CLASS_SET) {
        //这个Class实例是否带有@Component标签
        if (cls.isAnnotationPresent(Component.class)) {
            classSet.add(cls);
        }
    }
    return classSet;
}
/**
 * 创建实例
 * @param cls
 * @return
 */
public static Object newInstance(Class<?> cls) {
    Object instance;
    try {
        instance = cls.newInstance();
    } catch (Exception e) {
        LOGGER.error("new instance failure",e);
        throw new RuntimeException(e);
    }
    return instance;
}

这里是被@Component标签识别加载的称为bean,我们之前的确获取了所有的类,并且加载了,但并没有初始化。但有一些可能并没有打上@Component标签的就不能称为bean.我们需要对有@Component标签的进行初始化。把bean类跟带有@Component的类分离,是为了方便扩展,以后有其他的标签的可以方便修改。

BeanHelper.setBeanMap();的意思就是把所有的bean都给初始化了,并建立了一个Class实例跟bean类本身的实例的映射关系的HashMap.其中cls.isAnnotationPresent(Component.class)就是检测Class实例是否被我们自定义的标签@Component标记,这是一个比较重点的地方吧。

--------------------------------------------------------------------------------------------------------

然后是第三条语句IocHelper.ioc();

public static void ioc(){
    //获取所有的Bean类与Bean实例之间的映射关系
    Map<Class<?>,Object> beanMap = BeanHelper.getBeanMap();
    if (CollectionUtil.isNotEmpty(beanMap)) {
        //遍历Bean Map
        for (Map.Entry<Class<?>,Object> beanEntry:beanMap.entrySet()) {
            //从BeanMap中获取Bean类与Bean实例
            Class<?> beanClass = beanEntry.getKey();
            Object beanInstance = beanEntry.getValue();
            //获取Bean类定义的所有成员变量
            Field[] beanFields = beanClass.getDeclaredFields();
            if (ArrayUtil.isNotEmpty(beanFields)) {
                //遍历Bean Field
                for (Field beanField:beanFields) {
                    //判断当前Bean Field是否带有Autowired注解
                    if (beanField.isAnnotationPresent(Autowired.class)) {
                        //获取当前Bean Field的Class实例
                        Class<?> beanFieldClass = beanField.getType();
                        //通过该Class实例在HashMap中获取对应的Bean Field类本身的实例,此时并没有设置到Field中
                        //且注意beanFieldInstance是beanInstance某个属性的实例,他们不是同一个实例
                        Object beanFieldInstance = beanMap.get(beanFieldClass);
                        if (beanFieldInstance != null) {
                            //通过反射初始化BeanField的值,把在HashMap中找到的Bean类实例设置给beanInstance
                            //的beanField
                            ReflectionUtil.setField(beanInstance,beanField,beanFieldInstance);
                        }
                    }
                }
            }
            Method[] beanMethods = beanClass.getMethods();
            if (ArrayUtil.isNotEmpty(beanMethods)) {
                //遍历method
                for (Method method:beanMethods) {
                    //判断当前注解是否有PostConstruct注解
                    if (method.isAnnotationPresent(PostConstruct.class)) {
                        try {
                            //执行该方法
                            method.invoke(beanInstance,null);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}
/**
 * 获取Class实例和Bean类本身实例的映射
 * @return
 */
public static Map<Class<?>,Object> getBeanMap() {
    return BEAN_MAP;
}
/**
 * 设置成员变量值
 * @param obj
 * @param field
 * @param value
 */
public static void setField(Object obj, Field field,Object value) {
    try {
        //如果field在类中为private,则必须setAccessible(true)才可以在反射中正常访问
        field.setAccessible(true);
        //obj为要注入的Bean类的实例,value为该Bean类的field字段的要注入的值
        field.set(obj,value);
    } catch (IllegalAccessException e) {
        LOGGER.error("set field failure",e);
        throw new RuntimeException(e);
    }
}

这里主要是为了实现IOC依赖注入(@Autowired标签)以及@PostConstruct标签方法的自动运行。其中Class<?> beanFieldClass = beanField.getType();用来获取类的属性的Class实例,比较重要,再去HashMap中查找该Class实例对应的Bean类本身的实例,这里Class实例跟类本身的实例一定要分清楚。然后ReflectionUtil.setField(beanInstance,beanField,beanFieldInstance);把找到的实例设置给要设置的Bean类的field属性,完成初始化。同样method.invoke(beanInstance,null);也是调用beanInstance自身的方法。

总体思路就是在Class实例中查找各种属性,方法以及类自定义标签、属性自定义标签,方法自定义标签,再结合类本身的实例,通过Class实例的Field,method进行属性赋值,方法运行,这些又都必须在反射(Class实例)中调用类本身的实例。

具体谈一下反射在抽象工厂模式下的应用。

抽象工厂模式下有3个概念,抽象工厂,抽象零件,抽象产品。它是一个以抽象对抽象的处理,无需具体的实现类参与。

具体例子可以参考 设计模式整理

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券