反射和注解和类加载器和类的加载方式

更多内容请关注:

Java快速开发学习

锁清秋

反射和注解和类加载器和类的加载方式

反射机制概念 (运行状态中知道类所有的属性和方法)

在 Java 中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为 Java 语言的反射机制。

Java 反射 API

反射 API 用来生成 JVM 中的类、接口或则对象的信息。

  1. Class 类:反射的核心类,可以获取类的属性,方法等信息。
  2. Field 类: Java.lang.reflec 包中的类, 表示类的成员变量,可以用来获取和设置类之中的属性值。
  3. Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
  4. Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法。

反射使用步骤(获取 Class 对象、调用对象方法)

  1. 获取想要操作的类的 Class 对象,他是反射的核心,通过 Class 对象我们可以任意调用类的方法。
  2. 调用 Class 类中的方法,既就是反射的使用阶段。
  3. 使用反射 API 来操作这些信息。

获取字节码的三种方法

获取class对象的三种方法

  1. Class.forName(全类名)
  2. 类名.class
  3. 对象.getClass()

如:

//1.Class.forName(全类名)
Class<?> clazz1 = Class.forName("com.njau.construct.Person");
//2.类名.class
Class<Person> clazz2 = Person.class;
//3.对象.getClass()
Person person = new Person();
Class<? extends Person> clazz3 = person.getClass();

创建对象的两种方法

Class 对象的 newInstance()

  1. 使用 Class 对象的 newInstance()方法来创建该 Class 对象对应类的实例,但是这种方法要求该 Class 对象对应的类有默认的空构造器。

调用 Constructor 对象的 newInstance()
2. 先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance()
方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例。

//获取 Person 类的 Class 对象
Class clazz=Class.forName("reflection.Person");
//使用.newInstane 方法创建对象
Person p=(Person) clazz.newInstance();
//获取构造方法并创建对象
Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
//创建对象并设置属性
Person p1=(Person) c.newInstance("李四","男",20);

(其他操作可看JDK文档的Class类进行)

new关键字和newInstance()方法的区别

  • newInstance:弱类型,低效率,只能调用无参构造;
  • new:强类型,相对高效,能调用任何Public构造。

类加载器

虚拟机设计团队把加载动作放到 JVM 外部实现,以便让应用程序决定如何获取所需的类, JVM 提供了 3 种类加载器:

1.启动类加载器(Bootstrap ClassLoader)

  1. 负责加载 JAVA_HOME\lib 目录中的, 或通过 -Xbootclasspath 参数指定路径中的, 且被虚拟机认可(按文件名识别, 如 rt.jar) 的类。

2.扩展类加载器(Extension ClassLoader)

  1. 负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库。

3.应用程序类加载器(Application ClassLoader)

  1. 负责加载用户路径(classpath)上的类库。
    JVM 通过双亲委派模型进行类的加载, 当然我们也可以通过继承 java.lang.ClassLoader实现自定义的类加载器。

classloader

双亲委派

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class), 子类加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象。(避免了重复加在和官方的类发生冲突)

双亲委派

类的加载(三种方式)

Class的装载

Class的装载包括3个步骤:加载(loading),连接(link),初始化(initialize)

  1. 加载:通过 Classloader 加载.class文件字节码,生成 Class对象;
  2. 连接:
    1)、验证:确保被加载的类的正确性;
    2)、准备:为类的静态变量分配内存,并将其初始化为默认值(这里的‘默认值’是类变量类型的的初始值,而不是实质值);
    3)、解析:JVM 将常量池内的符号引用转换为直接引用;
  3. 初始化:执行类变量赋值和静态代码块

类加载分为动态加载和静态加载。动态加载是从外存储器中加载类,一般类加载机制分析的也是动态加载。而静态加载本质上是从内存中创建类的实例对象,此时类已经被加载到内存中。

类的加载方式 (****)

隐式加载:(静态加载)

  1. new关键字创建一个类的实例
    在由运行时刻用 new 方法载入
    例:

     Person person = new Person();

显式加载:(动态加载)

  1. 使用Class.forName()
    通过反射加载类型,并创建对象实例
    例:

     Class clazz = Class.forName("Person");
     Object person =clazz.newInstance();
  2. 使用某个ClassLoader实例的loadClass()方法
    通过该 ClassLoader 实例的 loadClass() 方法载入。应用程序可以通过继承 ClassLoader 实现自己的类装载器。
    例:

     Class clazz = classLoader.loadClass("Person");
     Object person =clazz.newInstance();

三种类加载方式的比较(new , Class.forName() , ClassLoader 实例的 loadClass()方法)

区别

  1. 加载器不同

1 和 2 使用的类加载器是相同的,都是当前类加载器(即:this.getClass.getClassLoader)。
3 由用户指定类加载器。如果需要在当前类路径以外寻找类,则只能采用第3种方式。即第3种方式加载的类与当前类分属不同的命名空间。

  1. 加载本质不同

1是静态加载,2、3是动态加载

  1. 得到的 class 对象的装载执行程度不同

Class.forName得到的 Class对象是已经初始化了的,而Classloader.loaderclass得到的Class对象是还没有连接的。即两者都是 动态加载,但是加载的程度是不相同的,加载的速度也是不同,应为loaderclass()方法加载的Class对象还没有连接,而通过forName()方法加载的Class 对象以及初始化完成,所以两者的速的上比较,显然前者的加载速度比后者快很多,而且前者的延迟加载的程度要远大于后者,这也是 Spring IOC 为了加快初始化速度大量而大量采用 Classloader.loaderclass()加载类的原因。

注解

例:

/**
 * @author 张文军
 * @Description:
 * @Company:南京农业大学工学院
 * @version:1.0
 * @date 2019/8/3113:57
 */
@Target({ElementType.TYPE})//注解可以用于什么位置
@Retention(RetentionPolicy.RUNTIME)//注解保留的时期(源码,Class,RunTime)
public @interface Pro {//注解的形式//public @interface 注解名称
/**
*
*以下为注解的属性,即接口的方法
*/
    /**
     * 类的全路径
     * @return
     */
    String clazzPath();

    /**
     * 方法名
     * @return
     */
    String methName();
}

解析注解

//获取当前类的Class(字节码文件对象)、
Class<TestPro> testProClass = TestPro.class;
/**
获取注解的对象(注解在哪里就获取哪里的注解对象,这里是在类上,
所以获取该类的注解对象)用相对应的对象获取指定的注解对象,如当前是Pro.class
注解
*/
Pro proClassAnnotation = testProClass.getAnnotation(Pro.class);
//获取注解的属性
String clazzPath = proClassAnnotation.clazzPath();
String methName = proClassAnnotation.methName();
//用反射的方法创建对象和运行方法(注解的使用)
Class<?> aClass = Class.forName(clazzPath);
Object instance = aClass.newInstance();
Method method = aClass.getMethod(methName);
method.invoke(instance);

文章作者: 张文军
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 张文军 !
评论