八、反射与动态代理

本文详细介绍了Java反射机制,包括反射的概念、Class类的使用、获取Class实例的四种方法、类的加载过程以及类加载器的工作原理。此外,还展示了如何通过反射创建对象、获取运行时类的完整结构、调用类的方法和属性以及动态代理的实现。动态代理通过代理对象集中处理方法调用,提供灵活的代码处理方式。
摘要由CSDN通过智能技术生成

八、反射

1、反射的概念
  1. Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API获取任何类的内部信息,并能直接操作任意对象的内部属性及方法
  2. 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构
2、关于Class类
  1. Object类中定义了public final Class getClass()方法,此方法被所有的子类继承
  2. 一个Class对象包含了某个特定的结构
    1. Class本身也是一个类
    2. Class对象只能由系统建立对象
    3. 一个加载的类在jvm中只会有一个Class实例
    4. 一个Class对象就是对应加载到JVM中的一个.class文件
    5. 每个类的实例都会记得自己是由哪个Class实例所构成
    6. 通过Class可以完整的得到一个类中所有被加载的结构
    7. Class是Reflection的根源,针对你任何想要动态加载、运行的类,唯有先获得响应的Class对象
  3. Class类的常用方法
    1. static Class forName(String name) : 返回指定类名 name 的 Class对象
    2. Object newInstance() 调用缺省的构造函数,返回该Class对象的一个实例
    3. getName() 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称
    4. Class getSuperClass() 返回当前Class对象的父类的Class对象
    5. Class[] getInterfaces() 获取当前Class对象的接口
    6. ClassLoader getClassLoader() 返回该类的类加载器
    7. Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组
    8. Field[] getDeclaredFields() 返回Field对象的一个数组
    9. Method getMethod(String naem,Class … paramTypes) 返回一个Method对象,此对象的形参类型为paramTypes
3、获取Class实例的四种方法
  1. 已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高 (.class)

  2. 已知某个类的实例,调用该实例的getClass()方法

  3. 已知一个类的全类名,可通过Class类的静态方法forName(String path),可能会抛出ClassNotFoundException

  4. 通过获取类的加载器,在通过加载器loadClass(“类的全类名”)获取Class实例

        /**
         * 获取Class实例的四种方式
         * 四种方式获取的Class实例都是一样的,证明同一个类在只有一个Class实例
         */
        @Test
        public void test1() throws ClassNotFoundException {
            //方式一:根据类的class属性获取
            Class<Person> clazz1 = Person.class;
            System.out.println(clazz1);//class day01.domain.Person
            //方式二:根据对象的getClass()方法获取
            Person p1 = new Person();
            Class<? extends Person> clazz2 = p1.getClass();
            System.out.println(clazz2);//class day01.domain.Person
            //方式三:根据类的全类名使用Class的静态方法forName("全类名")方法获取(会报类找不到异常)
            Class<?> clazz3 = Class.forName("day01.domain.Person");
            System.out.println(clazz3);//class day01.domain.Person
            //方式四:根据类的加载器ClassLoader获取
            Person p2 = new Person();
            ClassLoader loader1 = p2.getClass().getClassLoader();
            Class<?> clazz4 = loader1.loadClass("day01.domain.Person");
            System.out.println(clazz4);//class day01.domain.Person
            
            ClassLoader loader2 = Person.class.getClassLoader();
            Class<?> clazz5 = loader2.loadClass("day01.domain.Person");
            System.out.println(clazz5);//class day01.domain.Person
        }
    
4、类的加载过程
  1. 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时 数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与
  2. 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程
    1. 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
    2. 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存 都将在方法区中进行分配
    3. 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  3. 初始化:JVM负责对类 进行初始化
5、关于类的加载器(了解)
  1. 类加载器作用是用来把类(class)装载进内存的
  2. JVM规范定义了三种类型的类加载器
    1. 引导类加载器:用C++编写的,是JVM自带的类 加载器,负责Java平台核心库,用来装载核心类 库。该加载器无法直接获取
    2. 扩展类加载器:负责jre/lib/ext目录下的jar包或 – D java.ext.dirs 指定目录下的jar包装入工作库
    3. 系统类加载器:负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工 作 ,是最常用的加载器
