Java反射机制深入学习(反射 实现配置文件 到 自定义注解转换 案例实现)

一、前言引入

  • 什么是反射?

Java 反射是可以让我们在运行时获取类的函数、属性、父类、接口等 Class内部信息的机制。通过反射还可以让我们在运行期实例化对象,调用方法,通过调用 get / set方法获取变量的值,即使方法或属性是私有的的也可以通过反射的形式调用,这种“看透 class” 的能力被称为内省,这种能力在框架开发中尤为重要。

  • 反射能干吗?

有些情况下,我们要使用的类在运行时才会确定,这个时候我们不能在编译期就使用它,因此只能通过反射的形式来使用在运行时才存在的类(该类符合某种特定的规范,例如
JDBC,这是反射用得比较多的场景。Java类通过java反射机制,可以在程序中访问已经装载到JVM中的java对象描述,实现访问、检测和修改描述java对象本身信息的功能。

  • 接下来我们要学习什么?

    • 认识反射
    • 掌握获取Class对象的三种方法
    • 了解反射中常用的方法
    • 反射小案例

Java反射机制深入学习(反射 实现配置文件 到 自定义注解转换 案例实现)_第1张图片

二、具体内容

2.1 认识反射
反射先认识“反”,在正常情况下,一定是先有类,而后有对象,

import java.util.Date; // 先有类,而后有对象

public class TestDemo {
	public static void main(String args[]) throws Exception{
		Date data = new Date();
		System.out.println(data);//产生对象
	}
}

所谓的“反”就是指利用对象找到对象的出处,在Object类里面提供有一个方法:
取得Class对象:

方法 释义
public final Class getClass() 返回此 Object 的运行时类

范例:

 public class TestDemo {
	public static void main(String args[]) throws Exception{
    	Date date = new Date();
		System.out.println(date);//产生对象
		System.out.println(date.getClass());
	}
}
  • 返回:class java.util.Date 调用了getClass()方法后的输出类的完整名称,等于是找到了对象的出处。

2.2、Class 类对象实例化
Java反射机制深入学习(反射 实现配置文件 到 自定义注解转换 案例实现)_第2张图片
获取Class对象的三种方式对应图中 的三个阶段

Java.,lang.Class 是一个类,这个类是反射操作的源头,即:所有的反射都要从此类开始

获取Class对象的三种方式:

  • 第一种:调用Object类中的getClass()方法;

此种实例化方法的使用可用于代码优化,不太常用。

public class TestDemo {
	public static void main(String args[]) throws Exception{
		Date date = new Date();
	    Class cls = date.getClass();
		System.out.println(cls);
	}
}

结果返回:class java.util.Date

  • 第二种:使用 “类.class” 取得,此种方法在讲解Hibernate、MyBatis、Spring时常用;

    public class TestDemo {
             	public static void main(String args[]) throws Exception{
             	    Class cls = Date.class;
            	System.out.println(cls);
             	}
    }
    

之前是在产生了类的实例化对象之后取得的class类对象,但是此时并没有实例化对象的产生。

  • 第三种:调用Class 类提供的一个方法
方法 释义
public static Class forName(String className) throws ClassNotFoundException 返回与带有给定字符串名的类或接口相关联的 Class 对象

范例:

public class TestDemo {
	public static void main(String args[]) throws Exception{
	    Class cls = Class.forName("java.util.Date");
		System.out.println(cls);
	}
}结果一样
  • 此时可以不使用import语句导入一个明确的类,而类名称是采用字符串的形式进行描述。

2.3 反射实例化对象

当拿到一个类的时候,肯定要直接使用关键字new经行实例化操作,这属于习惯性实例化类对象,但是如果有了calss类实例化对象,那么就可以做到利用反射来实现对象实例化的操作:

方法 释义
public T newInstance() throws InstantiationException,IllegalAccessException 创建此 Class 对象所表示的类的一个新实例

范例:利用反射实例化对象

class Book {
	public Book() {
		System.out.println("*****book类的无参构造方法******");
	}
	public String toString() {
		return "这是一本书";
	}
}
public class TestDemo{
	public static void main(String args[]) throws Exception{
		Class cls = Class.forName("com.javabase.demo.Book");
		Object obj = cls.newInstance();//相当于使用new调用无参构造实例化			
	}
}
public class TestDemo{
	public static void main(String args[]) throws Exception{
		Class cls = Class.forName("com.javabase.demo.Book");
		Object obj = cls.newInstance();//相当于使用new调用无参构造实例化
		Book book = (Book) obj;//向下转型
		System.out.println(book);
	}

}

实例化 Book对象 将 输出第一行 “book类的无参构造”
向下转型 输出 book对象 将调用 toString() 方法输出“这是一本书”在这里插入图片描述

  • 有了反射之后,以后对象的实例化操作不再只是单独的依靠关键字new完成了,但是并不表示new关键字就被完全取代了。

2.4 反射中常用的方法

功能 方法名
得到类对象 1.类名.class 2.对象名.getClass() 3.Class.forName(“全类名”)
类对象得到所有构造方法 类对象.getConstructors() 类对象.getDeclaredConstructors()
类对象得到所有成员方法 类对象.getMethods() 类对象.getDeclaredMethods()
类对象得到所有成员变量 类对象.getFields()类对象.getDeclaredFields()
构造方法实例化对象 构造对象.newInstance()
调用普通方法 方法对象.invoke()
成员变量设置/取值 成员对象.set() / 成员对象.get()
暴力反射 对象名.setAccessible(true)

范例: 类对象得到所有成员变量

mport com.itheima.domain.Person;

import java.lang.reflect.Field;

/**
 * @Author: kangna
 * @Date: 2019/8/8 10:49
 * @Version:
 * @Description: 
 */
public class ReflectPerson {
    public static void main(String[] args) throws  Exception{

        // 1.获取class对象的第一种方式
        Class cls1 = Class.forName("com.itheima.domain.Person");
        System.out.println(cls1);

        // 2.获取class 对象的第二种方式
        Class cls2=Person.class;
        System.out.println(cls2);
        System.out.println("---------------------------------------------");

        // 3. 获取class 对象的第三种方式

        Class cls3 = new Person().getClass();
        System.out.println("类加载器:" + cls3.getClassLoader());
        System.out.println(cls3);

        System.out.println(cls1 == cls2); // true
        System.out.println(cls1 == cls3); // true
        System.out.println(cls2 == cls3); // true

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

        Class personClass = Person.class;

        // 1. Field[] getFields() 获取所有public修饰的成员变量
        Field[] files = personClass.getFields();
        for (Field field : files) {
            System.out.println(field); // public java.lang.String com.itheima.domain.Person.a
        }

        Field  a = personClass.getField("a");

        // 获取成员变量的 a 的值
        Person p = new Person();
        Object value = a.get(p);

        System.out.println(value);
        a.set(p, "张三");
        System.out.println(p);
    }
}

范例:反射之Constructor 的使用

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

/*
 * 构造一个学生类
 */
class Student {
    private int age;
    private String name;
    public Student() {

    }

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

    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;
    }

    @Override
    public String toString() {
        return "Student [age=" + age + ", name=" + name + "]";
    }
}

