反射和代理

反射

概念

程序运行期间,动态获取/调用类的属性和功能**

理解:

	“类”本身也是一种事物,一类对象,各种“类”,都是“类”类的对象。
	Class类描述了有原生的、我们创建的各种类对象(class对象)。
	class类的属性包括成员方法、成员变量、类名等,所以class类有获取成员方法、成员变量、类名的方法。

Class c1 = Class.forName(“全限定类名”);
Class c2 = 类名.getClass();

方法

1. 获取 构造器对象(间接获取实体类的实例对象)

getConstructors()无参,返回公共的构造器数组
getDeclaredConstructors()无参,返回所有的构造器数组
getConstructor(A,B…)
获取公开的构造函数 返回参数列表为(A,B…)的Constructor对象

	参数列表不写则获取无参构造函数

	constructor 对象是构造函数对象,有构造新对象的方法:newInstance(A,B...)方法 
	参数列表需与获取constructor对象的参数列表类型数量完全一致(但class对象获取constructor对象时,参数时各种类型的class类,如String.class,constructor创建对象时,创建的是真正的实体类实例,参数是具体的值)

getDeclaredConstructor(A,B…)
可获取私有化的构造函数 返回参数列表为(A,B…)的Constructor对象

**暴力反射**
	
参数列表不写则获取无参私有构造函数

由于获取的是私有化构造函数,虽然可以获取,但无法使用。(会报illegalAccessError异常)
需要使用Constructor的setAccessible(true)方法,取消对这个constructor对象的权限检查访问。
破坏了封装性,称为暴力反射
package com.qf.test;

public class User {
	private String name;
	private int age;
	public User(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	
	private User(String name){
		super();
		this.name = name;
	}

	public User() {
	}

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

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

}

package com.qf.study;

import java.lang.reflect.Constructor;

public class fanshe {
	public static void main(String[] args) throws Exception {
		Class u = Class.forName("com.qf.test.User");

		for (Constructor c : u.getConstructors()) {
			System.out.println(c);
		}
		//public com.qf.test.User()
		//public com.qf.test.User(java.lang.String,int)

		for (Constructor c : u.getDeclaredConstructors()) {
			System.out.println(c);
		}
		//public com.qf.test.User()
		//private com.qf.test.User(java.lang.String)
		//public com.qf.test.User(java.lang.String,int)


		Constructor conU = u.getConstructor(String.class,int.class);
		Object obj = conU.newInstance("zs",55);
		System.out.println(obj);
		//User [name=zs, age=55]

		Constructor conU2 = u.getConstructor();
		Object obj2 = conU2.newInstance();
		System.out.println(obj2);
		//User [name=null, age=0]
		

		Constructor conU3 = u.getDeclaredConstructor(String.class);
		conU3.setAccessible(true);
		//如果不setaccessible,出现java.lang.IllegalAccessException
		Object obj3 = conU3.newInstance("ls");
		System.out.println(obj3);
		//User [name=ls, age=0]

		
	}

}

2. 直接获取 实例对象

class对象.newInstance()

前提
class对象的newinstance方法是 无参 的,只能创建无参构造函数;
无法调用私有 构造函数创建对象(class对象没有setAccessible暴力反射的方法)

Object obj4 = u.newInstance();
System.out.println(obj4);

3. 获取成员变量 Field对象

class对象.getDeclaredFiled(String参数即变量名)
返回指定的field对象(无论是否公开)

class对象.getDeclaredFileds(无参)
返回所有的filed对象

class对象.getFiled(String参数即变量名)
返回指定的公共的field对象

class对象.getFields(无参)
返回所有的公共filed对象

		//获取指定公共成员变量
		Field field = u.getField("id");
		System.out.println(field);
		//public int com.qf.test.User.id

		//获取指定私有成员变量(调用错误方法,获取不到)
		Field field2 = u.getField("name");
		System.out.println(field2);
		//java.lang.NoSuchFieldException,name是私有属性,读不到
		

		//获取指定私有成员变量
		Field field3 = u.getDeclaredField("name");
		System.out.println(field3);
		//private java.lang.String com.qf.test.User.name

		//获取所有公共成员变量,打印地址
		Field[] fields2 = u.getFields();
		System.out.println(fields2);
		//[Ljava.lang.reflect.Field;@4e25154f
		

