1、Java反射机制概述
- Reflection(反射)是被视为
动态语言
的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性以及方法 - 加载完类之后,在堆内存的方法区中就会产生一个Class类型的对象clazz(一个类只有一个Class对象),该对象包含了完整的类的结构信息,我们可以通过clazz对象看到类的结构
1.1、动态语言vs静态语言
- 动态语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构
- 主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
- 与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
- Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
- 框架 = 反射 + 注解 + 设计模式
1.2、反射机制提供的功能
- 在运行时 判断任意对象所属的类
- 在运行时 构造任意类的对象
- 在运行时 操作任意类所具有的成员变量和方法
- 在运行时 调用任意对象的成员变量和方法
- 在运行时 获取泛型信息
- 在运行时 处理注解
- 生成动态代理
1.3、相关API
- java.lang.Class:反射的源头
- java.lang.reflect.Method
- java.lang.reflect.Proxy
- java.lang.reflect.Field
- java.lang.reflect.Constructor
1.4、反射实现的操作
- 实体类Person
public class Person {
private int pid;
public String pname;
int age;
public Person() {
System.out.println("无参构造器被调用...");
}
public Person(int pid, String pname, int age) {
this.pid = pid;
this.pname = pname;
this.age = age;
}
private Person(String pname) {
this.pname = pname;
}
private Person(int pid, String pname) {
System.out.println("正在调用私有的构造方法...");
this.pid = pid;
this.pname = pname;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"pid=" + pid +
", pname='" + pname + '\'' +
", age=" + age +
'}';
}
private String showMsg(String msg){
System.out.println("测试 使用反射调用对象的私有方法,+"+ msg+"!!");
return "调用私有的showMsg()方法";
}
public void helloWays(){
System.out.println("调用public修饰的方法...");
}
}
- 通过反射,创建Person类对象 以及 调用对象指定的属性和方法
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectionTest {
// 在Person类外部,不可以通过Person类的对象调用其内部私有结构
// 通过反射,调用对象的非私有属性和方法
@Test
public void test1() throws Exception {
Class clazz = Person.class;
// 1.通过反射,创建Person类对象
Constructor constructor = clazz.getDeclaredConstructor(int.class, String.class,int.class);
Person person = (Person) constructor.newInstance(1213, "张三",26);
System.out.println(person);
// 如果调用私有的构造器来创建对象,会出现如下错误:
// java.lang.NoSuchMethodException: com.xyl.javase.reflection.Person.<init>(int, java.lang.String)
// Constructor constructor = clazz.getConstructor(int.class, String.class);
// constructor.setAccessible(true);
// 2.通过反射,调用对象指定的属性和方法
Field pname = clazz.getDeclaredField("pname");
// 调用属性 并设置值
pname.set(person,"瑞.达利欧");
System.out.println(person);
// 调用方法
Method helloWays = clazz.getDeclaredMethod("helloWays");
helloWays.invoke(person); // person.helloWays();
/**
* 疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?
* 建议:直接new的方式。
* 什么时候会使用:反射的方式。 反射的特征:动态性
* 疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
* 面向对象的封装性,主要体现在将属性和方法设置为私有的,使其在其他类中调用时无法访问该类中内部的 属性和私有方法,要想操作其私有属性或调用私有方法,则应该使用该类内部的public方法进行调用
* 反射机制是动态的,可以调用私有方法,但是不建议
*/
}
}
2、理解Class类并获取Class实例
2.1、Class类的理解
- 关于java.lang.Class类的理解,类的加载过程:
- 程序经过Javac.exe命令后,会生成字节码文件(.class结尾),使用java.exe命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中,该过程就称为类的加载;加载到内存中的类,称为运行时类(Class类的一个实例)
- 换句话说,Class实例对应着加载到内存中的一个运行时类
- 创建Class对应运行时类的对象的通用方法:
Object obj = clazz.newInstance(); // 创建对应的运行时类的对象
// 1.必须有空参的构造器
// 2.权限修饰符的权限要求 要足够,通常设置为public
Class类的常用方法:
2.2、获取Class实例(四种方式)
@Test
public void test3() throws ClassNotFoundException {
// Class实例对应着一个运行时类,该实例加载到内存后,会缓存一段时间
// 即加载到内存中的运行时类,会缓存一定的时间,在此时间之内,我们可以通过不同的方式来获取运行时类
// 方式1: 调用运行时类的class属性,如: XXX.class
Class<Person> clazz1 = Person.class;
System.out.println(clazz1);
// 方式2: 通过运行时类的对象来调用 getClass()方法
Person person = new Person();
Class<? extends Person> clazz2 = person.getClass();
System.out.println(clazz2);
// 方式3: 调用Class的静态方法 Class.forName(String classPath),该方法也是反射动态性的体现
// classPath: 类的全路径
Class<?> clazz3 = Class.forName("com.xyl.javase.reflection.Person");
System.out.println(clazz3);
System.out.println(clazz1 == clazz2); // ==: 比较两个对象的内存地址是否相同
System.out.println(clazz1 == clazz3);
// 方式4: 使用类的加载器 loader.loadClass(classPath) (方式4了解即可)
ClassLoader loader = ReflectionTest.class.getClassLoader();
Class<?> clazz4 = loader.loadClass("com.xyl.javase.reflection.Person");
System.out.println(clazz4);
System.out.println(clazz1 == clazz4);
}
2.3、Class实例对应的结构
- 哪些类型可以有Class对象?
- (1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- (2)interface:接口
- (3)[]:数组
- (4)enum:枚举
- (5)annotation:注解@interface
- (6)primitivetype:基本数据类型
- (7)void
3、类的加载与ClassLoader
3.1、类的加载过程(广义)
- 当程序主动使用某个类时,如果该类还未被就加载到内存中,则系统会通过类的加载(Load)、类的链接(Link)以及类的初始化(Initialize)来完成整个类的初始化
- 详细说明:
3.2、理解ClassLoader
- Java类编译、运行的执行流程
- 类加载器的作用是把类装载到内存中,即将class文件字节码的内容加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,然后在堆中生成一个代表该类的java.lang.Class对象,作为方法区中类数据的入口。
- 标准的JavaSE类加载器可以根据要求查找类,但一旦某个类被装载到类加载器中,它将维持加载(缓存)一段时间,另外,JVM垃圾回收机制也可以回收这些Class对象
- JVM 规范定义了如下类型的类的加载器:
- 代码说明:
import org.junit.Test;
public class ClassLoaderTest {
@Test
public void test1(){
// 对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader1 = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader1); // sun.misc.Launcher$AppClassLoader@18b4aac2
// 调用系统类加载器的getParent()方法,无法获取引导类加载器
// 引导类加载器主要负责加载Java的核心类库,无法加载自定义类
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2); // sun.misc.Launcher$ExtClassLoader@63961c42
ClassLoader classLoader3 = classLoader2.getParent();
System.out.println(classLoader3); // null
ClassLoader classLoader4 = String.class.getClassLoader();
System.out.println(classLoader4); // null
}
}
3.3、ClassLoader加载配置文件
- 需要的配置文件所在的目录结构如下,
userInfo1.properties
在模块的src目录下,userInfo2.properties
在模块的目录下:
- 其配置文件的内容如下:
uname=瑞.达利欧22
password=abc123
- 代码示例:
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class ClassLoaderTest {
// Properties: 可用来读取配置文件
// 使用IO流或反射读取配置文件
@Test
public void test2() throws IOException {
Properties prop = new Properties();
// 读取配置文件的方式1: IO流
// 文件默认是在当前module目录下
// FileInputStream fileInputStream = new FileInputStream("userInfo2.properties");
FileInputStream fileInputStream = new FileInputStream("src\\userInfo1.properties");
// prop.load(fileInputStream);
// 读取配置文件的方式2: 使用ClassLoader
// 配置文件的地址默认是在当前module的src目录下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("userInfo1.properties");
prop.load(inputStream);
String uname = prop.getProperty("uname");
String password = prop.getProperty("password");
System.out.println("uname = " + uname + ", password = " + password);
}
}
4、创建运行时类的对象
- 反射创建运行时类的对象
import org.junit.Test;
import java.util.Random;
public class NewInstanceTest {
@Test
public void test1() throws InstantiationException, IllegalAccessException {
Class<Person> clazz = Person.class;
// newInstance(): 调用该方法,创建运行时类的对象,内部会调用运行时类的无参构造器
/*
* 要想该方法可以正常地创建运行时类的对象,要求:
* 1.运行时类必须提供空参的构造器
* 2.空参构造器的访问权限符合访问要求,空参构造器通常设置为public
*
* 在Java Bean中要求提供一个public的空参构造器,原因:
* 1.便于通过反射,创建运行时类的对象
* 2.便于子类继承该运行类时,默认调用super()时,保证父类有此构造器
* */
Person person = clazz.newInstance();
System.out.println(person);
}
}
- 反射动态性的体现:需要在运行时才会知道到底创建的是哪个对象
@Test
public void test2(){
for (int i = 1; i <= 20; i++) {
// 根据生成的随机数,去创建对应的实例对象
int num = new Random().nextInt(3); // 生成[0,3)区间的随机数
String classPath = "";
switch (num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.xyl.javase.reflection.Person";
break;
}
try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public Object getInstance(String classPath) throws Exception {
Class<?> clazz = Class.forName(classPath);
return clazz.newInstance();
}
5、获取运行时类的完整结构
5.0、前提数据准备
- 创建自定义接口
public interface MyInterface {
public void info();
}
- 创建自定义注解
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "自定义注解";
}
- 创建父类Creature
import java.io.Serializable;
public class Creature<T> implements Serializable {
private String cname;
public int age;
double height;
double weight;
private void breath(){
System.out.println("调用private修饰的私有方法breath(),呼吸中...");
}
public void eat(){
System.out.println("调用public修饰的eat(),吃东西中...");
}
void play(){
System.out.println("调用play()方法,玩耍中...");
}
}
- 创建子类Employee
import com.xyl.javase.reflection.Person;
@MyAnnotation(value = "Employee雇员类")
public class Employee extends Creature<String> implements Comparable<String>,MyInterface{
private String name;
int age;
protected String email;
public String address;
public Employee(){ }
@MyAnnotation(value = "私有的有参构造器")
private Employee(String name){
this.name = name;
}
Employee(String name,int age){
this.name = name;
this.age = age;
}
@MyAnnotation(value = "private修饰的showInfo()方法")
private String showInfo(String nation){
System.out.println("我的国籍:" + nation);
return nation;
}
public String display(String interest){
return interest;
}
@Override
public void info() {
System.out.println("调用info()方法");
}
@Override
public int compareTo(String o) {
return 0;
}
}
5.1、获取类的属性(权限修饰符、数据类型、变量名)
@Test
public void test1(){
Class<Employee> clazz = Employee.class;
// 获取属性结构
// getFields(): 获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
System.out.println("获取公有的属性:");
for (Field field : fields) {
System.out.println(field);
}
// getDeclaredFields(): 获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
System.out.println("获取全部的私有属性------");
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
}
@Test
public void test2(){
Class<Employee> clazz = Employee.class;
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
// 1.获取权限修饰符
int modifiers = field.getModifiers();
// 2.获取数据类型
Class<?> type = field.getType();
// 3.获取变量名
String name = field.getName();
System.out.print(modifiers + " -- " + Modifier.toString(modifiers) + " -- " + type + " -- " + name);
System.out.println();
}
}
- test2()方法的输出结果如下:
2 -- private -- class java.lang.String -- name
0 -- -- int -- age
4 -- protected -- class java.lang.String -- email
1 -- public -- class java.lang.String -- address
注意:如上所示,访问修饰符由数字来表示,见源码Modifier类
5.2、获取类的方法
@Test
public void test2() {
Class<Employee> clazz = Employee.class;
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
// 1.获取方法声明的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
System.out.print(annotation);
}
System.out.println();
// 2.权限修饰符
System.out.print(Modifier.toString(method.getModifiers()) + "\t");
// 3.返回值类型
System.out.print(method.getReturnType().getName() + "\t");
// 4.方法名
System.out.print(method.getName() + "\t");
System.out.print("(");
// 5.形参列表
Class[] types = method.getParameterTypes();
if (!(types == null && types.length == 0)) { // 含参构造器
int index = 0;
for (int i = 0; i < types.length; i++) {
if (index == types.length - 1) {
System.out.println(types[i].getName() + "args" + i);
break;
}
System.out.print(types[i].getName() + "args" + i + ",");
}
}
System.out.print(")");
System.out.println();
}
}
5.3、获取类的构造器
@Test
public void test3() throws NoSuchMethodException {
Class<Employee> clazz = Employee.class;
// getConstructors(): 获取当前运行时类中声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("--------");
// getDeclaredConstructors(): 获取当前运行时类中声明的所有构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor cons : declaredConstructors) {
System.out.println(cons);
}
}
5.4、获取父类的基本信息
- 获取运行时类的普通父类、带泛型的父类、带泛型父类的泛型
@Test
public void test1(){
// 1. 获取运行时类的父类
Class<Employee> clazz = Employee.class;
Class<? super Employee> superclass = clazz.getSuperclass();
System.out.println(superclass); // class com.xyl.javase.reflection.structure.Creature
// 2.获取运行时类的带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass); // com.xyl.javase.reflection.structure.Creature<java.lang.String>
// 3.获取运行时类的带泛型 父类的泛型
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
Type[] types = paramType.getActualTypeArguments();
System.out.println(types[0].getTypeName()); // java.lang.String
}
- 获取运行时类的接口
@Test
public void test2(){
// 1.获取运行时类实现的接口
Class<Employee> clazz = Employee.class;
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> interf : interfaces) {
System.out.println(interf);
}
System.out.println("----------");
// 2.获取运行时类的父类 实现的接口
Class<?>[] interfaces2 = clazz.getSuperclass().getInterfaces();
for (Class<?> inter : interfaces2) {
System.out.println(inter);
}
}
- 获取运行时类所在的包和接口
@Test
public void test3(){
Class<Employee> clazz = Employee.class;
// 1.获取运行时类的注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 2.获取运行时类所在的包
Package aPackage = clazz.getPackage();
System.out.println(aPackage);
}
6、调用运行时类的指定结构
6.1、操作运行时类中的指定属性
@Test
public void test3() throws InstantiationException, IllegalAccessException, NoSuchFieldException {
Class<Employee> clazz = Employee.class;
// 创建运行时类的实例对象
Employee employee = clazz.newInstance();
// 1.getDeclaredField(String field): 获取运行时类中指定变量名的属性
Field address = clazz.getDeclaredField("address");
Field fname = clazz.getDeclaredField("name");
// 2.setAccessible(boolean flag): 保证当前属性是可以被访问的
fname.setAccessible(true); // 访问私有属性时,需要设置对应的访问权限
// 3.获取、设置指定对象的属性值
fname.set(employee,"张三");
address.set(employee,"高新路");
System.out.println(address.get(employee));
System.out.println(fname.get(employee));
}
6.2、操作运行时类中的方法
@Test
public void testMethod() throws Exception {
Class<Employee> clazz = Employee.class;
Employee employee = clazz.newInstance();
// 获取指定的某个方法,
// getDeclaredMethod()方法: 参数1,指明获取的方法名称; 参数2,获取方法的形参列表
// 调用invoke方法,其返回值为 获取到的方法的返回值
Method showInfo = clazz.getDeclaredMethod("showInfo", String.class);
showInfo.setAccessible(true);
// 调用invoke方法,其参数值1为 方法的调用者,参数2:给方法形参赋值的实参
Object returnValue = showInfo.invoke(employee, "China");
System.out.println(returnValue);
System.out.println("如下调用私有的静态方法....");
// private static void testPrivateAndStatic()
Method method = clazz.getDeclaredMethod("testPrivateAndStatic");
method.setAccessible(true);
// 如果调用 运行时类中的方法没有返回值,则invoke()方法返回null
// Object invoke = method.invoke(null);
Object invoke = method.invoke(Employee.class);
System.out.println(invoke); // 无返回值,则返回null
}
6.3、操作运行时类中的构造器
@Test
public void testConstructor() throws Exception {
Class<Employee> clazz = Employee.class;
// 1.获取指定的构造器
// getDeclaredConstructor(Class<?>... parameterTypes): 方法参数中需要传入构造器的参数类型
Constructor<Employee> constructor = clazz.getDeclaredConstructor(String.class,int.class);
// 2.保证此构造器是可被访问的
constructor.setAccessible(true);
// 3.调用对应的构造器,创建运行时类的对象
Employee employee = constructor.newInstance("维克多.雨果", 26);
System.out.println(employee);
}
7、反射的应用:动态代理
Spring中的IOC容器和AOP,IOC容器基于依赖注入实现,AOP基于动态代理来完成。
7.1、静态代理
- 静态代理:代理类和被代理类 在编译期间就可以确定
public interface GoodsFactory {
public void productGoods();
}
// 被代理类
public class AppleFactory implements GoodsFactory{
@Override
public void productGoods() {
System.out.println("AppleFactory 开始生产 Apple ...");
}
}
// 代理类
public class ProxyGoodsFactory implements GoodsFactory{
private GoodsFactory goodsFactory; // 用被代理类对象进行实例化
public ProxyGoodsFactory(GoodsFactory goodsFactory) {
this.goodsFactory = goodsFactory;
}
@Override
public void productGoods() {
System.out.println("代理工厂ProxyGoodsFactory 开始做些准备工作:");
goodsFactory.productGoods();
System.out.println("产品生产完成,接下来去执行首尾工作....");
}
public static void main(String[] args) {
// 创建被代理类的对象
AppleFactory appleFactory = new AppleFactory();
// 创建代理类的对象
ProxyGoodsFactory proxy = new ProxyGoodsFactory(appleFactory);
proxy.productGoods();
}
}
7.2、动态代理
- 动态代理:代理类和被代理类在运行时动态确定
public interface Human {
String getBelief();
void eat(String food);
}
public class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃 " + food);
}
}
- 代理工厂
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
// 返回代理类对象
public static Object getProxyInstance(Object obj){ // Obj: 被代理类的对象
CustomInvocationHandler handler = new CustomInvocationHandler();
Class<?> clazz = obj.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),handler);
}
}
class CustomInvocationHandler implements InvocationHandler{
private Object object; // 需要使用被代理类对象要调用的方法
public void bind(Object object){
this.object = object;
}
// 当通过代理类的对象,调用某方法时,就会自动调用如下的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// method: 代理类对象调用的方法,也作为被代理对象要调用的方法
// object: 被代理类的对象
return method.invoke(object,args);
}
}
- 测试:
public class DynamicProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
// instance: 代理类的对象
Human instance = (Human) ProxyFactory.getProxyInstance(superMan);
// 当通过代理类对象调用方法时,会自动地调用被代理类中同名的方法
instance.getBelief();
instance.eat("极品佛跳墙");
System.out.println("================");
AppleFactory appleFactory = new AppleFactory();
GoodsFactory goodsFactory = (GoodsFactory) ProxyFactory.getProxyInstance(appleFactory);
goodsFactory.productGoods();
}
}
7.3、动态代理与AOP
- 前面介绍的Proxy和InvocationHandler,很难看出这种动态代理的优势,下面介绍一种更实用的动态代理机制
- 改进后的说明:代码段1、代码段2、代码段3和深色代码段分离开了,但代码段1、2、3又和一个特定的方法A耦合了!最理想的效果是:代码块1、2、3既可以执行方法A,又无须在程序中以硬编码的方式直接调用深色代码的方法。
- 使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理
- 这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。