Home 注解And反射

框架:类里面定义大量的注解,然后通过反射框架去读取注解生成相应信息

假设数据库中有db——student这样的表,我们会通过注解去定义一些数据库的语言,通过注解去执行增删改查

 

 

package com.yang.reflection;


import java.lang.annotation.*;
import java.lang.reflect.Field;

//练习反射操作注解
public class ReflectionOpsAnno {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {

        //通过反射获得注解
        Class c1 = Class.forName("com.yang.reflection.Student");
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        
        //获取注解的vlaue的值
        TableYang tableYang = (TableYang)c1.getAnnotation(TableYang.class);
        String value = tableYang.value();
        System.out.println(value);

        //获得类指定的注解(Declared才可以获取私有的)
        Field field = c1.getDeclaredField("name");
        FieldYang annotation = field.getAnnotation(FieldYang.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }
}

//对应表名
@TableYang("db_student")
class Student{

    //对应表字段名
    @FieldYang(columnName = "db_id",type = "int" ,length =10)
    private int id;
    @FieldYang(columnName = "db_age",type = "int" ,length =10)
    private int age;
    @FieldYang(columnName = "db_name",type = "varchar" ,length =3)
    private String name;

    public Student(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public Student() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//类名的注解
@Target(ElementType.TYPE)//作用域
@Retention(RetentionPolicy.RUNTIME)//获取范围
@interface TableYang{
    String value();
}

//属性的注解
@Target(ElementType.FIELD)//作用域
@Retention(RetentionPolicy.RUNTIME)//获取范围
@interface FieldYang{
    String columnName();
    String type();
    int length();
}

在我们的框架使用中大量运用到反射机制,我们以后的开发也必然会使用到反射,那么反射的性能分析就很有必要了,这篇文章将通过代码实例的方式为您对比反射和new之间调用方法及创建对象的性能,以及小小的优化(代码地址:Desktop\个人学习代码\Reflection)

 

package com.yang.reflection;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 测试New调用对象方法和反射调用对象方法的性能
 * test01:New
 * test02:Reflection
 * test03:关闭安全检查的反射
 * 注:如不关闭安全检查则无法修改私有属性
 */
public class ReflectionVsNew_Method {

    public static void test01(){
        long startTime = System.currentTimeMillis();
        User user = new User();
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("New方法所消耗的时间为:"+(endTime-startTime)+"ms");
    }

    /**
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     *
     * getDeclaredMethod(name,parameterTypes)
     * @param name 方法名
     * @param parameterTypes 方法所需要传入的参数
     *
     * method.invoke(object,args)
     * @param object 执行方法的对象
     * @param args 方法所需要传入的参数
     */
    public static void test02() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        long startTime = System.currentTimeMillis();
        User user = new User();
        Class c1= Class.forName("com.yang.reflection.User");
        Method getName = c1.getDeclaredMethod("getName", null);
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方法所消耗的时间为:"+(endTime-startTime)+"ms");
    }

    //反射方式调用,关闭检测
    public static void test03() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        long startTime = System.currentTimeMillis();
        User user = new User();
        Class c1= Class.forName("com.yang.reflection.User");
        Method getName = c1.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("关闭安全检查后,反射方法所消耗的时间为:"+(endTime-startTime)+"ms");
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        test01();
        System.out.println("===========================");
        test02();
        System.out.println("===========================");
        test03();
    }

}

总结:
反射比new大概慢了400倍
关闭安全检查后,大概慢了200倍

反射调用方法四部曲
    1.传入需要调用方法的所属对象
    2.获取该对象对应的Class对象
    3.通过Class对象得到需要调用的方法
    4.执行方法名.invoke()

 

通过Class创建实例

创建实例的五种方式链接:http://yangbili.co/class%e7%b1%bb%e7%9a%84%e8%8e%b7%e5%8f%96%e6%96%b9%e5%bc%8f%ef%bc%88%e9%87%8d%e7%82%b9%ef%bc%89/

老规矩,上代码:

package com.yang.reflection;


import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 创建对象的方式
 * 1.new
 * 2.默认无参构造器:Class.forname(),.newInstance()
 * 3.指定构造器
 */
//动态的创建对象,通过反射
public class GetNewObject {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //获得Class对象
        Class c1 =Class.forName("com.yang.reflection.User");

