• Class类、反射与代理


    Class类、反射与代理:

    RTTI:运行时类型识别,主要有两种方式:

    • 传统的RTTI,在编译的时候就已知所有类型,但是不是所有的class都能在编译时明确;
    • 反射机制,允许在运行市发现和使用类型的信息;

    一、Class类:

    1、概述:

    java用Class类来表示运行时的类型信息,

    • Class类的作用是运行时提供或获得某个对象的类型信息;
    • Class类也是类的一种,只是名字和class关键字高度相似;
    • Class类的对象表示创建的类的类型信息,如果你创建一个a类,编译后就会创建一个包含a类型信息的Class对象;
    • Class类只有私有构造函数,无法通过new创建,只能通过JVM创建;
    • 一个class类只用一个相对应的Class对象,无论创建多少实例,JVM只有一个Class对象;

    2、JVM加载类:

    程序创建第一个对类的静态成员的引用时,JVM的类加载器子系统会将类对应的Class对象加载到JVM中,说明构造器也是类的静态方法,尽管构造器前面并没有static关键字修饰,如果使用new操作符操作一个类的实例对象时,会被当做静态成员的引用;

    • 1、加载:类加载器根据类名找到此类的.Class文件,将该文件包含的字节码加载到内存中,生成class对象;

    • 2、链接:

      • 验证:确保class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全;

      • 准备:正式为类变量(static 成员变量)分配内存并设置初始值(零值)的阶段,这些变量所使用的内存都将在方法区中分配;

      • 解析:虚拟机将常量池中的符号引用替换为直接引用的过程;

    • 3、初始化:类在静态属性和初始化赋值,以及静态快的执行;

    如下图所示:
    请添加图片描述

    3、Class对象的获取方式:

    • 1、Class.forName():
    try {
        Class<?> user = Class.forName("com.test.User");//中间的字符串为类的路径
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 2、getClass():
      需要先new一个对象,通过该对象来获取Class的引用;
    User user = new User();
    Class<? extends com.test.User> aClass = user.getClass();
    
    • 1
    • 2
    • 3、类字面常量:
      通过类字面常量的方式获取Class对象的引用,在编译时会受到检查,相对于前面两种方式更加安全简单;
    Class<User> userClass = User.class;
    
    • 1

    数据类型转换:
    基本的转换思想:只有同类可转换、低级转高级可自动转换、高级转低级需强制转换;

    instanceof:保持了类型的概念,而==或者equals比较的是实际的class对象,没有考虑继承

    4、反射:(灵魂)

    将类的各个组成部分封装成其他对象;

    1、 优点:

    • 1、可以在程序运行中操作对象;
    • 2、解耦,提高程序的可扩展性;

    同一个字节码文件在一次程序运行过程中,只会加载一次,不论通过哪一种方式获取的Class对象都是同一个;

    5、Class类的理解:(三个阶段)

    1、Source源代码阶段:

    写好一个类的代码后,通过javac编译成一个class文件;通过类加载器进入第二阶段

    2、类对象阶段:

    有一个Class类对象,里面封装了成员变量类(数组)、构造方法类(数组)、成员方法类(数组)

    • 成员变量 Field【】fields
    • 构造方法 Constructor 【】 cons
    • 成员方法 Method 【】 methods

    3、Runtime运行时阶段:

    直接创建对象

    6、Class对象功能:

    1、获取成员变量们:

    • getFields():获取该类对象中所有public修饰的成员变量,用一个Field数组来存储;

    • getFields(String name):获取指定名称的成员变量;

    • Field[] getDeclaredFields():获取所有成员变量,不考虑修饰符;

    • field.setAccessible(true):忽略权限修饰符的安全检查;

    @Data
    class User {
        public String name;
        Integer no;
        private String szf;
    }
    public class ClassTest {
        public static void main(String[] args) {
    public class ClassTest {
        public static void main(String[] args) {
            Class<User> userClass = User.class;
            Field[] fields = userClass.getFields();
            for (Field field : fields) {
                System.out.println("getFields 方法 --->"+field);
            }
            try {
                //Field szf = userClass.getField("szf");//会抛出异常
                //System.out.println("getField(\"szf\") 获取 private 修饰的字段 --->"+szf);
                Field szf = userClass.getDeclaredField("szf");
                szf.setAccessible(false);
                System.out.println("getDeclaredField(\"szf\") 获取 private 修饰的字段 忽略检查  --->"+szf);
                Field name = userClass.getField("name");
                System.out.println("getField(\"name\") 获取 public 修饰的字段 --->"+name);
    
                //Field no = userClass.getField("no");//会抛出异常
                //System.out.println("getField(\"no\") 获取 缺省  的字段 --->"+no);
                Field no = userClass.getDeclaredField("no");
                no.setAccessible(false);
                System.out.println("getField(\"no\") 获取 缺省  的字段  忽略检查 --->"+no);
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            }
            Field[] declaredFields = userClass.getDeclaredFields();//强制获取所有字段,无视修饰符
            for (Field declaredField : declaredFields) {
                System.out.println("getDeclaredFields 方法 --->"+declaredField);
            }
        }
    }
    
        }
    }
    
    输出结果:
    getFields 方法 --->public java.lang.String com.test1.User.name
    getDeclaredField("szf") 获取 private 修饰的字段 忽略检查  --->private java.lang.String com.test1.User.szf
    getField("name") 获取 public 修饰的字段 --->public java.lang.String com.test1.User.name
    getField("no") 获取 缺省  的字段  忽略检查 --->java.lang.Integer com.test1.User.no
    getDeclaredFields 方法 --->public java.lang.String com.test1.User.name
    getDeclaredFields 方法 --->java.lang.Integer com.test1.User.no
    getDeclaredFields 方法 --->private java.lang.String com.test1.User.szf
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    获取到成员变量类之后,需要让它和实例化原类绑定,才能回去值和修改值:

    Field a = class.getField("b");
    Person p = new Person();//得到实例化对象
    Object value = a.get(p);//a得到实例化对象,将实例化的p传进去,用Object接受
    a.set(b,"eeee");
    
    • 1
    • 2
    • 3
    • 4

    2、获取构造方法:

    • getConstructor():获取构造器
    Constructor constructor1 = personClass.getConstructor();//有参数构造方法需要获取是添加具体的类对象至参数上,如:personClass.getConstructor(String.class);
    System.out.println(constructor1);
    //创建对象
    Object person1 = constructor1.newInstance();
    System.out.println(person1);
    Object o = personClass.newInstance();
    System.out.println(o);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3、获取成员方法们:

    • getMethod():获取方法,也可以根据名称获取;
    //执行方法,需要传递一个实例化类:
    Method amethod = class.getMethod("a");
    User u = new User();
    amethod.invoke(u);//有参方法添加相关的参数类型
    
    • 1
    • 2
    • 3
    • 4

    二、代理模式:(Proxy、Surrogate)

    1、概述:

    一个人或者一个机构代表另一个人或者另一个机构采取行动,在一些情况下,一个客户不想或者不能直接引用一个对象,而代理对象可以在客户端与目标对象之间起到中介的作用。代理就是为其它对象提供一个代理以控制对某个对象的访问;

    代理模式:代理类和被代理类实现共同的接口,代理类中存在指向被代理类的索引,实际执行中通过调用代理类的方法,实际执行的是被代理类的方法;

    2、优点:

    • 1、隐藏真实目标类的实现;
    • 2、实现客户与真实目标间的解耦,在不修改真实目标类代码的情况下能做一些额外处理;

    3、分类:

    • 静态代理:创建或特定工具自动生成源代码,在进行编译,在程序运行前代理类的class文件已经存在;
    • 动态代理:在程序运行时,运用反射机制动态创建而成,动态代理类的字节码程序运行时由java反射机制动态生成,无需编写源代码;

    4、动态代理分类:

    基于接口的代理( JDK代理 )和基于继承的代理(CGlib代理 );

    一般的动态代理示例:

    //接口
    public interface FontProvider {
        Font getFont(String name);
    }
    //真正提供的类
    public class FontProviderFromDisk implements FontProvider{
        @Override
        public Font getFont(String name) {
            System.out.println("磁盘上的文字");
            return null;
        }
    }
    //代理类
    public class FontProviderProxy implements FontProvider{
    
        private FontProviderFromDisk fontProviderFromDisk;
    
        FontProviderProxy(FontProviderFromDisk frontProviderFromDisk){
            this.fontProviderFromDisk = frontProviderFromDisk;
        }
    
        @Override
        public Font getFont(String name) {
            System.out.println("调用前");
            Font font = fontProviderFromDisk.getFont(name);
            System.out.println("调用后");
            return null;
        }
    }
    //测试类
    public class MyFont {
        public static void main(String[] args) {
            FontProvider provider = new FontProviderProxy(new FontProviderFromDisk());
            provider.getFont("字体");
        }
    }
    测试结果:
    调用前
    磁盘上的文字
    调用后
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    1、JDK代理:

    JDK动态代理主要涉及java.lang.reflect包下的Proxy类和InvocationHandler接口:

    • 1、通过java.lang.reflect.Proxy类来动态生成代理类;

    • 2、代理类要实现InvocationHandler接口;

    • 3、JDK代理只能基于接口进行动态代理;

    //Proxy类的作用就是用来创建代理对象的类,提供了很多方法,常用的就是newProxyInstance方法,这个方法是用来得到动态的代理对象,并接受三个参数:
    public static Object newProxyInstance(ClassLoader loader,Class interfaces,InvocationHandler h) throws IllegalArgumentException;
    loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来生成代理对象进行加载;
    interfaces:一个interface对象的数组,表示将要给我需要代理的对象提供一组什么接口,如果我提供一组接口给他,就会调用这组接口中的方法;
    h:一个InvocationHandler对象,表示的是动态代理在调用方法的时候,会关联哪一个InvocationHandler对象上;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    示例:

    //接口,动态代理的前提
    public interface JDKSubject {
        void request();
        void hello();
    }
    //目标对象
    public class JDKSubjectImpl implements JDKSubject{
        @Override
        public void request() {
            System.out.println("JDKSubjectImpl ------->  request");
        }
    
        @Override
        public void hello() {
            System.out.println("JDKSubjectImpl ------->  hello");
        }
    }
    //代理类
    public class ProxyJDKSubject implements InvocationHandler {
    
        private Object subject;
    
        ProxyJDKSubject(Object subject){
            this.subject = subject;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("start");
            Object result = null;
            try{
                result = method.invoke(subject, args);
            }catch (Exception e){
                System.out.println(e.getMessage());
                e.printStackTrace();
            } finally {
                System.out.println("end");
            }
            return result;
        }
    }
    //测试类
    public class Client {
        public static void main(String[] args) {
            JDKSubjectImpl impl = new JDKSubjectImpl();
            InvocationHandler handler = new ProxyJDKSubject(impl);
            JDKSubject o = (JDKSubject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{JDKSubject.class}, handler);
            o.hello();
            o.request();
        }
    }
    
    测试结果:
    start
    JDKSubjectImpl ------->  hello
    end
    start
    JDKSubjectImpl ------->  request
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    2、CGlib代理模式:

    • CGlib采用ASM字节码生成框架,使用字节码技术生成代理类,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有对父类方法的调用,并顺势加入横切逻辑。CGlib是针对类来实现代理的,原理是对指定的业务类生成一个子类,覆盖其中的业务方法实现代理,**因为采用的是继承,所以不能对final修饰的类进行代理 **,也是通过方法反射调用目标对象的方法;
    • CGlib无需通过接口实现,是通过实现子类的方式完成调用的,Enhance对象把代理对象设置为被代理类的子类来实现动态代理;
      比java反射效率要高;

    示例:

    //目标类
    public class RealSubject {
        public void request(){
            System.out.println("RealSubject is ----> request");
        }
        public void hello(){
            System.out.println("RealSubject is ----> hello");
        }
    }
    //代理类
    public class CGlibProxy implements MethodInterceptor {
    
        private Object target;
    
        public Object getInstance(Object target){
            this.target = target;
            Enhancer enhancer = new Enhancer();//创建加强器,用来创建动态代理类
            enhancer.setSuperclass(this.target.getClass());
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("cglib start--");
            Object result = null;
            try {
                result = methodProxy.invokeSuper(o,objects);
            }catch (Exception e){
                System.out.println(e);
                throw e;
            }finally {
                System.out.println("cglib end--");
            }
            return result;
        }
    }
    //测试类
    public class Client {
        public static void main(String[] args) {
            RealSubject instance = (RealSubject) new CGlibProxy().getInstance(new RealSubject());
            instance.request();
            instance.hello();
        }
    }
    测试结果:
    cglib start--
    RealSubject is ----> request
    cglib end--
    cglib start--
    RealSubject is ----> hello
    cglib end--
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    5、Spring如何选择动态代理:

    • 1、当bean实现接口时,Spring就会使用JDK动态代理;
    • 2、当bean没有实现接口时,Spring使用CGlib的代理实现;
    • 3、可以通过修改配置文件强制使用CGlib代理;

    总结:

    通过简单学习,学会简单使用反射和代理,通常情况下,使用代理的效率会高于使用反射的效率,所以适合使用代理的情况就使用代理会更好!!!

  • 相关阅读:
    Au 入门系列之一:认识 Audition
    详解7道经典指针运算笔试题!
    【考研复试】计算机相关专业面试英语自我介绍范文(一)
    JOSEF约瑟 多档切换式漏电(剩余)继电器JHOK-ZBL1 30/100/300/500mA
    基于人体呼出气体的电子鼻系统的设计与实现
    EasySwipeMenuLayout - 独立的侧滑删除
    浅谈与 DBA 息息相关的 Database Plus 理念
    Python文件高阶操作:复制、删除、移动、压缩文件夹
    【软考软件评测师】第三十三章 数据库系统应用
    AOP面向切面编程
  • 原文地址:https://blog.csdn.net/weixin_44396684/article/details/125424118