Reflection反射

  • 反射是什么?
  • 反射的工作原理
  • 反射的用途
  • Class类
  • 1.获取类对应的字节码的对象的三种方式:
  • 2. 通过该Class实例来创建对应类型的实例
  • 3.Class类常用方法
  • Constructor类
  • 1.获取构造方法:
  • 2.调用构造方法:
  • Field类
  • 1.获取Field 字段
  • 2.通过反射获取和设置字段的值
  • 3.获取字段的值
  • Method类
  • 1.获取方法:
  • 2.调用方法:
  • 3.调用静态方法:
  • 4.多态:
  • 动态代理
  • 1.代理模式:
  • 2.静态代理:
  • 3.动态代理:
  • 4.动态代理的用途:
  • 总结



反射是什么?

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.


反射的工作原理

当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已。
反射的工作原理就是借助Class.java、Constructor.java、Method.java、Field.java这四个类在程序运行时动态访问和修改任何类的行为和状态。


反射的用途

  1. 动态加载类:在运行时根据某些条件动态地加载和使用类。
  2. 动态获取类的信息:可以获取类的属性、方法和构造函数等信息。
  3. 动态调用方法:可以在运行时根据方法名动态地调用类的方法。
  4. 动态修改属性:可以在运行时获取和设置类的属性值。
  5. 实现通用框架和工具:反射使得我们可以编写更通用、灵活的代码,例如自动化测试框架、ORM(对象关系映射)框架等

Class类

要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是 Class 类中的方法。所以先要获取到每一个字节码文件(.class)对应的 Class 类型的对象.

为实现方法的使用,以下所有方法均以Book类为例进行操作:

class Book extends Product{
	public String bookName;
	public double price;
	private int pageNum;
	private double salt;	
	public Book() {
	}
	public Book(String bookName) {		
	}
	public void dosth(){
	}
	public int dosth(int r){
	Random rand = new Random();
		int r =rand.nextInt(n);
		return r;
	}
	public void dosth(int a,double b,float c) {		
	}	
	@Override
	public String toString() {
		return "Book [bookName=" + bookName + ", price=" + price + ", pageNum=" + pageNum + ", salt=" + salt + "]";
	}
	public String getBookName() {
		return bookName;
	}
	public void setBookName(String bookName) {
		this.bookName = bookName;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	public int getPageNum() {
		return pageNum;
	}
	public void setPageNum(int pageNum) {
		this.pageNum = pageNum;
	}
	public double getSalt() {
		return salt;
	}
	public void setSalt(double salt) {
		this.salt = salt;
	}
	
}

1.获取类对应的字节码的对象的三种方式:

//方式1:通过类名访问class
Class bookCls1 = Book.class;
//方式2:通过实例访问getClass()
Book book= new Book();
Class bookCls2 = book.getClass();
//方式3:通过Class类的静态方法forName(类名)
Class bookCls3 = Class.forName("java.lang.package com.apesource.demo2.Book");

2. 通过该Class实例来创建对应类型的实例

newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。

//创建(获取)Class对象
Class cls = Book.class;
//调用无参构造方法创建String类型的对象
Book book = (Book) cls.newInstance();

3.Class类常用方法

//类名
System.out.println("类(接口)的名称:"+cls.getSimpleName());
System.out.println("完全限定名:"+cls.getName());
System.out.println("类(接口)的类型称:"+cls.getTypeName());
//父类
Class superCls = cls.getSuperclass();
System.out.println("类的父类"+superCls);
//实现的接口
Class[] interfaceCls = cls.getInterfaces();
System.out.println("实现的接口:");
System.out.println(Arrays.toString(interfaceCls));
//package包
Package pck = cls.getPackage();
	if(pck!=null) {
		System.out.println("类所在的包的名称:"+pck.getName());
	}
//判断类型
System.out.println("是否为接口:"+cls.isInterface());
System.out.println("是否为数组:"+cls.isArray());
System.out.println("是否为枚举:"+cls.isEnum());
System.out.println("是否为基本类型:"+cls.isPrimitive());

Constructor类

为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象是一个构造方法,调用结果总是返回实例:

1.获取构造方法:

Class cls = Book.class;
	....
//getConstructors():获取所有权限为 public 的构造方法
Constructor[] constructorArr= cls.getConstructors();
//getDeclaredContruectors():获取当前对象的所有构造方法
//Constructor[] constructorArr= cls.getDeclaredContruectors();
	for(Constructor construct : constructorArr) {
		System.out.println(construct);
	}

2.调用构造方法:

无参构造方法:

Class cls = Book.class;
	//调用无参构造方法创建Example类型的对象
	Book b1 = (Book)cls.newInstance();

有参构造方法:

//有一个参数的构造方法
Constructor constructor2 = cls.getConstructor(String.class);
//执行构造方法,创建String类型的对象
Book b2  = (Book) constructor2.newInstance("sssss");
//获取私有的有参构造方法
Constructor privateConstructor = cls.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true);
Book b3 = (Book)privateConstructor.newInstance("sss");

