Java反射机制浅说——运行时类简单获取和应用 及 静态代理

针对性——设问?解析!

·1.什么是Java反射机制?它有什么作用?用手术解剖理解反射!

·2.什么是Class类?该怎么获取Class类的实例?

·3.什么是类的加载?ClassLoader又是什么东西?

扩展:类的初始化时机————面向对象

·4.怎么通过反射创建运行时类的对象?

重点: 关于 Class实例与运行类的对象的区别?

·5.怎么通过反射获取运行时类的完整结构?

·6.怎么实现通过反射调用运行时类的指定属性、指定方法等

a.小问题:在通过Class对象获取类的成员后对其进行赋值或调用时,需声明其是属于哪一个类对象的,这必然创建了类对象,那么,在这种场景下还使用Class类实例的意义是什么?

b.Class类的应用场景是什么?有什么要求?

·7.反射的 应用  : 静态代理   、 动态代理

·答

·1.Java Reflection (Java反射)

a. Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

柳叶刀剖析反射机制

解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.得到字节码文件对象后,我们就可以随心所欲的通过它在运行状态中知道并调用类中的任意一个方法或属性,包括获取成员以及对成员赋值。这也是反射机制的一种体现。

b.Java反射机制提供的功能:

在运行时判断任意一个对象所属的类

在运行时构造任意一个类的对象

在运行时判断任意一个类所具有的成员变量和方法

在运行时调用任意一个对象的成员变量和方法

生成 静态代理 、 动态代理

c.反射相关的主要API 及其 包

java.lang.Class:代表一个类

java.lang.reflect.Method:代表类的方法

java.lang.reflect.Field:代表类的成员变量

java.lang.reflect.Constructor:代表类的构造方法

·2.1Class的理解:

* Class本身也是一个类

* Class 对象只能由系统建立对象

* 一个类在 JVM 中只会有一个Class实例

* 一个Class对象对应的是一个加载到JVM中的一个.class文件

* 每个类的实例都会记得自己是由哪个 Class 实例所生成* 通过Class可以完整地得到一个类中的完整结构

·2.2获取Class实例的代码及解说

/*
 * Class 实例的获取共四种方式:
 * 			//方法一:通过类名获取时类
 * 			//方法二:通过对象名获取运行时类
 * 			//方法:通过Class类的静态方法forName()获取,
 * 			//方法四:通过当前类的运行时类的加载器对象 加载 并导入“类的全类名”或“全路径名”获取Class的实例
			
	Class类中的静态方法: 
		public static Class froName(String className)
		 任意数据类型都具备一个class静态属性,看上去要比第二种简单.
	一般我们到底使用谁呢?
	A:自己玩     任一种,第一种比较方便
	B: 开发        第三种
		
		原因: 因为第种是一个字符串,而不是一个具体的类名,这样我们就可以把这样的字符串配置到配置文件中
 * */
public class ClassTest {
	
	@Test
	public void test() throws Exception{
		
		//方法一:已知具体的类,通过类名获取时类, 类名.class()掌握
		Class clazz = Student.class;
		System.out.println(clazz);
		
		//方法二:通过对象名获取运行时类           对象名.getClass()掌握
		Student s = new Student();
		Class clazz2 = s.getClass();
		System.out.println(clazz2);
		
		//方法:通过Class类的静态方法forName()获取, Class.forName("类的全类名");掌握
		Class clazz3 = Class.forName("Classclazz.Student");
		System.out.println(clazz3);
		
		//方法四:通过当前类的运行时类的加载器对象 加载 并导入“类的全类名”或“全路径名”获取Class的实例(理解即可)
		ClassLoader cl=this.getClass().getClassLoader();
		Class clazz4 = cl.loadClass("Classclazz.Student");
		System.out.println(clazz4);
		
		System.out.println(clazz.equals(clazz2));//true
		System.out.println(clazz2.equals(clazz3));//true
		System.out.println(clazz3.equals(clazz4));//true
		
	}
}