public class Demo_03Reflect {

	public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
		
		// 获得 Class  对象
		Class cls = Student.class;
		// 方式一 : 通过Calss 对象的方法  去  创建 
		Object obj = cls.newInstance();
		
		// 方式 二 ,通过构造方法  创建
		
		Constructor cons = cls.getConstructor(int.class, String.class);
		
		Object obj2 = cons.newInstance(23, "jack");
		
		System.out.println(obj2);
		
	}
}

在这里插入图片描述
2.5 综合案例:

在案例中我们将使用反射中常用的方法

需求:写一个"框架",通过反射帮我们创建任意类的对象,并且执行其中任意方法,例如有配置文件pro.properties,存储在项目的src文件夹下,内容如下。根据配置文件信息创建一个学生对象Student,Student中包含一个sleep方法,最终实现调用该方法。

方式一实现:配置文件

第一步:创建学生类

public class Student {
    public void sleep() {
        System.out.println("sleeping");
    }    
    public void eat() {
        System.out.println("eatting");
    }
}

第二步:创建pro.properties配置文件

className=com.itheima.domain.Student
methodName1=sleep
methodName2=eat

第三步:实现 ReflectTest 类

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * @Author: kangna
 * @Date: 2019/8/8 15:08
 * @Version:
 * @Description: ReflectTest  测试类实现
 */
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        // 创建 Properties 对象
        Properties pro = new Properties();


        // 加载class 目录文件下的配置文件
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("pro.properties");
        pro.load(is);

        // 获取配置文件定义的数据
        String className = pro.getProperty("className");
        String methodName1 = pro.getProperty("methodName1");
        String methodName2 = pro.getProperty("methodName2");

        // 3.加载该类进内存
        Class cls = Class.forName(className);

        // 4. 创建对象
        Object obj = cls.newInstance();

        // 5. 创建方法对象
        Method method1 = cls.getMethod(methodName1);
        Method method2 = cls.getMethod(methodName2);

        // 6. 执行方法
        method1.invoke(obj);
        method2.invoke(obj);
    }
}