        //构造一个对象
//        User user1 = (User) c1.newInstance();//本质上调用了类的无参构造器
//        System.out.println(user);

        //通过指造器创建对象
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        User user2 = (User) constructor.newInstance("咩咩咩", 001, 19);
        System.out.println(user2);

        //通过反射调用普通方法
        User user3 = (User) c1.newInstance();
        //通过反射获取一个方法
        Method setName = c1.getDeclaredMethod("setName",String.class);
        /**
         * @param user3 调用方法的对象(这里体现了反字)
         * @param "大黑狗" 给方法传参
         * */
        setName.invoke(user3,"大黑狗");
        System.out.println(user3.getName());

        //通过反射操作属性
        User user4 = (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");

        //不能直接操作私有属性,需要关闭程序的安全检查,setAccessible(true)
        name.setAccessible(true);
        name.set(user4,"白羊");
        System.out.println(user4.getName());
    }
}

废话不多说,直接上代码

(代码地址:Desktop\个人学习代码\Reflection)

package com.yang.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

//获得类的信息
public class GetClassinfomation{
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {

        Class<?> c1 = Class.forName("com.yang.reflection.User");

//        User user = new User();
//        c1 = user.getClass();

        //获得类的名字
        System.out.println(c1.getName());//包名+类名
        System.out.println(c1.getSimpleName());//获得类名
        System.out.println("======================");

        //获得类的属性
        Field[] fields = c1.getFields();//只能找到public属性

        fields = c1.getDeclaredFields();//找到全部的属性
        for (Field field : fields) {
            System.out.println(field);
        }

        Field name = c1.getDeclaredField("name");//通过属性名获得属性
        System.out.println(name);

        System.out.println("======================");

        //获取类的方法
        Method[] methods = c1.getMethods();//获得本类及父类的全部public方法
        for (Method method : methods) {
            System.out.println("正常的:"+method);
        }

        Method[] declaredMethods = c1.getDeclaredMethods();//获得本类的全部方法,包括私有的方法
        for (Method declaredMethod : declaredMethods) {
            System.out.println("不正常的:"+declaredMethod);
        }

        System.out.println(c1.getMethod("getName", null));//获得指定方法
        /**
         * 为什么这里需要传入String.class?
         * JAVA的重载,如果方法名一样,但是无参数判断的话,它无法判断要得到哪个方法
         */
        System.out.println(c1.getMethod("setName", String.class));
        System.out.println("======================");

        //获得类构造器
        System.out.println("======================");
        Constructor[] constructors = c1.getConstructors();//获得public构造器
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        Constructor[] constructors2 = c1.getDeclaredConstructors();//获得本类的全部构造器
        for (Constructor constructor : constructors2) {
            System.out.println("#"+constructor);
        }

        Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class,int.class);
        System.out.println("指定构造器:"+declaredConstructor);
    }
}




总结:
一般来说,通过Class对象获得类的运行时的完整结构分三类:
  1. 获得public方法 getxxx()
  2.获得全部方法(包括private)getDeclaredxxx()
  3.按指定参数获得指定方法

用处:可通过Class对象获得构造器之后创建相应的实例

九大类型,直接上代码:
package com.yang.reflection;

import java.lang.annotation.ElementType;