·3.1 类的加载

 

·3.1.1 类的加载

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

·加载

就是指将class文件读入内存,并为之创建一个Class对象。

任何类被使用时系统都会建立一个Class对象。

·连接

验证 是否有正确的内部结构,并和其他类协调一致

准备 负责为类的静态成员分配内存,并设置默认初始化值

解析 将类的二进制数据中的符号引用替换为直接引用

·初始化 就是我们以前讲过的初始化步骤

 

·3.1.2 类的初始化时机——扩展知识点

·创建类的实例

·访问类的静态变量,或者为静态变量赋值

·调用类的静态方法

·使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

·初始化某个类的子类

·直接使用java.exe命令来运行某个主类

·3.2 ClassLoader(理解)

·4 通过反射创建运行时类的对象

 

重点: 关于 Class实例与运行类的对象的关系

Class实例:所获取的是 字节码文件对象 ,也叫运行时类 。

运行时类的对象:即运行时类的源文件类的对象,使用 Object obj= clazz.newInstance(); 获取,默认返回值类型为Object类。clazz.newInstance();可用于创建运行时类对象

Object类是任意类的父类,所以可以使用向下转型的方式, Person p=(Person)obj;

另外的方式:构造器创建对象

//创建全类名的运行时类

//通过字节码文件对象获取参构造

//使用构造方法创建对象(多态),同时给属性赋值

 

·5 通过反射获取运行时类的完整结构

Field、Method、Constructor、Superclass、Interface、Annotation

实现的全部接口

所继承的父类

全部的构造器

全部的方法

全部的Field

使用反射可以取得:

·5.1.实现的全部接口

public Class<?>[] getInterfaces()   

确定此对象所表示的类或接口实现的接口。

·5.2.所继承的父类  (掌握)

public Class<? Super T> getSuperclass()

返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。

