JAVA基础——反射,反射详细内容介绍,类加载,反射获得有参无参构造,获取方法,获取属性,越过泛型检查,动态代理

一、 类的加载概述和加载时机

  1. 类的加载概述:

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载连接初始化三步来实现对这个类进行初始化。

  • 加载:就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
  • 连接
    • 验证 是否有正确的内部结构,并和其他类协调一致
    • 准备 负责为类的静态成员分配内存,并设置默认初始化值
    • 解析 将类的二进制数据中的符号引用替换为直接引用
  • 初始化:默认初始化,构造初始化。
  1. 加载时机
  • 创建类的实例。(new一个对象)
  • 访问类的静态变量,或者为静态变量赋值。(静态成员变量)
  • 调用类的静态方法
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类(父类先加载,子类再加载)
  • 直接使用java.exe命令来运行某个主类

二、 类加载器的概述和分类

  1. 类加载器的概述
    负责将.class文件加载到内存中,并为之生成对应的Class对象。虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。

  2. 类加载器的分类

  • Bootstrap ClassLoader 根类加载器
  • Extension ClassLoader 扩展类加载器
  • Sysetm ClassLoader 系统类加载器
  1. 类加载器的作用

(1) Bootstrap ClassLoader 根类加载器

  • 也被称为引导类加载器,负责Java核心类的加载。
  • 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中

(2) Extension ClassLoader 扩展类加载器

  • 负责JRE的扩展目录中jar包的加载。
  • 在JDK中JRE的lib目录下ext目录

(3) System ClassLoader 系统类加载器

  • 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。比如自己写的类。

三、 反射概述

  1. 反射概述
  • JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
  • 对于任意一个对象,都能够调用它的任意一个方法和属性;
  • 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
  • 要想解剖一个类,必须先要获取到该类的字节码文件对象。
  • 而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
  1. 三种方式
  • Class类中静态方法forName(),读取配置文件。如:写一个proprities文件获取数据库的连接。
  • Object类的getClass()方法,判断两个对象是否是同一个字节码文件
  • 静态属性class,锁对象。如,多线程中的锁对象。
    在这里插入图片描述
		Class clazz1=Class.forName("com.day27.bean.Person");
		Class clazz2=Person.class;
		Person person =new Person();
		Class clazz3= person.getClass();
		System.out.println(clazz1 ==clazz2);
		System.out.println(clazz2 ==clazz3);

效果如下:
在这里插入图片描述表示为同一个类

四、 Class.forName()读取配置文件举例

public static void main(String[] args) throws Exception {
    
    
		//没有使用反射,只使用多态
		//Juicer juicer =new Juicer();
		//juicer.run(new Apple()); 		//向榨汁机中放入苹果
		//juicer.run(new Orange());
	
		//使用反射机制
		BufferedReader br =new BufferedReader(new FileReader("config.properties"));
		Class clazz =Class.forName(br.readLine());
		//父类引用指向子类对象,水果的引用指向了苹果对象
		Fruit f =(Fruit) clazz.newInstance();			
		Juicer j =new Juicer();
		j.run(f);	
	}

}

interface Fruit{
    
    
	public void squeeze();
}

class Apple implements Fruit{
    
    
	public void squeeze() {
    
    
		System.out.println("榨出苹果汁");
	}
}

class Orange implements Fruit{
    
    
	public void squeeze() {
    
    
		System.out.println("榨出橘子汁");
	}
}

class Juicer{
    
    
	/*
	 * public void run(Apple a) { 
	 * a.squeeze(); 
	 * } 
	 * public void run(Orange o) {
	 * o.squeeze(); 
	 * }
	 */
	public void run(Fruit f) {
    
    
		f.squeeze();
	}

此时我们只需要修改我们的配置文件
在这里插入图片描述

五、 通过反射获取带参构造方法并使用

Constructor:
Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用Class类的getConstructor(String.class,int.class)方法获取一个指定的构造函数然后再调用Constructor类的newInstance(“张三”,20)方法创建对象

Class clazz=Class.forName("com.day27.bean.Person");
//Person person=(Person)clazz.newInstance();		
//通过无参构造创建对象
//如果我们在此删除掉person的无参构造函数,那么我们将无法使用该函数
//System.out.println(person);
Constructor
constructor=clazz.getConstructor(String.class,int.class);//获取有参构造	
Person person =(Person) constructor.newInstance("张三",24);			
//通过有参构造创建对象
System.out.println(person);		

效果如下:
在这里插入图片描述
Person person=(Person)clazz.newInstance();
//通过无参构造创建对象

六、 通过反射获取成员变量并使用

Field:
Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField("name")方法获取,通过set(obj, "李四")方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值

Class clazz=Class.forName("com.day27.bean.Person");
		Constructor constructor=clazz.getConstructor(String.class,int.class);
		Person person =(Person) constructor.newInstance("张三",23);
		
		//Field field =clazz.getField("name");	
//获取姓名字段,此时我们并不能访问到name的属性。因为Person类已经将其私有化。所以我们采用暴力反射
		//field.set(person, "李四");					
//修改姓名的值	
		