6、使用类的加载器来读取配置文件
    @Test
    public void test2() throws IOException {
        //读取配置文件方式一:使用I/O流
        Properties prop1 = new Properties();
        FileInputStream fis = 
            new FileInputStream("src\\main\\resources\\jdbc.properties");
        prop1.load(fis);
        String name1 = prop1.getProperty("name");
        String password1 = prop1.getProperty("password");
        System.out.println("name = " + name1 + ",password = " + password1);
        //读取配置文件方式二:使用类的加载器
        ClassLoader loader = ClassTest.class.getClassLoader();
        InputStream ras = loader.getResourceAsStream("jdbc.properties");
        Properties prop2 = new Properties();
        prop2.load(ras);
        String name2 = prop2.getProperty("name");
        String password2 = prop2.getProperty("password");
        System.out.println("name = " + name2 + ",password = " + password2);
    }
7、通过反射创建运行时类的对象
  1. 方式一:调用Class对象的newInstance()方法创建对象

  2. 方式二:通过获取构造器,调用构造器中的newInstance()方法创建对象

        /**
         * 通过反射创建运行时类的对象
         */
        @Test
        public void test3() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            /*
            方式一:调用Class对象的newInstance() 方法
                注意点:newInstance() 方法实际上还是通过调用运行时类的空参构造器
                所以要对运行时类有以下要求:
                    1.运行时类必须提供空参构造器
                    2.空参的构造器访问权限要足够,能访问的到,通常为public
                补充:javabean要求提供一个public的空参构造器的原因
                    1.便于通过反射,创建运行时类的对象
                    2.便于子类继承此运行时类对象时,默认调用super(),保证父类有此构造器
             */
            Class<Person> clazz1 = Person.class;
            Person p1 = clazz1.newInstance();
            System.out.println(p1);//Person{name='null', age=0}
            /*
            方式二:通过获取构造器来创建运行时类对象
                通过Class对象可以获取到运行时类的构造器,
                获取的构造器通过调用newInstance(Object ... args)方法创建运行时类的对象
                注意:这里面的newInstance() 方法和Class中的方法并不一样,
                    是Constructor中的newInstance()方法
             */
            Class<Person> clazz2 = Person.class;
            Constructor<Person> constructor = clazz2.getConstructor();
            Person p2 = constructor.newInstance();
            System.out.println(p2);
        }
    
8、获取运行时类的完整结构
  1. Field、Method、Constructor、Superclass、Interface、Annotation
  2. 获取实现的全部接口
    • public Class<?>[] getInterfaces()
  3. 获取所继承的父类
    • public Class<? Super T> getSuperclass()
  4. 获取属性(Field)
    • public Field[] getFields() : 获取当前运行时类及其父类中声明为public权限的属性
    • public Field[] getDeclaredFields() : 获取当前运行时类中声明的属性(不包含父类中声明的属性)
    • Field中的常用方法:
      • public int getModifiers() 以整数形式返回此Field的修饰符
      • public Class<?> getType() 返回Field的属性类型
      • public String getName() 返回Field的名称
  5. 获取方法(Method)
    • public Method[] getMethods() : 获取当前运行时类及其父类中声明为public权限的方法
    • public Method[] getDeclaredMethods() : 获取当前运行时类中声明的方法(不包含父类中声明的方法)
    • Method中的常用方法:
      • public Class<?> getReturnType() 取得全部的返回值类类型
      • public Class<?>[] getParameterTypes() 取得全部的参数
      • public int getModifiers() 取得权限修饰符
      • public Class<?>[] getExceptionTypes() 获取异常信息
      • public Annotation[] getAnnotations() 获取全部的注解
      • public int getModifiers() 取得权限修饰符
  6. 获取构造器(Constructor)
    • public Constructor[] getConstructords() : 获取当前运行时类及其父类中声明为public权限的构造器
    • public Constructor[] getDeclaredConstructors() : 获取当前运行时类中声明的构造器(不包含父类中声明的构造器)
    • Constructor中的常用方法:
      • public Class<?>[] getParameterTypes() 取得全部的参数
      • public int getModifiers() 取得权限修饰符
      • public int getModifiers() 取得权限修饰符
      • public Class<?> newInstance(Object … args) 创建一个运行时类的对象
  7. 获取注解(Annotation)
    • public Annotation getAnnotation(Class annotationClass) 获取注解
    • public Annotation[] getAnnotations() 获取全部生命的注解
  8. 获取泛型(GenericType)
  9. 获取所在的包(Package)