		//对所有公共成员变量遍历获得全限定类名
		for (Field f2 : fields2) {
			System.out.println(f2);
		}
		//public int com.qf.test.User.id
		
		

		//获取所有成员变量并遍历
		Field[] fields3 = u.getDeclaredFields();
		for (Field f3 : fields3) {
			System.out.println(f3);
		}
		//private java.lang.String com.qf.test.User.name
		//private int com.qf.test.User.age
		//public int com.qf.test.User.id

field对象.set(实例对象,值)
为公开成员变量赋值

field对象.setAccessible(true)
暴力反射,为私有成员变量设置是否判断访问权限

field对象.set(实例对象,值)
为私有成员变量赋值

public static void main(String[] args) throws Exception {
		Class u = Class.forName("com.qf.test.User");
		Object  obj = u.newInstance();
		//获取指定的公共的成员变量并赋值
		Field f3 = u.getDeclaredField("id");
		f3.set(obj, 234);
		System.out.println(obj);
		//User [name=null, age=0, id=234]

		Field f1 = u.getDeclaredField("name");
		Field f2 = u.getDeclaredField("age");
		f1.setAccessible(true);
		f2.setAccessible(true);
		f1.set(obj, "张三");
		f2.set(obj, 22);
		System.out.println(obj);
		//User [name=张三, age=22, id=234]
	}

file对象.get(实例对象)
获取属性值

属性是无法使用实例对象直接调出的,因为实例对象使用了多态,是object类型

field对象在创建时已经就是 从实体类的class对象获取的属性对象,所以其所属的类和数据类型已经是固定的。只需确认要获取的是哪个实例的值即可。
//获取属性值1:向下强转(不推荐)
		User u1 = (User)obj;
		int a  = u1.getAge();
		System.out.println(a);
		//22
		
//获取属性值2:用field对象直接获取
		System.out.println(	f2.get(obj));
		//22

优点
所有的对象都是object类(多态)
所有属性都是field
如果需要改动对象类型,只需要改动class.forname()里面的全限定类名即可。

4. 获取成员方法 Method对象

class对象.getMethods()
获得所有公共方法,包括从父类继承的(受保护的和私有的和默认的都拿不到)

class对象.getDeclaredMethods()
获取所有本类方法(包括本类私有的但不包括父类非公有的)

class对象.getMethod(“方法名”,参数类型.class…)
获取指定公开方法

class对象.getDeclaredMethod(“方法名”,参数类型.class…)
获取指定方法(公开+非公开)

method对象.invoke(对象名,参数值…)

Class u = Class.forName("com.qf.test.User");
Object obj  = u.newInstance();
Method m1 = u.getDeclaredMethod("play",int.class);
m1.setAccessible(true);
m1.invoke(obj, 20);

面向对象
创建类型(实体类),创建方法和属性
创建对象,赋值
调用属性和方法

反射

  1. 拿到实体类的class对象
  2. 拿到class对象的method、field、constructor、object实例对象
  3. 使用method对象/field对象/constructor对象的invoke(对象,参数值…),set/get(对象,参数值…),newInstance(参数值…)方法,对具体某个实例对象进行创建/属性赋值/方法调用

反射+properties

properties文件

className=com.qf.study.Emp
method=work

## properties文件的注释方式:##
##className=com.qf.study.Boss
##method=manage

1.不能不换行!
2.多个键相同会被最后一个覆盖!(map特性)

java代码

package com.qf.study;

public class Emp {
	public int work(int days) {
		System.out.println("拼命工作"+days+"天");
		return days;
	}

}

package com.qf.study;

public class Boss {
	public int manage() {
		System.out.println("老板管人");
		return 100;
	}

}
package com.qf.study;

import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.Properties;

//根据反射和配置文件动态创建对象调用方法
public class RelectTest {
	public static void main(String[] args) throws Exception {

		//如果写盘符,就写完整绝对路径名
		//不写盘符,就从当前项目的根目录(src)往下查找
		FileReader fr1 = new FileReader("my.properties");
	
		//创建properties对象
		Properties pro = new Properties();
		//加载一个输入流来读取properties文件中的键值对
		pro.load(fr1);
		//流对象加载完了就可以关闭(能走到这一句就证明已经加载完毕了)
		fr1.close();
		//getProperty方法根据键名获取值
		String className = pro.getProperty("className");
		String method = pro.getProperty("method");
		//根据键值对,利用反射,创建对象并调用方法
		//创建配置文件中的class对象
		Class c1 = Class.forName(className);
		//创建该类的实例对象
		Object o1 = c1.newInstance();
		//创建该类的指定方法对象
		Method oM1 = c1.getMethod(method);
		//执行方法并获取返回值
		Object i = oM1.invoke(o1,20);
		System.out.println(i);
		//好处:动态读取配置文件来创建对象
		//程序不断运行期间,如需更改对象或方法,无需关程序改源码,改properties即可
	}

}

重载方法怎么办(properties文件怎么配置?键不能重复)
方法含参怎么办

动态代理

应用场景:对实体类的某些核心方法X()进行扩展(不改动源码,只在前后拓展)

个人理解:

  1. 前提
    被代理的对象,其被代理的功能X()必须是规范的(定义一个接口中包含X()方法,被代理的对象所属类应继承该接口)

  2. 创建代理器对象

     代理器对象如何实现代理(通过反射获取要执行的方法)
    
  3. 将代理器对象转型为接口

     不能转型为被代理的类
     因为代理器对象只代理了接口中定义的功能,不包括对象所属的类的其他特有属性
    
  4. 调用转型得到的接口对象的X()(即需要被代理的方法)实现代理执行

    因为接口对象是转型来的,所以实际执行的是代理器的对应方法
    
    代理器的对应方法怎么来的:
    	代理器的三个参数,大致分别用于加载被代理的类,获得接口的class对象数组,**利用反射执行方法**。
    	第三个参数invocationHandler中的参数:反射的实例对象,参数(要调用的方法全名),参数(调用方法时的参数值)
    
  5. invocationHandler中重写的invoke方法中,可以添加功能扩展,在method这个方法执行前后添加功能

package com.qf.study;

public interface BossInterface {
	 int manage() ;
	 int giveSalary(int i) ;
}

package com.qf.study;

public class Boss implements BossInterface{
	public int manage() {
		System.out.println("老板管人");
		return 100;
	}
	
	public int giveSalary(int i) {
		System.out.println("老板发工资"+i+"元");
		return i;
	}

}

package com.qf.study;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class RelectTest {
	public static void main(String[] args)  {
		Object obj = Proxy.newProxyInstance(//新建一个代理对象(代理的是对象的某个核心功能)
				Boss.class.getClassLoader(),//被代理的类的类加载器 
				new Class[]{BossInterface.class}, //被代理的对象所继承的接口的class对象的数组(这个接口描述了需要被代理功能,对象的功能来源于此)
				new InvocationHandler() {//需求一个invocationHandler对象,用于方法的执行。因为invocationHandler是只有一个抽象函数的函数式接口,适合用匿名内部类方式创建子类对象
					
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						//proxy:Proxy对象,即代理(者)对象
						//后续调用什么方法,method就是什么方法public abstract java.lang.String com.qf.study.EmpInterface.write() 
//						System.out.println(method);
						//后续方法调用时传的什么参数,args的内容就是什么:无参结果是null;有参结果是数组的地址(查看数组内容就是传的参数值)
//						System.out.println(Arrays.toString(args));
						//代理者可以进行一些特定的拓展(此处是 代理发工资,扣掉100元)
						args[0] =(Integer)args[0] -  100;
						//代理者的执行,需要执行调用的方法(传递被代理的对象和参数)
						Object result =method.invoke(new Boss(), args);
						//这个invoke的返回必须与调用的方法的返回类型一致,否则会类型不匹配异常/空指针异常(该返回没有返回)
						return result;
					}
				});
		BossInterface boss = (BossInterface)obj;
		boss.giveSalary(100);//结果是老板发工资0元
//		e实例是由动态代理对象转型来的,调用任何方法都要先创建动态代理对象(obj),调用的方法实际执行的也是右边obj的方法
//		obj是从proxy.newinstance创建而来,会从被代理的类中加载类的信息(包括方法)
//		找到要执行的方法后,使用自己的invocationHandler对象的invoke方法,根据执行自己找到的被代理的类方法和传递来的参数执行
//		invocationHandler对象的invoke方法是重写的,在执行method的invoke方法前后可以进行代码的增强(适用于不想改动接口或类的时候)
		
	}

}

  • 优点
    目标类实现接口,代理类无需实现接口;故目标类的功能扩展也不影响代理类
  • 缺点
    性能一般(因为使用反射)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值