Field类

对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。比如通过一个Class实例获取字段信息。Class类提供了以下几个方法来获取字段:

1.获取Field 字段

Class cls = Book.class;
//获取所有public修饰的字段(包含父类)
Field[] fields = cls.getFields();
//获取所有定义的字段(仅自己)
//	Field[] fields = cls.getDeclaredFields();
//
for (Field field : fields) {
	System.out.println("成员变量修饰符:"+field.getModifiers());
	System.out.println("成员变量修饰符:"+Modifier.toString(field.getModifiers()));
	System.out.println("成员变量的类型:"+field.getType());
	System.out.println("成员变量的名称:"+field.getName());
	System.out.println("----------------------------");
	}

2.通过反射获取和设置字段的值

//运行期通过反射完成对象类型和成员变量的访问
//public修饰的字段
//获取Class类型对象
Class cls = Book.class;
//通过反射创建Book类型对象
Object obj = cls.newInstance();
//按照字段名称,获取指定字段
Field field1 = cls.getField("bookName");
//设置成员变量的值
//参数1:目标Book对象,参数2:存入成员变量中的值
field1.set(obj, "GGBond三打白骨精");

//private和protected修饰的字段:
Field field2 =cls.getDeclaredField("pageNum");
field2.setAccessible(true);
field2.set(obj, 1123);

3.获取字段的值

Class cls = obj.getClass();
	//获取所有定义的字段
	Field[] fields = cls.getDeclaredFields();
	for (Field field : fields) {
		System.out.println("成员变量名称:"+field.getName());
		if(!field.isAccessible()) {
			field.setAccessible(true);
		}
		system.out.println("成员变量的值:"+field.get(obj));
		System.out.println("-----------------------------------");
		}

Method类

每一个方法都会被封装成一个Method对象,同样的,可以通过Class实例获取所有方法(Method类型的对象)。Class类提供了以下几个方法来获取Method:

1.获取方法:

Class cls = Book.class;
//获取所有public方法(包含父类)
//Method[] methods = cls.getMethods();
//获取所有定义的方法(仅自己)
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
	System.out.println("方法的访问修饰符:"+Modifier.toString(method.getModifiers()));
	System.out.println("方法的返回值类型:"+method.getReturnType());
	System.out.println("方法的名称:"+method.getName());
			
	//获取所有的参数类型:
	//System.out.println("方法的参数类型:");
	//Class[]	 paramTypes = method.getParameterTypes();
	//获取所有的参数对象
	Parameter[] parm = method.getParameters();
	for (Parameter p : parm) {
		System.out.println("("+p.getType()+" "+p.getName()+")");
		System.out.println("-----------");
			}
			
		}
		
	}

2.调用方法:

//获取class对象
		Class cls = Book.class;
		//创建Base对象
		Object obj = cls.newInstance();
		//按照方法名称和“参数类型”获取method方法对象
		//creat()
//		Method method = cls.getMethod("dosth");
		
		//creat(int n)
		Method method = cls.getMethod("creat",int.class);
		//invoke():以反射的方式执行方法
		int r = (int)method.invoke(obj,1000);
		System.out.println(r);
	}

3.调用静态方法:

//计算以10为底的对数(获取某个数字的位数)
System.out.println((int)(Math.log10(1235332))+1);
//以反射的方式实现
Class cls = Math.class;
Method method = cls.getMethod("log10", double.class);
int size = Double.valueOf((double)method.invoke(null, 1235332)).intValue()+1;
System.out.println(size);

4.多态:

通过反射调用方法时,仍然遵循多态原则:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Class cls = Person.class;	
		Method method = cls.getMethod("hello");		
		method.invoke(new Sutdent());
	}
}
class Person{
	public void hello() {
		System.out.println("Person:Hello");
	}
}
class Sutdent extends Person{
	@Override
	public void hello() {
		// TODO Auto-generated method stub
		System.out.println("Student:Hello");
	}
}

动态代理

1.代理模式:

代理模式是Java众多设计模式中的一种,代理模式就是给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。其本质就是在不改变原实现对象的情况下,通过"代理类"来完成对程序逻辑的扩展。

代理模式角色分为 3 种:

  1. Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,通常被设计成接口;
  2. RealSubject(真实主题角色):真正实现业务逻辑的类;
  3. Proxy(代理主题角色):用来代理和封装真实主题;
    以下面这个案例为例:
//接口
public interface UserService {
	void select();
	void update();
}
// 真正的实现类
public class UserServiceImpl implements UserService {
	@Override
	public void select() {
		System.out.println("select * ..................");
		System.out.println("数据库中完成用户信息的查询执行!");
	}
}

2.静态代理:

自己手动创建"代理类",通过代理,对行为逻辑的扩展:

public class UserServiceProxy implements UserService {
	private UserService target;
	public UserServiceProxy() {
		target = new UserServiceImpl();
	}
	@Override
	public void select() {
		before();
		target.select();//通过目标对象,真正实现业务逻辑处理
		after();
	}
	private void before() {
		System.out.println("方法开始执行........");
	}
	private void after() {
		System.out.println("方法结束执行........");
	}
}

3.动态代理:

通过Proxy.newProxyInstance()方法,由Jvm在运行期为我们自动生成一个看不见的代理类,通过InvocationHandler接口的实现类完成对程序逻辑的扩展。

/InvocationHandler接口实现类:代理类中扩展逻辑和抽取封装
public class LogInvoctionHandlerImpl implements InvocationHandler {
	private Object targer;
	public LogInvoctionHandlerImpl(Object target) {
		this.targer=target;
	}	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.printf("方法%s正在被执行。。。\n",method.getName());		
		//执行目标对象的目标方法
		Object resultValue=method.invoke(targer, args);//invoke(对象名,方法参数)		
		System.out.printf("方法%s执行结束。。。\n",method.getName());
		return resultValue;
	}

}

测试类:

public class Client {
	public static void main(String[] args) {
		//静态代理:
		UserService user = new UserServiceProxy();
		user.select();
		//动态代理
		//创建InvocationHandler
		InvocationHandler handler = new LogInvoctionHandlerImpl(new UserServiceImpl());		
		//创建UserService接口的动态代理对象
		UserService proxy = (UserService)Proxy.newProxyInstance(
									UserServiceImpl.class.getClassLoader(), 
									new Class[] {
											UserService.class
									}, 
									handler);
		System.out.println(proxy.getClass());
		//通过代理对象调用方法====>LogInvoctionHandlerImpl实现类的invoke
		proxy.select();

4.动态代理的用途:

动态代理在Java中有着广泛的应用,比如Spring AOPHibernate数据查询测试框架的后端mockRPC远程调用Java注解对象获取日志用户鉴权全局性异常处理性能监控,甚至事务处理等。


总结

  • 通过Class实例获取class信息的方法称为反射(Reflection);
  • 获取一个class对应的Class实例后,就可以获取该class的所有信息;
  • Java的反射API提供的Constructor对象封装了构造方法的所有信息;
  • Java的反射API提供的Field类封装了字段的所有信息;
  • 通过反射读写字段是一种非常规方法,它会破坏对象的封装;
  • Java的反射API提供的Method对象封装了方法的所有信息:
  • 通过反射调用方法时,仍然遵循多态原则;
  • 动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。