//所有类型的class
public class AllClassTest04 {
    public static void main(String[] args) {

        //类的Class
        Class c1 = Object.class;
        //接口的Class
        Class c2 = Comparable.class;
        //一维数组的Class
        Class c3 = String[].class;
        //二维数组的Class
        Class c4 = int[][].class;
        //注解类型的Class
        Class c5 = Override.class;
        //枚举类型的Class
        Class c6 = ElementType.class;
        //Class类型的Class
        Class c7 = Integer.class;
        //void类型的Class
        Class c8 = void.class;
        //Class类型的Class
        Class c9 = Class.class;

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);

        System.out.println("===================");
        //长度不一样的数组,其Class也相同!!!同一个类型只有一份Class
        //只要元素类型与维度一样,就算同一个Class
        int[] a = new int[100];
        int[] b = new int[1000];
        System.out.println(a.getClass().hashCode());
        System.out.println(b.getClass().hashCode());
    }
}

其中getClass()与.class有如下区别:

两者最直接的区别就是,getClass()是一个类的实例所具备的方法,而class()方法是一个类的方法。
另外getClass()是在运行时才确定的,而class()方法是在编译时就确定了。

参考链接:http://yangbili.co/getclass%e5%92%8cclass%ef%bc%88%ef%bc%89%e7%9a%84%e5%8c%ba%e5%88%ab/

java中获取Class对象的五种方法总结:

1)运用getClass()
注:每个class 都有此函数
String str = “abc”;
Class c1 = str.getClass();

2)运用 Class.getSuperclass()
Button b = new Button();
Class c1 = b.getClass();
Class c2 = c1.getSuperclass();

3)Class.forName()
(最常被使用)
Class c1 = Class.forName(“java.lang.String”);
Class c2 = Class.forName(“java.awt.Button”);

4)运用 .class语法
Class c1 = String.class;

5)运用 primitivewrapper classes 的TYPE 语法
Class c1 = Boolean.TYPE;
Class c2 = Byte.TYPE;
Class c3 = Character.TYPE;
Class c4 = Short.TYPE;
Class c5 = Integer.TYPE;
Class c6 = Long.TYPE;
Class c7 = Float.TYPE;
Class c8 = Double.TYPE;
Class c9 = Void.TYPE;
————————————————

总结:

  1. 知道对象通过.getClass()
  2. 知道包名通过forname
  3. 知道类通过.class
  4. 知道子类通过.getSuperclass()

请说说你理解的类加载器

通过一个类的全限定名来获取描述此类的二进制字节流这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”

说说有哪几种类加载器,他们的职责分别是什么,他们之前存在什么样的约定。

类加载的结构如下:preview

  • BootstrapClassLoader:启动类类加载器,它用来加载<JAVA_HOME>/jre/lib路径,Xbootclasspath参数指定的路径以<JAVA_HOME>/jre/classes中的类。BootStrapClassLoader是由c++实现的。
  • ExtClassLoader:拓展类类加载器,它用来加载<JAVA_HOME>/jre/lib/ext路径以及java.ext.dirs系统变量指定的类路径下的类。
  • AppClassLoader:应用程序类类加载器,它主要加载应用程序ClassPath下的类(包含jar包中的类)。它是java应用程序默认的类加载器。
  • 用户自定义类加载器:用户根据自定义需求,自由的定制加载的逻辑,继承AppClassLoader,仅仅覆盖findClass()即将继续遵守双亲委派模型。
  • *ThreadContextClassLoader:线程上下文加载器,它不是一个新的类型,更像一个类加载器的角色,ThreadContextClassLoader可以是上述类加载器的任意一种,但往往是AppClassLoader,作用我们后面再说。

在虚拟机启动的时候会初始化BootstrapClassLoader,然后在Launcher类中去加载ExtClassLoader、AppClassLoader,并将AppClassLoader的parent设置为ExtClassLoader,并设置线程上下文类加载器。

Launcher是JRE中用于启动程序入口main()的类,让我们看下Launcher的代码

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            //加载扩展类类加载器
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            //加载应用程序类加载器,并设置parent为extClassLoader
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        //设置默认的线程上下文类加载器为AppClassLoader
        Thread.currentThread().setContextClassLoader(this.loader);
        //此处删除无关代码。。。
        }




