前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >解析反射 顶

解析反射 顶

作者头像
算法之名
发布2019-08-20 10:15:25
5700
发布2019-08-20 10:15:25
举报
文章被收录于专栏:算法之名算法之名

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

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

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

代码语言:javascript
复制
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";
    }
}
代码语言:javascript
复制
public class CompanyInstance {
    private static Company company = new Company();

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

运行结果

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

又可以写成如下

代码语言:javascript
复制
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()被重载为有一个参数和三个参数的,我们来看一下其源码

代码语言:javascript
复制
@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
代码语言:javascript
复制
@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()源码如下

代码语言:javascript
复制
@CallerSensitive
public static native Class<?> getCallerClass();

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

代码语言:javascript
复制
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()以后,是肯定会初始化的。

代码语言:javascript
复制
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是一个抽象类,我们来看一下它的部分源码。

代码语言:javascript
复制
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方法。

代码语言:javascript
复制
public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}
代码语言:javascript
复制
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方法。源码如下(加了注释)

代码语言:javascript
复制
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;
    }
}

加锁处理片段代码

代码语言:javascript
复制
private final ConcurrentHashMap<String, Object> parallelLockMap;
代码语言:javascript
复制
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;
}

查找最高权限加载器源码

代码语言:javascript
复制
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源码

代码语言:javascript
复制
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

resolveClass源码

代码语言:javascript
复制
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数据的能力。其中,应用类加载器的双亲为扩展类加载器,扩展类加载器的双亲为启动类加载器。当系统需要使用一个类时,在判断类是否已经被加载时,会先从当前底层类加载器进行判断。当系统需要加载一个类时,会从顶层类开始加载,依次向下尝试,直到成功。

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

代码语言:javascript
复制
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自实现

程序主入口

代码语言:javascript
复制
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")代码如下

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

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

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

代码语言:javascript
复制
/**
 * 获取类加载器
 * @return
 */
public static ClassLoader getClassLoader() {
    return Thread.currentThread().getContextClassLoader();
}

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

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

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

代码语言:javascript
复制
private static void doAddClass(Set<Class<?>> classSet,String className) {
    Class<?> cls = loadClass(className,false);
    classSet.add(cls);
}
代码语言:javascript
复制
/**
 * 加载类
 * @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();

代码语言:javascript
复制
/**
 * 获取所有Class实例跟类本身实例的映射关系
 */
public static void setBeanMap() {
    Set<Class<?>> beanClassSet = ClassHelper.getBeanClassSet();
    for (Class<?> beanClass:beanClassSet) {
        Object obj = ReflectionUtil.newInstance(beanClass);
        BEAN_MAP.put(beanClass,obj);
    }
}
代码语言:javascript
复制
/**
 * 定义Bean映射(用于存放Bean类与Bean实例的映射关系)
 */
private static final Map<Class<?>,Object> BEAN_MAP = new HashMap<Class<?>, Object>();
代码语言:javascript
复制
/**
 * 获取应用包名下所有Bean类
 * @return
 */
public static Set<Class<?>> getBeanClassSet() {
    Set<Class<?>> beanClassSet = new HashSet<Class<?>>();
    beanClassSet.addAll(getComponentClassSet());
    return beanClassSet;
}
代码语言:javascript
复制
/**
 * 获取应用包名下所有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;
}
代码语言:javascript
复制
/**
 * 创建实例
 * @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();

代码语言:javascript
复制
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();
                        }
                    }
                }
            }
        }
    }
}
代码语言:javascript
复制
/**
 * 获取Class实例和Bean类本身实例的映射
 * @return
 */
public static Map<Class<?>,Object> getBeanMap() {
    return BEAN_MAP;
}
代码语言:javascript
复制
/**
 * 设置成员变量值
 * @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个概念,抽象工厂,抽象零件,抽象产品。它是一个以抽象对抽象的处理,无需具体的实现类参与。

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档