Java反射、代理机制

1.什么是反射

官方解释:反射允许对封装类的字段、方法和构造方法的信息进行编程访问。

虚拟机加载类文件后,会在方法区生成一个类对象,包含了类的结构信息,如字段、方法、构造方法等。反射是一种能够在程序运行时动态访问、修改类对象中任意属性的机制(包括private属性)。

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。

如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。

反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。

通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。

获取类对象的四种方式

plain 复制代码
`1.通过对象获取到类对象.getClass();
Class personClass = person.getClass();
2.通过 Class.forName()传入类的全路径获取;装入类,并做类的静态初始化,返回Class的对象
Class personClass = Class.forName("全类名");
3.通过类属性 ClassName.Class获取类对象;
Class personClass = Person.Class;
4.通过ClassLoader.getSystemClassLoader().loadClass("全类名");通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行
`

注意:全类名是包名+类名,一般是程序src目录下的包名+类名。

类对象中描述各个部分的对象:

  1. Class:描述整个类的对象

  2. Constructor:描述构造方法的对象

    java 复制代码
            Class clazz = Class.forName("Person");
            //获取类的所有构造方法
            Constructor[] constructorAll = clazz.getDeclaredConstructors();
            for(Constructor c: constructorAll){
                System.out.println(c);
            }
    
            Constructor constructor1 = clazz.getDeclaredConstructor();
            Constructor constructor2 = clazz.getDeclaredConstructor(String.class);
            System.out.println(constructor1);
            System.out.println(constructor2);
    
            //可以获取构造方法中的一些内容
            int modifiers = constructor1.getModifiers();
            System.out.println(modifiers);
    
            int parameterCount = constructor1.getParameterCount();
            System.out.println(parameterCount);
    
            Parameter[] parameters = constructor2.getParameters();
            for(Parameter p: parameters){
                System.out.println(p);
            }
    
            //使用构造方法创建对象
            Person person = (Person) constructor1.newInstance();
            System.out.println(person);
  3. Field:描述成员变量的对象

    java 复制代码
            Class clazz = Class.forName("Person");
            //获取私有/非私有属性
            Field[] fields = clazz.getDeclaredFields();
            for(Field f: fields){
                System.out.println(f);
            }
    
            Field field = clazz.getDeclaredField("name");
            System.out.println(field);
    
            //获取权限修饰符
            int modifiers = field.getModifiers();
            System.out.println(modifiers);
    
            //获取数据类型
            String value = field.getName();
            System.out.println( value);
  4. Method:描述成员方法的对象。

2.反射的功能

1.获取一个类里面所有的信息和其他业务逻辑。

2.结合配置文件,动态的创建对象并调用方法。

1、Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。

2、Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。

3.序列化、反序列化

Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization是一种将这些字节重建成一个对象的过程。将程序中的对象,放入文件中保存就是序列化,将文件中的字节码重新转成对象就是反序列化。

ObjectOutputStream 类用来序列化一个对象,可将对象序列化到一个文件中。

注意: 当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名。

java 复制代码
`public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //创建文件输出流对象
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\Serializable\\Person.ser");
        //创建序列化对象
        Person person = new Person("name",22,"女","black","boy");
        //创建对象输出流
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        //将对象序列化
        objectOutputStream.writeObject(person);
        //创建文件输入流对象
        FileInputStream fileInputStream = new FileInputStream("D:\\Serializable\\Person.ser");
        //创建对象输入流
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        //反序列化
        Person person1 = (Person) objectInputStream.readObject();
        System.out.println(person1);
    }
}
`

三、序列化和反序列化的注意点:

1.序列化时,只对对象的属性进行保存。

2.当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口。

3.当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化。

4.声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。

5.Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现serializable接口的。

6.序列号id,用于标识序列号对象的版本,在反序列化的过程中,会使用序列化id来检查序列化对象的版本与当前类的版本是否匹配。

功能

  • 对象随着程序的运行而创建,程序结束对象就销毁,序列化可以让对象持久的保持在内存中
  • 可以在进程之间进行对象传输

4.动态代理

无侵入的为对象的方法增加新功能。

动态代理的实现步骤:

  1. 定义接口,并定义要在代理对象中实现的方法(包括返回类型、参数列表、方法名)
  2. 创建一个实现InvocationHandler接口的类,并重写invoke方法,invoke方法在代理对象调用方法时触发,并允许在方法调用前后增加额外的逻辑。
  3. 使用Proxy类创建代理对象java.lang.reflect.Proxy类的静态方法newProxyInstace来创建代理对象,该方法有三个参数。参数1:加载代理类的类加载器;参数2:目标对象实现的接口;参数3:InvocationHandler对象,表示实现invoke方法的对象。
  4. 调用代理对象的方法,当代理对象的方法被调用时,invoke方法会被触发。
java 复制代码
public class Person implements IPerson{
    private String name;

    public Person(){

    }

    public Person(String name){
        this.name = name;
    }

    @Override
    public String eat(String thing){
        System.out.println(this.name+"正在吃"+thing);
        return thing;
    }

    @Override
    public void run(){
        System.out.println(this.name+"正在run");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public interface IPerson {
    //把需要代理的方法定义在接口当中
    public abstract String eat(String name);
    public abstract void run();
}

public class ProxyUtil {
    /**
     *为给定的对象生成一个代理对象
     * @param person
     */
    public static IPerson createProxy(Person person) {
        /**
         * 参数1:指定类加载器,去加载生成的代理类
         * 参数2:指定接口,这个接口用于指定生成的代理长什么,也就是有哪些方法
         * 参数3:用来指定生成的代理对象要干什么事情
         */

        IPerson iPerson = (IPerson) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader()
                , new Class[]{IPerson.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        /**
                         * 参数1:代理的对象
                         * 参数2:要运行的方法
                         * 参数3:调用方法时传递的实参
                         */
                        if("eat".equals(method.getName())){
                            System.out.println("拿筷子");
                            System.out.println("拿碗");
                        }else if("run".equals(method.getName())){
                            System.out.println("带水");
                            System.out.println("穿裤子");
                        }

                        return method.invoke(person,args);
                    }
                });
        return iPerson;
    }
}

实现原理:通过代理类生成字节码文件,使用类加载器将文件加载到内存,最终创建代理对象。

注意:动态的生成字节码文件,是在运行时发生的。

相关推荐
Chris _data3 分钟前
二叉树oj题解析
java·数据结构
牙牙7058 分钟前
Centos7安装Jenkins脚本一键部署
java·servlet·jenkins
paopaokaka_luck16 分钟前
[371]基于springboot的高校实习管理系统
java·spring boot·后端
以后不吃煲仔饭28 分钟前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师29 分钟前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
学点东西吧.30 分钟前
JVM(五、垃圾回收器)
jvm
The_Ticker35 分钟前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
大数据编程之光1 小时前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink
爪哇学长1 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
ExiFengs1 小时前
实际项目Java1.8流处理, Optional常见用法
java·开发语言·spring