上面画的几种类加载器是遵循双亲委派模型的,其实就是,当一个类加载器去加载类时先尝试让父类加载器去加载,如果父类加载器加载不了再尝试自身加载。这也是我们在自定义ClassLoader时java官方建议遵守的约定。





ExtClassLoader为什么没有设置parent?

让我们看看下面代码的输出结果

 public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader classLoader = Test.class.getClassLoader();
        System.out.println(classLoader);
        System.out.println(classLoader.getParent());
        System.out.println(classLoader.getParent().getParent());
    }

看看结果是啥

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@5a61f5df
null

因为BootstrapClassLoader是由c++实现的,所以并不存在一个Java的类,因此会打印出null,所以在ClassLoader中,null就代表了BootStrapClassLoader(有些片面)

那么双亲委派的好处是什么呢?

双亲委派模型能保证基础类仅加载一次不会让jvm中存在重名的类。比如String.class,每次加载都委托给父加载器,最终都是BootstrapClassLoader,都保证java核心类都是BootstrapClassLoader加载的,保证了java的安全与稳定性。

 

 

那自己怎么去实现一个ClassLoader呢?请举个实际的例子。

自己实现ClassLoader时只需要继承ClassLoader类,然后覆盖findClass(String name)方法即可完成一个带有双亲委派模型的类加载器。

我们看下ClassLoader#loadClass的代码

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 查看是否已经加载过该类,加载过的类会有缓存,是使用native方法实现的
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //父类不为空则先让父类加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    //父类是null就是BootstrapClassLoader,使用启动类类加载器加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 父类类加载器不能加载该类
                }

                //如果父类未加载该类
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    //让当前类加载器加载
                    c = findClass(name);
                }
            }
            return c;
        }
    }

经典的模板方法模式,子类只需要实现findClass,关心从哪里加载即可。

还有一点,parent需要自己设置哦,可以放在构造函数做这个事情。

 

 

为什么不继承AppClassLoader呢?

因为它和ExtClassLoader都是Launcher的静态类,都是包访问路径权限的。

 

 

为什么需要o.getClass().getMethod(“printVersion”).invoke(o);这样通过反射获取method调用,不能先强转成Test,然后test.printVersion()吗?

我:因为如果你这么写

Test test = (Test)o;
o.printVersion();

Test.class会隐性的被加载当前类的ClassLoader加载,当前Main方法默认的ClassLoader为AppClassLoader,而不是我们自定义的MyClassLoader。

那会发生什么呢?

会抛出ClassCastException,因为一个类,就算包路径完全一致,但是加载他们的ClassLoader不一样,那么这两个类也会被认为是两个不同的类。

 

 

转载自:请叫我程序猿大人 -知乎

ClassLoader 做什么的?

顾名思义,它是用来加载 Class 的。它负责将 Class 的字节码形式转换成内存形式的 Class 对象。字节码可以来自于磁盘文件 *.class,也可以是 jar 包里的 *.class,也可以来自远程服务器提供的字节流,字节码的本质就是一个字节数组 []byte,它有特定的复杂的内部格式。

有很多字节码加密技术就是依靠定制 ClassLoader 来实现的。先使用工具对字节码文件进行加密,运行时使用定制的 ClassLoader 先解密文件内容再加载这些解密后的字节码。

字节码加密:先对字节码文件加密,运行时先用定制ClassLoader解密再加载解密后的内容

每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。ClassLoader 就像一个容器,里面装了很多已经加载的 Class 对象。

 

class Class<T> {
  ...
  private final ClassLoader classLoader;
  ...
}

延迟加载(懒加载)

JVM 运行并不是一次性加载所需要的全部类的,它是按需加载,也就是延迟加载。程序在运行的过程中会逐渐遇到很多不认识的新类,这时候就会调用 ClassLoader 来加载这些类。加载完成后就会将 Class 对象存在 ClassLoader 里面,下次就不需要重新加载了。

