反射和动态代理
- 反射
- 前言
- 获取class对象的方式
- 反射获取构造方法
- 反射获取成员变量
- 反射获取成员方法
- 实例
- 动态代理
反射
前言
什么是反射?
反射允许对成员变量,成员方法和构造方法的信息进行编程访问。
为什么用反射 / 反射的作用?
可以轻易地获取成员变量、构造方法和成员方法的所有信息。
①获取一个类里面所有的信息,获取到了之后,再执行其他的业务逻辑。
②结合配置文件,动态的创建对象并调用方法。
获取class对象的方式
三种方式
①Class.forName("全类名");
②类名.class
③对象.getClass()
使用时机
这三种方式的使用时机实际上和Java文件的编译过程息息相关:
源代码阶段没有把代码加载到内存当中,只是在硬盘中进行的操作,这一阶段使用第一种方式获得class字节码文件的对象;运行Java代码时,需要将类的字节码文件加载到内存中,这个阶段是加载阶段,该阶段使用第二种方式;在内存中创建某个类的对象时为运行阶段,该阶段使用第三种方式。
具体实现
//1. Class.forName("全类名") 全类名就是:包名+类名
//clazz_one就是Student类的字节码文件
//第一种方式最为常用
Class clazz_one = Class.forName("com.wmy.myreflect1.Student");
System.out.println(clazz_one);
//2. 类名.class
//第二种方式更多的是当做参数来传递
Class clazz_two = Student.class;
System.out.println(clazz_two);
//3. 对象.getClass()
//当我们已经有了这个类的对象时 才可以使用方式三
Class clazz_three = new Student().getClass();
System.out.println(clazz_three);
System.out.println(clazz_one == clazz_two);
System.out.println(clazz_two == clazz_three);
System.out.println(clazz_one == clazz_three);
运行结果
反射获取构造方法
在Java中,存在“万物皆对象”的思想,比如说class字节码文件是Class类的对象,那么构造方法可以看作是Constructor类的对象、成员变量(字段)可以看作是Field类的对象、成员方法可以看作是Method类的对象。
//1. 获取class字节码文件对象
Class clazz = Class.forName("com.wmy.myreflect1.demo02.Student02");
//2. 获取构造方法
System.out.println("获取公共构造方法:");
Constructor[] cons1 = clazz.getConstructors();
for (Constructor con : cons1){
System.out.println(con);
}
System.out.println("分割线-----------------------------------"+"\n");
System.out.println("获取所有构造方法(含私有):");
Constructor[] cons2 = clazz.getDeclaredConstructors();
for (Constructor con : cons2){
System.out.println(con);
}
System.out.println("注:虽然该方法可以获得私有构造方法,但是无法通过这种方式完成私有构造方法new一个对象出来");
System.out.println("分割线-----------------------------------"+"\n");
System.out.println("根据构造方法中参数类型和参数数量的不同 向getDeclaredConstructor中传入参数类型的字节码文件即可获得对应的构造方法:");
Constructor con1 = clazz.getDeclaredConstructor();
Constructor con2 = clazz.getDeclaredConstructor(String.class);
Constructor con3 = clazz.getDeclaredConstructor(int.class);
Constructor con4 = clazz.getDeclaredConstructor(String.class, int.class);
System.out.println(con1);
System.out.println(con2);
System.out.println(con3);
System.out.println(con4);
System.out.println("分割线-----------------------------------"+"\n");
System.out.println("获取权限修饰符");
int modifiers = con4.getModifiers();
System.out.println("1表示公有public 2表示私有private");
System.out.println(modifiers);
System.out.println("分割线-----------------------------------"+"\n");
System.out.println("暴力反射:表示临时取消权限校验");
con4.setAccessible(true);
Student02 stu = (Student02)con4.newInstance("张三", 23);
System.out.println(stu);
反射获取成员变量
//1. 获取class字节码文件的对象
Class clazz = Class.forName("com.wmy.myreflect1.demo03.Student03");
//2. 获取成员变量
System.out.println("获取所有公共成员变量:");
Field[] fields1 = clazz.getFields();
for (Field field : fields1){
System.out.println(field);
}
System.out.println("分割线-----------------"+"\n");
System.out.println("获取所有成员变量(含私有):");
Field[] fields2 = clazz.getDeclaredFields();
for (Field field : fields2){
System.out.println(field);
}
System.out.println("分割线-----------------"+"\n");
System.out.println("获取单个成员变量:");
Field gender = clazz.getField("gender");
System.out.println(gender);
Field name = clazz.getDeclaredField("name");
System.out.println(name);
Field age = clazz.getDeclaredField("age");
System.out.println(age);
System.out.println("分割线-----------------"+"\n");
System.out.println("获取权限修饰符:");
int modifiers = name.getModifiers();
System.out.println(modifiers);
System.out.println("获取成员变量名称:");
String n = name.getName();
System.out.println(n);
System.out.println("获取成员变量类型:");
Class t = name.getType();
System.out.println(t);
System.out.println("获取成员变量记录的值:");
Student03 s = new Student03("张三",23,"男");
name.setAccessible(true);
String value = (String) name.get(s);
System.out.println(value);
System.out.println("修改对象里面记录的值:");
name.set(s,"李四");
System.out.println(s);
反射获取成员方法
//1. 获取class字节码文件对象
Class clazz = Class.forName("com.wmy.myreflect1.demo04.Student04");
//2. 获取成员方法
System.out.println("获取所有方法对象(包含父类中所有的公共方法):");
Method[] methods1 = clazz.getMethods();
for (Method method : methods1){
System.out.println(method);
}
System.out.println("分割线-----------------"+"\n");
System.out.println("获取所有方法对象(不能获取父类的,但是可以获取本类中私有的方法):");
Method[] methods2 = clazz.getDeclaredMethods();
for (Method method : methods2){
System.out.println(method);
}
System.out.println("分割线-----------------"+"\n");
System.out.println("获取指定的单一方法:");
Method m = clazz.getDeclaredMethod("eat", String.class, int.class);
System.out.println(m);
System.out.println("获取方法的修饰符:");
int modifiers = m.getModifiers();
System.out.println(modifiers);
System.out.println("获取方法的名字:");
String name = m.getName();
System.out.println(name);
System.out.println("获取方法的形参:");
Parameter[] parameters = m.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter);
}
System.out.println("获取方法抛出的异常:");
Class[] exceptionTypes = m.getExceptionTypes();
for (Class exceptionType : exceptionTypes) {
System.out.println(exceptionType);
}
System.out.println("获取方法的返回值:");
Student04 s = new Student04();
m.setAccessible(true);
String result = (String)m.invoke(s,"石乐志",996);
System.out.println(result);
实例
需求1
保存信息:对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去。
实现
public class MyReflectDemo {
public static void main(String[] args) throws IllegalAccessException, IOException {
/*
* 对于任意一个对象 都可以把对象所有的字段名和值 保存到文件中去
* */
Student s = new Student("小A" , 23, '女' , 167.5 , "睡觉");
Teacher t = new Teacher("gls" , 1000);
saveObject(t);
}
//把对象的所有成员变量名和值保存到本地文件中
private static void saveObject(Object obj) throws IllegalAccessException, IOException {
//1. 获取字节码文件的对象
Class clazz = obj.getClass();
//创建IO流
BufferedWriter bw = new BufferedWriter(new FileWriter("G:\\JavaWorks_IntelliJIDEA\\myreflect\\src\\main\\java\\com\\wmy\\myreflect1\\record.txt"));
//2. 获取所有的成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
//获取成员变量的名字
String name = field.getName();
//获取成员变量的值
Object value = field.get(obj);
//写出数据
bw.write(name+"="+value);
bw.newLine();
}
bw.close();
}
}
结果
需求2
跟配置文件结合动态创建:反射可以和配置文件结合的方式,动态的创建对象,并调用方法。
实现
prop.properties
classname=com.wmy.myreflect1.case2.Student
method=study
com/wmy/myreflect1/case2/MyReflectDemo.java
public class MyReflectDemo {
public static void main(String[] args) throws IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
/*
* 反射可以和配置文件结合的方式 动态地创建对象 并调用方法
* */
//1.读取配置文件中的信息
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("G:\\JavaWorks_IntelliJIDEA\\myreflect\\prop.properties");
prop.load(fis);
fis.close();
System.out.println(prop);
//2.获取全类名和方法名
String className = (String)prop.get("classname");
String methodName = (String)prop.get("method");
System.out.println(className);
System.out.println(methodName);
//3.利用反射去创建对象并运行方法
Class clazz = Class.forName(className);
//获取构造方法
Constructor con = clazz.getDeclaredConstructor();
Object o = con.newInstance();
System.out.println(o);
//获取成员方法并运行
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
method.invoke(o);
}
}
动态代理
1.什么是动态代理?
动态代理可以无侵入式的给代码增加额外的功能。
2.程序为什么需要代理?
对象如果嫌身上干的事太多的话,可以通过代理来转移部分职责。
对象有什么方法想被代理,代理就一定要有对应的方法。
3.代理长什么样?
代理里面就是对象要被代理的方法。
4.Java通过什么来保证代理的样子?
通过接口保证,后面的对象和代理需要实现同一个接口,接口中就是被代理的所有方法。
5.如何为Java对象创建一个代理对象?
java.lang.reflect.Proxy类
:提供了为对象产生代理对象的方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interface,InvocationHandler h)
参数一:用于指定用哪个类加载器 去加载生成的代理类
参数二:指定接口 这些接口用于指定生成的代理长什么样 也就是有哪些方法
参数三:用来指定生成的代理对象要干什么事情
需求
实现BigStar类中成员方法的动态代理。
步骤
1.创建BigStar类(记得实现步骤2中的接口)
com/wmy/mydynamicproxy/BigStar.java
public class BigStar implements Star{
private String name;
public BigStar(){
}
public BigStar(String name){
this.name = name;
}
//唱歌
@Override
public String sing(String name){
System.out.println(this.name+"正在唱"+name);
return "谢谢大家";
}
//跳舞
@Override
public void dance(){
System.out.println(this.name+"正在让你蠢蠢欲动");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "BigStar{" +
"name='" + name + '\'' +
'}';
}
}
2.封装要代理的方法为接口
com/wmy/mydynamicproxy/Star.java
public interface Star {
//我们可以把所有想要被代理的方法定义在接口当中
//唱歌
public abstract String sing(String name);
//跳舞
public abstract void dance();
}
3.创建ProxyUtil类用于创建代理
com/wmy/mydynamicproxy/ProxyUtil.java
/*
* 类的作用:
* 创建一个代理
* */
public class ProxyUtil {
/*
* 方法的作用:
* 给一个明星的对象创建一个代理
* 形参:
* 被代理的明星对象
* 返回值:
* 给明星创建的代理
* 需求:
* 外面的人想要大明星唱一首歌
* 1.获取代理的对象
* 代理对象 = ProxyUtil.createProxy(大明星的对象);
* 2.再调用代理的唱歌的方法
* 代理对象.唱歌的方法();
* */
public static Star createProxy(BigStar bigStar){
/*
* public static Object newProxyInstance(ClassLoader loader, Class<?>[] interface,InvocationHandler h)
* 参数一:用于指定用哪个类加载器(将字节码文件加载到内存中的工具) 去加载生成的代理类
* 参数二:指定接口 这些接口用于指定生成的代理长什么样 也就是有哪些方法
* 参数三:用来指定生成的代理对象要干什么事情
* */
Star star = (Star)Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),
new Class[]{Star.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
* 参数一:代理的对象
* 参数二:要运行的方法 sing
* 参数三:调用sing方法时,传递的实参
* */
if("sing".equals(method.getName())){
System.out.println("准备话筒 收钱!");
}else if("dance".equals(method.getName())){
System.out.println("准备场地 收钱!");
}
//去找大明星开始唱歌或者跳舞
//代码的表现形式:调用大明星里面唱歌或者跳舞的方法
return method.invoke(bigStar,args);
}
}
);
return star;
}
}
4.创建测试类测试动态代理的过程
com/wmy/mydynamicproxy/Test.java
/*
* 需求:
* 外面的人想要大明星唱一首歌
* 1.获取代理的对象
* 代理对象 = ProxyUtil.createProxy(大明星的对象);
* 2.再调用代理的唱歌的方法
* 代理对象.唱歌的方法();
* */
public class Test {
public static void main(String[] args) {
//1.获取代理的对象
BigStar bigStar = new BigStar("鸡哥");
Star proxy = ProxyUtil.createProxy(bigStar);
//2.调用唱歌的方法
String result = proxy.sing("姬霓太美");
System.out.println(result);
//2.调用跳舞的方法
proxy.dance();
}
}
5.效果