在这里插入图片描述

  • 观察运行结果我们可以看到,运用配置文件的方式,使用反射技术 我们成功的运行了Student类中的方法。

方式二:自定义注解使用反射技术实现上面的案例

第一步:自定义注解(代替配置文件)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 
 * @author kangna
 * @Date 2019年8月8日 下午8:49:16
 * 
 * @description 使用  自定义注解  配置 类,使用  java 反射技术  实现  配置文件的 用注解实现的转换
 */
@Target({ ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
	/*
	 * 在  注解中 定义属性 ,也就是  定义成员方法 
	 * 自定义的注解是当前的接口继承  java.lang.annotation.Annotation 接口    
	 * 使用  反射技术  定义 子类  实现  Pro 接口对象
	 */
	String className();  
	String methodName();
}

第二步:定义Demo类

public class Demo {
	
	public void show() {
		System.out.println("Demo....show()");
	}
}

第三步:ReflectAnnoTest 类

    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * 
     * @author kangna
     * @Date 2019年8月8日 下午9:07:52
     * 
     * @description 使用 注解 定义类的 全路径  然后使用  反射  获取类的  对象
     */
    @Pro(className = "com.itheima.day02.Demo", methodName = "show")
    public class ReflectAnnoTest {
    
    	public static void main(String[] args) throws Exception {
    		// 1. 解析
    		// 1.1  获取, 该类的字节码文件对象
    		Class reflectAnno = ReflectAnnoTest.class;
    		
    		// 2. 获取上边的  注释对象,, getAnnotation  获取接口对象 class 对象
    		Pro an = reflectAnno.getAnnotation(Pro.class);
    		
    		// 3. 调用注释对象中定义的方法, 获取返回值,,,  使用接口对象  调用定义的属性
    		String className = an.className();
    		String methodName = an.methodName();
    		
    		// 输出验证
    		System.out.println(className);
    		System.out.println(methodName);
    		
    		// 加载该类进内存, 使用  forName方法  读取类的全路径
    		Class cls = Class.forName(className);
    		
    		
    		// 创建对象
    		Object obj = cls.newInstance();
    		
    		// 创建方法对象
    		Method method = cls.getMethod(methodName);
    		
    		method.invoke(obj);
    	}

}

总结

灵活使用反射能让我们代码更加灵活,这里比如JDBC原生代码注册驱动,hibernate 的实体类,Spring 的AOP等等都有反射的实现。但是凡事都有两面性,反射也会消耗系统的性能,增加复杂性等,合理使用才是真!

你可能感兴趣的:(Java基础学习)