比如你在调用某个类的静态方法时,首先这个类肯定是需要被加载的,但是并不会触及这个类的实例字段,那么实例字段的类别 Class 就可以暂时不必去加载,但是它可能会加载静态字段相关的类别,因为静态方法会访问静态字段。而实例字段的类别需要等到你实例化对象的时候才可能会加载。

结构图:

 

 

各司其职

JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的服务地址来加载。

JVM 中内置了三个重要的 ClassLoader,分别是 BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader。

BootstrapClassLoader 负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.xxx.* 都在里面,比如 java.util.*、java.io.*、java.nio.*、java.lang.* 等等。这个 ClassLoader 比较特殊,它是由 C 代码实现的,我们将它称之为「根加载器」。

ExtensionClassLoader 负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,有很多 jar 包。

AppClassLoader 才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。

那些位于网络上静态文件服务器提供的 jar 包和 class文件,jdk 内置了一个 URLClassLoader,用户只需要传递规范的网络路径给构造器,就可以使用 URLClassLoader 来加载远程类库了。URLClassLoader 不但可以加载远程类库,还可以加载本地路径的类库,取决于构造器中不同的地址形式。ExtensionClassLoader 和 AppClassLoader 都是 URLClassLoader 的子类,它们都是从本地文件系统里加载类库。

AppClassLoader 可以由 ClassLoader 类提供的静态方法 getSystemClassLoader() 得到,它就是我们所说的「系统类加载器」,我们用户平时编写的类代码通常都是由它加载的。当我们的 main 方法执行的时候,这第一个用户类的加载器就是 AppClassLoader。

总结:这三个最重要的加载器从不同的地方来加载字节码文件,不同的加载来源地层次不同,层次越高优先级越高!

 

ClassLoader 传递性

程序在运行过程中,遇到了一个未知的类,它会选择哪个 ClassLoader 来加载它呢?虚拟机的策略是使用调用者 Class 对象的 ClassLoader 来加载当前未知的类。何为调用者 Class 对象?就是在遇到这个未知的类时,虚拟机肯定正在运行一个方法调用(静态方法或者实例方法),这个方法挂在哪个类上面,那这个类就是调用者 Class 对象。前面我们提到每个 Class 对象里面都有一个 classLoader 属性记录了当前的类是由谁来加载的。

因为 ClassLoader 的传递性,所有延迟加载的类都会由初始调用 main 方法的这个 ClassLoader 全全负责,它就是 AppClassLoader。

 

双亲委派

前面我们提到 AppClassLoader 只负责加载 Classpath 下面的类库,如果遇到没有加载的系统类库怎么办,AppClassLoader 必须将系统类库的加载工作交给 BootstrapClassLoader 和 ExtensionClassLoader 来做,这就是我们常说的「双亲委派」。

ppClassLoader 在加载一个未知的类名时,它并不是立即去搜寻 Classpath,它会首先将这个类名称交给 ExtensionClassLoader 来加载,如果 ExtensionClassLoader 可以加载,那么 AppClassLoader 就不用麻烦了。否则它就会搜索 Classpath 。

而 ExtensionClassLoader 在加载一个未知的类名时,它也并不是立即搜寻 ext 路径,它会首先将类名称交给 BootstrapClassLoader 来加载,如果 BootstrapClassLoader 可以加载,那么 ExtensionClassLoader 也就不用麻烦了。否则它就会搜索 ext 路径下的 jar 包。

这三个 ClassLoader 之间形成了级联的父子关系,每个 ClassLoader 都很懒,尽量把工作交给父亲做,父亲干不了了自己才会干。每个 ClassLoader 对象内部都会有一个 parent 属性指向它的父加载器。

class ClassLoader {
  ...
  private final ClassLoader parent;
  ...
}

