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

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

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

相关推荐
努力也学不会java几秒前
【Java并发】深入理解synchronized
java·开发语言·人工智能·juc
TDengine (老段)几秒前
TDengine 数学函数 CEIL 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
LB211222 分钟前
Redis 黑马skyout
java·数据库·redis
豐儀麟阁贵28 分钟前
Java知识点储备
java·开发语言
hrrrrb34 分钟前
【Spring Security】Spring Security 密码编辑器
java·hive·spring
豐儀麟阁贵37 分钟前
2.3变量与常量
java·开发语言
兮动人2 小时前
Eureka注册中心通用写法和配置
java·云原生·eureka
爱编程的小白L4 小时前
基于springboot志愿服务管理系统设计与实现(附源码)
java·spring boot·后端
聪明的笨猪猪6 小时前
Java Redis “持久化”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
勤奋菲菲6 小时前
使用Mybatis-Plus,以及sqlite的使用
jvm·sqlite·mybatis