文章目录
反射
基本概念
什么是反射
专业的解释(了解一下):
- 是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意属性和方法;
- 这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
通俗的理解(掌握)
-
利用反射创建的对象可以无视修饰符调用类里面的内容
-
可以跟配置文件结合起来使用,把要创建的对象信息和方法写在配置文件中
- 读取到什么类,就创建什么类的对象
- 读取到什么方法,就调用什么方法
- 此时当需求变更的时候不需要修改代码,只要修改配置文件即可
字节码文件和字节码文件对象的理解
概念 | 理解 |
---|---|
java文件 | 编写的java代码,后缀为.java |
字节码文件 | 编译java文件后生成的class文件(在硬盘上真实存在) |
字节码文件对象 | class文件加载到内存后,虚拟机自动创建出来的对象(对象包含:构造方法、成员变量、成员方法) |
反射要获取的是字节码文件对象,这个对象在内存中是唯一的
获取【字节码文件对象】的三种方法
获取手段 | |
---|---|
根据Class类的静态方法获取(用的最多) | Class.forName(“全类名”) |
根据要获取类名的class属性获取 | 类名.class |
根据类的实例对象获取 | 实例对象.getClass() |
// 1、根据Class类的静态方法获取(用的最多)
// 全类名 = 包名 + 类名
Class clazz1 = Class.forName("com.itheima.reflectdemo.Student");
// 2、根据要获取类名的class属性获取
Class clazz2 = Student.class;
System.out.println(clazz1 == clazz2);//true,因为class文件在硬盘中是唯一的,所以,当这个文件加载到内存之后产生的对象也是唯一的
// 3、根据类的实例对象获取
Student s = new Student();
Class clazz3 = s.getClass();
System.out.println(clazz1 == clazz2);//true
System.out.println(clazz2 == clazz3);//true
利用获取到的字节码文件对象,可以获取类的相关信息:构造方法、成员变量、成员方法等
【根据字节码文件对象】获取构造方法
获取构造方法
方法名 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 获得所有的构造(只能public修饰) |
Constructor<?>[] getDeclaredConstructors() | 获得所有的构造(包含private修饰) |
Constructor getConstructor(Class<?>… parameterTypes) | 获取指定构造(只能public修饰) |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 获取指定构造(包含private修饰) |
public class ReflectDemo2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1、获得整体(class字节码文件对象)
Class clazz = Class.forName("com.itheima.reflectdemo.Student");
//2-1、获取所有(public)构造方法
Constructor[] constructors1 = clazz.getConstructors();
for (Constructor constructor : constructors1) {
System.out.println(constructor);
}
System.out.println("=======================");
//2-2、获取所有构造方法(共有和私有的都算上)
Constructor[] constructors2 = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors2) {
System.out.println(constructor);
}
System.out.println("=======================");
//2-3、获取指定的空参构造(public)
Constructor con1 = clazz.getConstructor();
System.out.println(con1);
//2-4、获取指定的带参构造(public)
Constructor con2 = clazz.getConstructor(String.class,int.class);
System.out.println(con2);
System.out.println("=======================");
//2-5、获取指定的空参构造(共有+私有都可以获取到)
Constructor con3 = clazz.getDeclaredConstructor();
System.out.println(con3);
System.out.println(con3 == con1); // false,每一次获取构造方法对象的时候,都会新new一个。
//2-6、获取指定的空参构造(共有+私有都可以获取到)
Constructor con4 = clazz.getDeclaredConstructor(String.class);
System.out.println(con4);
}
}
根据获取到的构造方法创建对象
根据空参构造方法创建对象
//1.获取整体的字节码文件对象
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
//2.获取空参的构造方法
Constructor con = clazz.getConstructor();
//3.利用空参构造方法创建对象
Student stu = (Student) con.newInstance();
System.out.println(stu);
根据带参构造方法创建对象
- 如果获取到的构造方法是私有的,需要临时修改访问权限,否则无法使用该构造方法创建对象
//1.获取整体的字节码文件对象
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
//2.获取有参构造方法
Constructor con = clazz.getDeclaredConstructor(String.class, int.class);
//3.临时修改构造方法的访问权限(暴力反射)
con.setAccessible(true);
//4.直接创建对象
Student stu = (Student) con.newInstance("zhangsan", 23);
System.out.println(stu);
【根据字节码文件对象】获取成员变量
获取成员变量
方法名 | 说明 |
---|---|
Field[] getFields() | 返回所有成员变量对象的数组(只能拿public的) |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组,存在就能拿到 |
Field getField(String name) | 返回单个成员变量对象(只能拿public的) |
Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到 |
public class ReflectDemo4 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
//获取成员变量对象
//1.获取字节码文件对象
Class clazz = Class.forName("com.itheima.reflectdemo.Student");
//2.获取成员变量的对象(Field对象)只能获取public修饰的
Field[] fields1 = clazz.getFields();
for (Field field : fields1) {
System.out.println(field);
}
System.out.println("===============================");
//获取成员变量的对象(public + private)
Field[] fields2 = clazz.getDeclaredFields();
for (Field field : fields2) {
System.out.println(field);
}
System.out.println("===============================");
//获得单个成员变量对象
//如果获取的属性是不存在的,那么会报异常
//Field field3 = clazz.getField("aaa");
//System.out.println(field3);//NoSuchFieldException
Field field4 = clazz.getField("gender");
System.out.println(field4);
System.out.println("===============================");
//获取单个成员变量(私有)
Field field5 = clazz.getDeclaredField("name");
System.out.println(field5);
}
}
根据获取到的成员变量(Field):获取实例化对象的成员值、修改实例化对象的成员值
方法 | 说明 |
---|---|
void set(Object obj, Object value) | 赋值 |
Object get(Object obj) | 获取值 |
public class ReflectDemo5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
// 实例化对象
Student s = new Student("zhangsan",23,"广州");
Student ss = new Student("lisi",24,"北京");
//1.获取字节码文件对象
Class clazz = Class.forName("com.itheima.reflectdemo.Student");
//2.获取name成员变量
Field field = clazz.getDeclaredField("name"); //field就表示name这个属性的对象
field.setAccessible(true); //临时修改访问权限
//3.修改实例化对象name属性的值
field.set(s,"wangwu"); // 参数1:实例化对象;参数2:要将属性设置的值
//4、获取实例化对象name属性的值
String result = (String)field.get(s); // 参数1:实例化对象
System.out.println(s);
System.out.println(ss);
}
}
【根据字节码文件对象】获取成员方法
获取成员方法
方法名 | 说明 |
---|---|
Method[] getMethods() | 返回所有成员方法对象的数组(只能拿public的) |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,存在就能拿到 |
Method getMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象(只能拿public的) |
Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象,存在就能拿到 |
public class ReflectDemo6 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.获取class对象
Class<?> clazz = Class.forName("com.itheima.reflectdemo.Student");
//2.获取方法
//getMethods可以获取父类中public修饰的方法
Method[] methods1 = clazz.getMethods();
for (Method method : methods1) {
System.out.println(method);
}
System.out.println("===========================");
//获取所有的方法(包含私有)
//但是只能获取自己类中的方法
Method[] methods2 = clazz.getDeclaredMethods();
for (Method method : methods2) {
System.out.println(method);
}
System.out.println("===========================");
//获取指定的方法(空参)
Method method3 = clazz.getMethod("sleep");
System.out.println(method3);
Method method4 = clazz.getMethod("eat",String.class); // 参数1:方法名称;后续参数:方法的参数类型
System.out.println(method4);
//获取指定的私有方法
Method method5 = clazz.getDeclaredMethod("playGame");
System.out.println(method5);
}
}
运行获取到的成员方法
package com.itheima.a02reflectdemo1;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectDemo6 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//1.获取字节码文件对象
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
//2.获取一个对象,需要用这个对象去调用方法
Student s = new Student();
//3.获取一个指定的方法
Method eatMethod = clazz.getMethod("eat",String.class); // 参数1:方法名;参数后续:参数列表
//运行
//eatMethod:方法对象
//s:执行方法的实例化对象
//"重庆小面":方法的具体参数
//result:方法的返回值
String result = (String) eatMethod.invoke(s, "重庆小面");
System.out.println(result);
}
}
动态代理
什么是动态代理?
动态代理就是:
在程序运行期间,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术
- 这句话解释了动态代理的一个好处:可以给目标对象的方法进行增强
在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法
- 这句话解释了动态代理的另一个好处:增强的手段是无侵入式的,因为目标对象不变
可以理解为运行期间,对目标对象中某方法的动态拦截,在拦截某方法的前后执行相应的功能操作
动态代理三要素
- 目标对象:真正干活的对象
- 代理对象:用来代理目标对象的对象
- 目标方法:代理对象中要调用的目标对象的方法,可以在代理对象中对该方法完成增强
示例:请明星演唱表演
需求:
- 明星会唱歌、跳舞等才艺表演
- 一些地方或人想请明星进行表演,明星助理就负责安排相关活动
涉及到的动态代理三要素:
- 目标对象:明星
- 代理对象:明星助理
- 目标方法:唱歌、跳舞
目标对象:明星
- 明星接口:接口里面的方法是明星应该具备的基本才艺
public interface Star {
//唱歌
public abstract String sing(String name);
//跳舞
public abstract void dance();
}
- 明星对象:一个明星必须会基本明星该有的所有才艺(明星对象需要实现明星接口里面所有的方法)
public class BigStar implements Star {
private String name;
// 空参、带参构造方法、tostring()方法:省略
//唱歌
@Override
public String sing(String name){
System.out.println(this.name + "正在唱" + name);
return "谢谢";
}
//跳舞
@Override
public void dance(){
System.out.println(this.name + "正在跳舞");
}
// 明星的其他才能:省略
}
代理对象:助理协助明星,完善明星表演的前后期工作
// 动态代理的工具类
public class ProxyUtil {
// 创建明星对象的代理类
public static Star createProxy(BigStar bigStar){
// java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法,该方法参数列表、返回值内容如下:
// public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
// 参数一:用于指定用哪个类加载器,去加载生成的代理类
// 参数二:指定接口,目标对象的接口
// 参数三:用来指定生成的代理对象要干什么事情:匿名内部类写法
Star star = (Star) Proxy.newProxyInstance(
bigStar.getclass().getClassLoader(),
bigStar.getClass().getInterfaces(),
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;
}
}
完成明星演唱工作
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);
}
}
【扩展】方法拦截
在这个故事中,经济人作为代理,如果别人让邀请大明星去唱歌,打篮球,经纪人就允许。但是如果别人让大明星去扫厕所,经纪人就要加以拦截
public class ProxyUtil {
public static Star createProxy(BigStar bigStar){
Star star = (Star) Proxy.newProxyInstance(
bigStar.getclass().getClassLoader(),
bigStar.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("cleanWC".equals(method.getName())){
System.out.println("拦截,不调用大明星的方法");
return null;
}
//如果是其他方法,正常执行
return method.invoke(bigStar,args);
}
}
);
return star;
}
}
示例:增强List集合的add、remove方法
public class MyProxyDemo1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
List proxyList = (List) Proxy.newProxyInstance(
list.getclass().getClassLoader(),
list.getclass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//对add方法做一个增强,统计耗时时间
if (method.getName().equals("add")) {
long start = System.currentTimeMillis();
method.invoke(list, args);
long end = System.currentTimeMillis();
System.out.println("耗时时间:" + (end - start));
return true; //需要进行返回,返回值要跟真正增强或者拦截的方法保持一致
}else if(method.getName().equals("remove") && args[0] instanceof Integer){
System.out.println("拦截了按照索引删除的方法");
return null;
}else if(method.getName().equals("remove")){
System.out.println("拦截了按照对象删除的方法");
return false;
}else{
System.out.println("如果当前调用的是其他方法,我们既不增强,也不拦截")
method.invoke(list,args);
return null;
}
}
}
);
proxyList.add("aaa");
proxyList.add("bbb");
proxyList.add("ccc");
proxyList.add("ddd");
proxyList.remove(0);
proxyList.remove("aaa");
//打印集合
System.out.println(list);
}
}
动态代理的注意事项
- 搞清楚:目标对象、代理对象、目标方法
- 在代理对象中增强目标方法的时候:要注意返回值与目标对象的返回值结果保持一致
- 在代理对象拦截目标对象方法的时候:两者返回值就没必要一致了