值得注意的是图中的 ExtensionClassLoader 的 parent 指针画了虚线,这是因为它的 parent 的值是 null,当 parent 字段是 null 时就表示它的父加载器是「根加载器」。如果某个 Class 对象的 classLoader 属性值是 null,那么就表示这个类也是「根加载器」加载的。

Class.forName

当我们在使用 jdbc 驱动时,经常会使用 Class.forName 方法来动态加载驱动类。

Class.forName("com.mysql.cj.jdbc.Driver");

其原理是 mysql 驱动的 Driver 类里有一个静态代码块,它会在 Driver 类被加载的时候执行。这个静态代码块会将 mysql 驱动实例注册到全局的 jdbc 驱动管理器里。

class Driver {
  static {
    try {
       java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
       throw new RuntimeException("Can't register driver!");
    }
  }
  ...
}

forName 方法同样也是使用调用者 Class 对象的 ClassLoader 来加载目标类。不过 forName 还提供了多参数版本,可以指定使用哪个 ClassLoader 来加载

Class<?> forName(String name, boolean initialize, ClassLoader cl)

通过这种形式的 forName 方法可以突破内置加载器的限制,通过使用自定类加载器允许我们自由加载其它任意来源的类库。根据 ClassLoader 的传递性,目标类库传递引用到的其它类库也将会使用自定义加载器加载。

自定义加载器

ClassLoader 里面有三个重要的方法 loadClass()、findClass() 和 defineClass()。

loadClass() 方法是加载目标类的入口,它首先会查找当前 ClassLoader 以及它的双亲里面是否已经加载了目标类,如果没有找到就会让双亲尝试加载,如果双亲都加载不了,就会调用 findClass() 让自定义加载器自己来加载目标类。ClassLoader 的 findClass() 方法是需要子类来覆盖的,不同的加载器将使用不同的逻辑来获取目标类的字节码。拿到这个字节码之后再调用 defineClass() 方法将字节码转换成 Class 对象。下面我使用伪代码表示一下基本过程

class ClassLoader {

  // 加载入口,定义了双亲委派规则
  Class loadClass(String name) {
    // 是否已经加载了
    Class t = this.findFromLoaded(name);
    if(t == null) {
      // 交给双亲
      t = this.parent.loadClass(name)
    }
    if(t == null) {
      // 双亲都不行,只能靠自己了
      t = this.findClass(name);
    }
    return t;
  }

  // 交给子类自己去实现
  Class findClass(String name) {
    throw ClassNotFoundException();
  }

  // 组装Class对象
  Class defineClass(byte[] code, String name) {
    return buildClassFromCode(code, name);
  }
}

class CustomClassLoader extends ClassLoader {

  Class findClass(String name) {
    // 寻找字节码
    byte[] code = findCodeFromSomewhere(name);
    // 组装Class对象
    return this.defineClass(code, name);
  }
}

自定义类加载器不易破坏双亲委派规则,不要轻易覆盖 loadClass 方法。否则可能会导致自定义加载器无法加载内置的核心类库。在使用自定义加载器时,要明确好它的父加载器是谁,将父加载器通过子类的构造器传入。如果父类加载器是 null,那就表示父加载器是「根加载器」。

// ClassLoader 构造器
protected ClassLoader(String name, ClassLoader parent);

双亲委派规则可能会变成三亲委派,四亲委派,取决于你使用的父加载器是谁,它会一直递归委派到根加载器。

Class.forName vs ClassLoader.loadClass

这两个方法都可以用来加载目标类,它们之间有一个小小的区别,那就是 Class.forName() 方法可以获取原生类型的 Class,而 ClassLoader.loadClass() 则会报错。

Class<?> x = Class.forName("[I");
System.out.println(x);

x = ClassLoader.getSystemClassLoader().loadClass("[I");
System.out.println(x);

