反射、注解和动态代理

反射是指计算机程序在运行时访问、检测和修改它本身状态或行为的一种能力,是一种元编程语言特性,有很多语言都提供了对反射机制的支持,它使程序能够编写程序。Java的反射机制使得Java能够动态的获取类的信息和调用对象的方法。

一、Java反射机制及基本用法

在Java中,Class(类类型)是反射编程的起点,代表运行时类型信息(RTTI,Run-Time Type Identification)。java.lang.reflect包含了Java支持反射的主要组件,如Constructor、Method和Field等,分别表示类的构造器、方法和域,它们的关系如下图所示。

Java反射机制主要组件

Constructor和Method与Field的区别在于前者继承自抽象类Executable,是可以在运行时动态调用的,而Field仅仅具备可访问的特性,且默认为不可访问。下面了解下它们的基本用法:

Java反射类及核心方法

获取Class对象有三种方式,Class.forName适合于已知类的全路径名,典型应用如加载JDBC驱动。对同一个类,不同方式获得的Class对象是相同的。

// 1. 采用Class.forName获取类的Class对象 Class clazz0 = Class.forName("com.yhthu.java.ClassTest"); System.out.println("clazz0:" + clazz0); // 2. 采用.class方法获取类的Class对象 Class clazz1 = ClassTest.class; System.out.println("clazz1:" + clazz1); // 3. 采用getClass方法获取类的Class对象 ClassTest classTest = new ClassTest(); Class clazz2 = classTest.getClass(); System.out.println("clazz2:" + clazz2); // 4. 判断Class对象是否相同 System.out.println("Class对象是否相同:" + ((clazz0.equals(clazz1)) && (clazz1.equals(clazz2))));

注意:三种方式获取的Class对象相同的前提是使用了相同的类加载器,比如上述代码中默认采用应用程序类加载器(sun.misc.Launcher$AppClassLoader)。不同类加载器加载的同一个类,也会获取不同的Class对象:

// 自定义类加载器 ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null) { return super.loadClass(name); } byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; // 采用自定义类加载器加载 Class clazz3 = Class.forName("com.yhthu.java.ClassTest", true, myLoader); // clazz0与clazz3并不相同 System.out.println("Class对象是否相同:" + clazz0.equals(clazz3));

通过Class的getDeclaredXxxx和getXxx方法获取构造器、方法和域对象,两者的区别在于前者返回的是当前Class对象申明的构造器、方法和域,包含修饰符为private的;后者只返回修饰符为public的构造器、方法和域,但包含从基类中继承的。

// 返回申明为public的方法,包含从基类中继承的 for (Method method: String.class.getMethods()) { System.out.println(method.getName()); } // 返回当前类申明的所有方法,包含private的 for (Method method: String.class.getDeclaredMethods()) { System.out.println(method.getName()); }

通过Class的newInstance方法和Constructor的newInstance方法方法均可新建类型为Class的对象,通过Method的invoke方法可以在运行时动态调用该方法,通过Field的set方法可以在运行时动态改变域的值,但需要首先设置其为可访问(setAccessible)。

二、 注解

注解(Annontation)是Java5引入的一种代码辅助工具,它的核心作用是对类、方法、变量、参数和包进行标注,通过反射来访问这些标注信息,以此在运行时改变所注解对象的行为。Java中的注解由内置注解和元注解组成。内置注解主要包括:

@Override - 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。

@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。

@SuppressWarnings - 指示编译器去忽略注解中声明的警告。

@SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。

@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。

这里,我们重点关注元注解,元注解位于java.lang.annotation包中,主要用于自定义注解。元注解包括:

@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问,枚举类型分为别SOURCE、CLASS和RUNTIME;

@Documented - 标记这些注解是否包含在用户文档中。

@Target - 标记这个注解应该是哪种Java 成员,枚举类型包括TYPE、FIELD、METHOD、CONSTRUCTOR等;

@Inherited - 标记这个注解可以继承超类注解,即子类Class对象可使用getAnnotations()方法获取父类被@Inherited修饰的注解,这个注解只能用来申明类。

@Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspzfx.html