【系统学习】【Java基础】2-Java进阶知识总结-8-反射-动态代理

本文详细介绍了Java反射的基本概念,包括如何获取字节码文件对象的不同方法,以及如何通过反射获取构造方法、成员变量和成员方法。此外,还讲解了动态代理的概念、动态代理的三要素应用示例,以及增强List集合add和remove方法的案例,最后提到了动态代理的注意事项。
摘要由CSDN通过智能技术生成

反射

基本概念

什么是反射

专业的解释(了解一下):

  • 是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
  • 对于任意一个对象,都能够调用它的任意属性和方法;
  • 这种动态获取信息以及动态调用对象方法的功能称为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);
    }
}

动态代理的注意事项

  • 搞清楚:目标对象、代理对象、目标方法
  • 在代理对象中增强目标方法的时候:要注意返回值与目标对象的返回值结果保持一致
  • 在代理对象拦截目标对象方法的时候:两者返回值就没必要一致了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值