---------------------
class [I

Exception in thread "main" java.lang.ClassNotFoundException: [I
...

钻石依赖

项目管理上有一个著名的概念叫着「钻石依赖」,是指软件依赖导致同一个软件包的两个版本需要共存而不能冲突。

 

Maven扁平化管理:我们平时使用的 maven 是这样解决钻石依赖的,它会从多个冲突的版本中选择一个来使用,如果不同的版本之间兼容性很糟糕,那么程序将无法正常编译运行。Maven 这种形式叫「扁平化」依赖管理。

使用 ClassLoader 可以解决钻石依赖问题。不同版本的软件包使用不同的 ClassLoader 来加载,位于不同 ClassLoader 中名称一样的类实际上是不同的类。下面让我们使用 URLClassLoader 来尝试一个简单的例子,它默认的父加载器是 AppClassLoader

$ cat ~/source/jcl/v1/Dep.java
public class Dep {
    public void print() {
        System.out.println("v1");
    }
}

$ cat ~/source/jcl/v2/Dep.java
public class Dep {
 public void print() {
  System.out.println("v1");
 }
}

$ cat ~/source/jcl/Test.java
public class Test {
    public static void main(String[] args) throws Exception {
        String v1dir = "file:///Users/qianwp/source/jcl/v1/";
        String v2dir = "file:///Users/qianwp/source/jcl/v2/";
        URLClassLoader v1 = new URLClassLoader(new URL[]{new URL(v1dir)});
        URLClassLoader v2 = new URLClassLoader(new URL[]{new URL(v2dir)});

        Class<?> depv1Class = v1.loadClass("Dep");
        Object depv1 = depv1Class.getConstructor().newInstance();
        depv1Class.getMethod("print").invoke(depv1);

        Class<?> depv2Class = v2.loadClass("Dep");
        Object depv2 = depv2Class.getConstructor().newInstance();
        depv2Class.getMethod("print").invoke(depv2);

        System.out.println(depv1Class.equals(depv2Class));
   }
}

在运行之前,我们需要对依赖的类库进行编译

$ cd ~/source/jcl/v1
$ javac Dep.java
$ cd ~/source/jcl/v2
$ javac Dep.java
$ cd ~/source/jcl
$ javac Test.java
$ java Test
v1
v2
false

在这个例子中如果两个 URLClassLoader 指向的路径是一样的,下面这个表达式还是 false,因为即使是同样的字节码用不同的 ClassLoader 加载出来的类都不能算同一个类

depv1Class.equals(depv2Class)

我们还可以让两个不同版本的 Dep 类实现同一个接口,这样可以避免使用反射的方式来调用 Dep 类里面的方法。

Class<?> depv1Class = v1.loadClass("Dep");
IPrint depv1 = (IPrint)depv1Class.getConstructor().newInstance();
depv1.print()

ClassLoader 固然可以解决依赖冲突问题,不过它也限制了不同软件包的操作界面必须使用反射或接口的方式进行动态调用。Maven 没有这种限制,它依赖于虚拟机的默认懒惰加载策略,运行过程中如果没有显示使用定制的 ClassLoader,那么从头到尾都是在使用 AppClassLoader,而不同版本的同名类必须使用不同的 ClassLoader 加载,所以 Maven 不能完美解决钻石依赖。
如果你想知道有没有开源的包管理工具可以解决钻石依赖的,我推荐你了解一下 sofa-ark,它是蚂蚁金服开源的轻量级类隔离框架。

 

分工与合作

这里我们重新理解一下 ClassLoader 的意义,它相当于类的命名空间,起到了类隔离的作用。位于同一个 ClassLoader 里面的类名是唯一的,不同的 ClassLoader 可以持有同名的类。ClassLoader 是类名称的容器,是类的沙箱。

不同的 ClassLoader 之间也会有合作,它们之间的合作是通过 parent 属性和双亲委派机制来完成的。parent 具有更高的加载优先级。除此之外,parent 还表达了一种共享关系,当多个子 ClassLoader 共享同一个 parent 时,那么这个 parent 里面包含的类可以认为是所有子 ClassLoader 共享的。这也是为什么 BootstrapClassLoader 被所有的类加载器视为祖先加载器,JVM 核心类库自然应该被共享。

 

getClass()作用:返回该对象的运行时类的java.lang.Class对象(API上的解释)

java中getClass( )和class()的联系和区别

转载出处:http://blog.csdn.net/xingjiarong/article/details/47908521
java有两个获得类名的方法getClass()和class(),这两个方法看似一样,实则不然。这两个方法涉及到了java中的反射。

反射

所谓反射,可以理解为在运行时期获取对象类型信息的操作。传统的编程方法要求程序员在编译阶段决定使用的类型,但是在反射的帮助下,编程人员可以动态获取这些信息,从而编写更加具有可移植性的代码。严格地说,反射并非编程语言的特性,因为在任何一种语言都可以实现反射机制,但是如果编程语言本身支持反射,那么反射的实现就会方便很多。

类型类
我们知道在Java中一切都是对象,我们一般所使用的对象都直接或间接继承自Object类。Object类中包含一个方法名叫getClass,利用这个方法就可以获得一个实例的类型类。类型类指的是代表一个类型的类,因为一切皆是对象,类型也不例外,在Java使用类型类来表示一个类型。所有的类型类都是Class类的实例。

联系
一般情况下,getclass()方法和class()方法是等价的,都可以获得一个类型名,例如下面的代码:

class A{
public void func(){

}
}

public class Test {

public static void main(String[] args) {
A a = new A();
System.out.println(a.getClass()+” “+A.class);
}

}

输出的结果为:class A class A

区别:

1.getClass()是一个类的实例所具备的方法,而class()方法是一个类的方法。
2.getClass()是在运行时才确定的,而class()方法是在编译时就确定了。

例如下面的程序:

class A{
public void func(){

}
}

class B extends A{

}
public class Test {

public static void main(String[] args) {
A a = new A();
B b = new B();
A ab = new B();
System.out.println(a.getClass()+” “+A.class);
System.out.println(b.getClass()+” “+B.class);
System.out.println(ab.getClass());
ab = a;
System.out.println(ab.getClass());
}

}

结果:
class A class A
class B class B
class B
class A

从最后的结果为class B来看,当出现继承和多态时,getclass和class方法的确实有所不同,因为是在运行时确定的,所以结果是ab在运行到这一时刻是所属的类型。

(代码:Reflection文件夹)

静态 VS 动态语言

区分标准:运行时能否改变其结构

 

什么叫反射:

Java反射机制提供的功能:

 

反射的优点和缺点:

优点:可以实现动态创建对象和编译,体现出很大的灵活性;

缺点:对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作;

性能:二者差了很多倍

 

反射相关的主要API

  • Java.lang.Class:代表一个类
  • Java.lang.reflect.Method:代表类的方法
  • Java.lang.reflect.Field:代表类的成员变量
  • Java.lang.reflect.Constructor:代表类的构造器

代码如下:

//什么叫反射
public class Test extends Object{
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//通过反射获取类的Class对象

Class<?> c1 = Class.forName(“com.yang.reflection.Test”);
System.out.println(c1);

Class<?> c2 = Class.forName(“com.yang.reflection.Test”);
Class<?> c3 = Class.forName(“com.yang.reflection.Test”);
Class<?> c4 = Class.forName(“com.yang.reflection.Test”);
//一个类在内存中只有一个Class对象
//一个类被加载后,类的整个结构都会被封装在Class对象中
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
}

}
结果:
class com.yang.reflection.Test
1956725890
1956725890
1956725890

分析:
相同对象的Class都是同一个

Class类详解:
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构的有关信息

  • Class本身也是一个类
  • Class对象只能由系统建立对象
  • 一个加载的类在JVM中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个Class实例所生成(通过getClass()得到)
  • 通过Class可以完整地得到一个类中的所有被加载的结构
  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得对应的Class对象