·5.3.全部的构造器   (掌握

public Constructor<T>[] getConstructors()

返回此 Class 对象所表示的类的所有public构造方法。

public Constructor<T>[] getDeclaredConstructors()

返回此 Class 对象表示的类声明的所有构造方法。

 

Constructor类中:

取得修饰符: public int getModifiers();

取得方法名称: public String getName();

取得参数的类型:public Class<?>[] getParameterTypes();

·5.4.全部的方法     (掌握

public Method[] getDeclaredMethods()

返回此Class对象所表示的类或接口的全部方法

public Method[] getMethods()  

返回此Class对象所表示的类或接口的public的方法

 

Method类中:

public Class<?> getReturnType()取得全部的返回值

public Class<?>[] getParameterTypes()取得全部的参数

public int getModifiers()取得修饰符

public Class<?>[] getExceptionTypes()取得异常信息

·5.5.全部的Field (掌握

public Field[] getFields()

返回此Class对象所表示的类或接口的public的Field。

public Field[] getDeclaredFields()

返回此Class对象所表示的类或接口的全部Field。

 

Field方法中:

public int getModifiers()  以整数形式返回此Field的修饰符

public Class<?> getType()  得到Field的属性类型

public String getName()  返回Field的名称。

·5.7.泛型相关 (掌握

获取父类泛型类型:Type getGenericSuperclass()

泛型类型:ParameterizedType

获取实际的泛型类型参数数组:getActualTypeArguments()

 

·6.怎么实现通过反射调用运行时类的指定属性、指定方法等

·6.1.调用指定方法

1.通过Class类的getMethod(String name,Class…parameterTypes)若方法无形参,则形参列表为null

2.之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。注意 a 、 b两点!!

a.静态方法,直接为形参赋值,Object obj 对象名为null

b.形参为空,直接调用类对象名,Object[] args为null

3.方法声明为private ,在 invoke()前 调用 setAccessible(true)方法获取权限,方可访问private方法

·6.2.调用指定属性

a.public Field getField(String name)   获取公共属性

b.public Field getDeclaredField(String name) 获取私有属性

public void setAccessible(true)访问私有属性时,让这个属性可见。

Field 的赋值与获取:

a.public Object get(Object obj) 取得指定对象obj上此Field的属性内容

b.public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容

 

·7.反射的 应用  : 静态代理   、 动态代理(掌握——代码

·7.1什么是代理

我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托 者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家 做了一次对客户群体的“过滤”。我们把微商代理和厂家进一步抽象,前者可抽象为代理类,后 者可抽象为委托类(被代理类)。通过使用代理,通常有两个优点,并且能够分别与我们提到的 微商代理的两个特点对应起来:

优点一:可以隐藏委托类的实现;

优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

代理模式的元素是:共同接口、代理对象、目标对象。

代理模式的行为:由代理对象执行目标对象的方法、由代理对象扩展目标对象的方法。

代理模式的宏观特性:对客户端只暴露出接口,不暴露它以下的架构。

代理模式的微观特性:每个元由个类构成,如图。

·关于静态代理

首先要思考的一点就是为什么需要实现同一个接口,如果不实现同一个接口,一样可以“代理”功能,所以为什么非要实现同一个接口。我个人认为不实现统一接口的话代理方法有可能会不能完全实现(因为实现接口必须实现它的抽象方法),其次就是方法名称了,已经由接口定义的方法就是目标对象实现了的功能,也算是一种提醒,最后我能想到的就是不实现统一接口的话应该叫做聚合而不是代理。

/**
 * 创建Person接口
 * @author 唯宇空灵
 */
/*
 * 	假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,班长就是学生的代理。
首先,我们创建一个Person接口。这个接口就是学生(被代理类,和班长(代理类的公共接口,他们都上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
 * */
public interface Person {
	
	//上交班费
	void giveMoney();	
}
public class Student implements Person {
	
	String name;
	
	public Student(String name){
		this.name = name;
	}
	@Override
	public void giveMoney() {
		// TODO Auto-generated method stub
		
		System.out.println(name+"上交班费50元");
	}
}
/*
*学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为
*/
public class StudentsProxy implements Person {
	//被代理的学生
	Student stu;
	
	public StudentsProxy(Person stu){
		// 只代理学生对象
		if(stu.getClass()==Student.class){
			this.stu=(Student)stu;
		}
	}

	//代理上交班费,调用被代理学生的上交班费行为
	@Override
	public void giveMoney() {
		// TODO Auto-generated method stub
			
		stu.giveMoney();
	}
}
public class StaticProxyTest {
	
	public static void main(String[] args) {
		
		//被代理的学生张,他的班费上交由代理对象monitor(班长)完成
		Person zhangsan = new Student("张");
		
		//生成代理对象,并将张串给代理对象
		Person monitor =new StudentsProxy(zhangsan);
		
		//班长代理上交班费
		monitor.giveMoney();
	}
}

 

·动态代理出现的原因:

 

静态代理就是写死了在代理对象中执行这个方法前后执行添加功能的形式,每次要在接口中添加一个新方法,则需要在目标对象中实现这个方法,并且在代理对象中实现相应的代理方法,幸而Java有独特的反射技术,可以实现动态代理。

动态代理:使用反射技术获得类的加载器并且创建实例,根据类执行的方法在执行方法的前后发送通知。

具体实现原则:

在代理对象Proxy的新建代理实例方法中,必须要获得类的加载器、类所实现的接口、还有一个拦截方法的句柄。

  在句柄的invoke中如果不调用method . invoke 则方法不会执行。在invoke前后添加通知,就是对原有类进行功能扩展了。

 

  创建好代理对象之后,proxy可以调用接口中定义的所有方法,因为它们实现了同一个接口,并且接口的方法实现类的加载器已经被反射框架获取到了。

 

 

猜你喜欢

转载自blog.csdn.net/weixin_42405670/article/details/82468422