9、调用运行时类的指定结构
  1. 操作指定运行时类的属性(Field):

        /**
         * 操作指定运行时类的属性Field
         */
        @Test
        public void test5() throws Exception {
            Class<Person> clazz = Person.class;
            Person person = clazz.newInstance();
            //1.getDeclaredField() 方法获取属性
            Field name = clazz.getDeclaredField("name");
            //2.设置属性
            name.setAccessible(true);//设置之前必须要开启权限
            name.set(person,"张三");
            //3.得到属性
            System.out.println(name.get(person));
        }
    
  2. 操作指定运行时类的方法(Method):

        /**
         * 操作指定运行时类的方法Method
         * 当操作静态方法时,传递 类.class,null都可以
         */
        @Test
        public void test6() throws Exception {
            Class<Person> clazz = Person.class;
            Person person = clazz.newInstance();
            //1.getDeclaredMethod()获取方法
            Method showMethod = clazz.getDeclaredMethod("show", String.class);
            //2.设置是可以访问的
            showMethod.setAccessible(true);
            //invoke() 方法的返回值就是要执行的方法的返回值
            String nation = (String) showMethod.invoke(person, "中国");
            System.out.println(nation);
        }
    
  3. 操作指定运行时类的构造器(Constructor):

        /**
         * 操作运行时类指定构造器
         */
        @Test
        public void test7() throws Exception {
            Class<Person> clazz = Person.class;
            Constructor<Person> constructor = clazz.getDeclaredConstructor();
            Person person = constructor.newInstance();
            System.out.println(person);
        }
    
10、动态代理
  1. 原理:使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上

  2. 动态代理优点:抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中 处理,这样,我们可以更加灵活和统一的处理众多的方法

  3. 静态代理的例子:

    public class ProxyTest {
        public static void main(String[] args) {
            //被代理类对象
            ClothFactory nike = new NikeFactory();
            //代理类对象
            ClothFactory proxyClothFactory = new ProxyClothFactory(nike);
            //代理类执行方法
            proxyClothFactory.product();
    
        }
    }
    //抽象接口
    interface ClothFactory{
        void product();
    }
    //被代理类
    class NikeFactory implements ClothFactory{
        @Override
        public void product() {
            System.out.println("Nike工厂生产衣服");
        }
    }
    //代理类
    class ProxyClothFactory implements ClothFactory{
        private ClothFactory factory;
        public ProxyClothFactory(ClothFactory factory){
            this.factory = factory;
        }
        @Override
        public void product() {
            System.out.println("代理类做一些准备工作");
            factory.product();
            System.out.println("代理类做一些收尾工作");
        }
    }
    
  4. 动态代理:

    public class ProxyTest {
        public static void main(String[] args) {
            //创建被代理类对象
            SuperMan man = new SuperMan();
            //根据被代理类创建代理类对象
            Human proxy = (Human) ProxyFactory.getProxy(man);
            String belief = proxy.belief();
            System.out.println(belief);
            proxy.eat("甜甜圈");
        }
    }
    //抽象接口
    interface Human {
        String belief();
        void eat(String food);
    }
    //被代理类
    class SuperMan implements Human{
        @Override
        public String belief() {
            System.out.println("I believe I can fly");
            return "SuperMan";
        }
        @Override
        public void eat(String food) {
            System.out.println("超人爱吃" + food);
        }
    }
    
    //代理工厂
    class ProxyFactory{
        //调用此方法返回一个代理类对象
        public static Object getProxy(Object obj){//obj:被代理对象
            MyInvocationHandler handler = new MyInvocationHandler();
            handler.setObj(obj);
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                                          obj.getClass().getInterfaces(),handler);
        }
    }
    class MyInvocationHandler implements InvocationHandler{
        private Object obj;//用来保存被代理对象
        public void setObj(Object obj) {
            this.obj = obj;
        }
        @Override
        public Object invoke(Object proxy, 
                             Method method, Object[] args) throws Throwable {
            Object returnVal = method.invoke(obj, args);
            return returnVal;
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值