		Field field =clazz.getDeclaredField("name");	
//暴力反射获取字段
		field.setAccessible(true);  					//去除私有权限,对自身开放
		field.set(person, "李四");								
		System.out.println(person);

七、 通过反射获取方法并使用

Method:
Class.getMethod(String, Class...)Class.getDeclaredMethod(String, Class...)方法可以获取类中的指定方法,调用invoke(Object, Object...)可以调用该方法Class.getMethod("eat") invoke(obj) Class.getMethod("eat",int.class) invoke(obj,10)

Class clazz=Class.forName("com.day27.bean.Person");
		Constructor constructor=clazz.getConstructor(String.class,int.class);
		Person person =(Person) constructor.newInstance("张三",23);
		//获取方法
		Method method =clazz.getMethod("eat");		
		method.invoke(person);
		//获取有参的eat方法
		Method method2=clazz.getMethod("eat",int.class);	
		method2.invoke(person, 10);

八、 练习:通过反射越过泛型检查

需求: ArrayList的一个对象,在这个集合中添加一个字符串数据,如何实现呢?

public class Demo1_Test {
    
    
	public static void main(String[] args) throws Exception {
    
    
		//注意泛型只在编译期有效,在运行期会被擦除
		ArrayList<Integer> list =new ArrayList<Integer>();
		list.add(111);
		list.add(222);
		
		Class clazz =Class.forName("java.util.ArrayList");
		
		Method method =clazz.getMethod("add", Object.class);
		method.invoke(list, "abc");
		System.out.println(list);
	}
	}

效果如下:
在这里插入图片描述

九、练习: 通过反射写一个通用的设置某个对象的某个属性为指定的值

public void setProperty(Object obj, String propertyName, Object value){},此方法可将obj对象中名为propertyName的属性的值设置为value。

先写一个工具类TOOL

public class Tool {
    
    
	//此方法可将obj对象中名为propertyName的属性的值设置为value
	public void setProperty(Object obj,String propertyName,Object value) throws Exception {
    
    
		Class clazz =obj.getClass();			//获取字节码对象
		Field field =clazz.getDeclaredField(propertyName);	//暴力反射获取字段
		field.setAccessible(true);
		field.set(obj, value);
	}
}

书写测试类

public class Demo2_Test {
    
    
	public static void main(String[] args) throws Exception {
    
    
		Student student =new Student("张三",13);	
		System.out.println(student);
		Tool tool =new Tool();
		tool.setProperty(student, "name", "李四");
		System.out.println(student);
	}	
}
class Student{
    
    
	private String name;
	private int age;
	public Student(){
    
    
		super();
	}
	public String getName() {
    
    
		return name;
	}
	public void setName(String name) {
    
    
		this.name = name;
	}
	public int getAge() {
    
    
		return age;
	}
	public void setAge(int age) {
    
    
		this.age = age;
	}
	public Student(String name, int age) {
    
    
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
    
    
		return "Student [name=" + name + ", age=" + age + "]";
	}
	
}

在这里插入图片描述

十、 已知一个类,定义如下:

public class DemoClass {
public void run() {
System.out.println(“hello world!”);
}
}
(1) 写一个Properties格式的配置文件,配置类的完整名称。
(2) 写一个程序,读取这个Properties配置文件,获得类的完整名称并加载这个类,用反射的方式运行run方法。
先要自行写一个配置文件

public class DemoClass {
    
    
	public void run() {
    
    
		System.out.println("hello world");
	}
}

通过方法测试:

BufferedReader bf =new BufferedReader(new FileReader("x.properties"));
		Class clazz =Class.forName(bf.readLine());
		DemoClass demoClass =(DemoClass) clazz.newInstance();//通过字节码对象创建对象
		demoClass.run();

十一、 动态代理的概述和实现

  1. 动态代理概述

    代理:本来应该自己做的事情,请了别人来做,被请的人就是代理对象。就是可以把本身的方法添加其他方法

    动态代理:在程序运行过程中产生的这个对象,而程序运行过程中产生对象其实就是反射,所以,动态代理其实就是通过反射来生成一个代理

  2. 在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象

  • public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
  • 最终会调用InvocationHandler的方法
  • InvocationHandler Object invoke(Object proxy,Method method,Object[] args)
    案例:
    第一步 创建接口
public interface User {
    
    
	public void add();
	public void delete();
}
public interface Student {
    
    
	public void login();
	public void submit();
}

第二步,实现接口方法

public class UserImp implements User {
    
    
	@Override
	public void add() {
    
    
			System.out.println("添加");
	}
	@Override
	public void delete() {
    
    
		System.out.println("删除");
	}
}
public class StudentImp implements Student {
    
    
	@Override
	public void login() {
    
    
		System.out.println("登陆");
	}

	@Override
	public void submit() {
    
    
		System.out.println("提交");
	}
}

第三步:书写MyInvocationHandler接口

public class MyInvocationHandler implements InvocationHandler {
    
    
	private Object target;
	public MyInvocationHandler(Object target) {
    
    
		this.target =target;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
		System.out.println("权限校验");
		method.invoke(target, args);		
//执行被代理target对象方法
		System.out.println("日志文件");
		return null;
	}
}

第四步做测试:

public class TEST {
    
    
		public static void main(String[] args) {
    
    
			UserImp ui =new UserImp();
			ui.add();
			ui.delete();
			System.out.println("------");
			MyInvocationHandler myInvocationHandler=new MyInvocationHandler(ui);
			User u  =(User) Proxy.newProxyInstance(ui.getClass().getClassLoader(), ui.getClass().getInterfaces(), myInvocationHandler);
			u.add();
			u.delete();
			System.out.println("-------");
			StudentImp si =new StudentImp();
			si.login();
			si.submit();
			
			MyInvocationHandler myInvocationHandler2 =new MyInvocationHandler(si);
			Student student=(Student) Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(),myInvocationHandler2);
			
			student.login();
			student.submit();
		}
}

效果如下:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Mr_GYF/article/details/108968535