zoukankan      html  css  js  c++  java
  • JAVA反射机制与动态代理

    承接上篇博客里面,类加载器把类加载把类加载进内存,同时创建出了一个唯一的Class对象,其实它本质上就是一个java类,只不过功能挺特殊的---说白了,就像当初,数据多了,用集合装,还多?写个类,用对象装, 类可以对一系列数据的描述,然后谁描述类呢-->Class里面有类的基本信息 1.类的属性:Field 2. 方法:Method 3 .构造器:Constructor(这三个属性都有自己的对应类)

    都说java是一门动态语言,怎么着他就动态了呢? reflection反射! 通过反射,我们可以动态的获取类的实例,平时我们自己写东西,用到反射的几率几乎没有,但是像Spring,Hibernate,Mybatis等等等等这些框架的底层到处都是反射

    初学spring时接触下面这个配置, Spring的控制反转,IOC,把对象交给Spring创建,依稀记得当初写一大堆xml配置文件,那时候很蒙!其中,把对象的创建权反转给Spring的时候,需要这样:

     <bean id="小写类名"  , class="带包名的全类名">
       <property name="XXX" value="XXX"/>
     </bean>
    

    她其实就是在通过反射,帮我们创建对象


    言归正传,下面分如下几步展开

    1.获取class对象的三种方式

    1. 抛弃 new , 动态构建任意一个类的对象
    2. 获取一个类的任意成员变量和方法,不论是否被private修饰
    3. 调用任意一个对象的方法
    4. 跨越泛型检验
    5. 动态代理
    6. 反射&泛型

    一: 获取class对象的三种方式

    图1

    • 源代码阶段(使用频率最多)
    //1 注意,当我用ide 快捷生成 左半部分的时候,   Class<?> forName  即便<?>是问号,他也是泛型的
    //Class<?> forName = Class.forName("com.atGongDa.entity.person");
    
    //2 然后我们可以这样
    // Class<person> personClass = (Class<person>) Class.forName("com.atGongDa.entity.person");
    
    //3 但是,在开发的时候,是不会写上泛型的,因为在开发框架的人们,在写它的时候,是不知道,程序员要传递进来什么类型
    Class forName = Class.forName("com.atGongDa.entity.person");
    
    /*
    newInstance() 创建对象的实例, 注意这里获取的对象,和直接new 出来的对象是不一样的,不知直接调用类里面的方法
    调用的是无参的 构造器, 这也是为什么要习惯性的写上一个无参的构造器,留着给反射用!!!
    */
    Object o = forName.newInstance();
    System.out.println(o);
    
    
    • 字节码阶段
    类名.class
    
    • 对象阶段
    对象.getClass()
    

    这个意义不太大, 我们都拿到对象了, 它是啥类型,有什么,点进去看就行了,还getClass()干啥,南辕北辙

    二. 抛弃 new , 动态构建任意一个类的对象,获取它的方法并执行

     //获取Class对象,它里面它里面封装着类的属性和方法
     Class clazz= Class.forName("com.atGongDa.entity.person");
     // 创建类实例,不过直接使用它没意义(因为我最终的目的还是使用对象里面的方法和字段)
     Object o = clazz.newInstance();
    

    获取类中的方法(包含它父类的方法),不包括获取不到私有的

    返回值: Method对象数组,里面的method对象是我们上面person中方法的抽象实体,类方法或实例方法(包括抽象方法)如果你去看它的API会发现它里面有一系列的方法,比如和它代表的方法执行

     Method[] methods = clazz.getMethods();
    

    获取所有的方法(包含私有的)

    Method[] declaredMethods = clazz.getDeclaredMethods();
    

    获取指定的方法

    Method method = clazz.getDeclaredMethod("text");
    

    暴力反射,执行方法

        //执行方法: Method对象执行方法,
        // 两个参数
        //  谁的方法? 创建实例
        //  获取method对象
        // Method对象调用invoke()
        Object o = clazz.newInstance();
        method.setAccessible(true);
       method.invoke(o,null); ///参数就是原方法的参数
    

    三. 获取类的任意字段

    • Field 也是个类,提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
    @Test
    public void textField() throws NoSuchFieldException, IllegalAccessException {
        //获取所有字段
        Field[] fields = person.class.getDeclaredFields();
        for (Field f:fields) {
            System.out.println("字段=="+f.getName());
        }
        //获取指定名字的字段
        Field field = person.class.getDeclaredField("neme");
        System.out.println(field.getName());
    
       /* Field neme = person.class.getField("neme");
        System.out.println(neme.getName());*/
    
       //获取指定对象的Field值
        Object p  = new person("张武",11);
        //暴力反射
        field.setAccessible(true);
        Object o = field.get(p);
        System.out.println(o);
    
        //给指定对象的 当前字段设置值
        field.set(p,"haha"); //filed 字段是上面的neme
    }
    
    

    四. 工具方法

    • 很多框架底层都使用类似的方法,去配置文件里面读取我们的配置,然后把需要的截取出来,当作参数,传递到方法执行
    1. 对象+方法名+可变参数
    /* @Param obj 对象
    * @Param methodName 方法名
    * @Param avgs 方法参数,可变参数
    *
    * 要求,执行执行指定对象的指定方法
    * */
    @Test
    public Object invoke1(Object obj,String methodName,Object ... avgs) throws InvocationTargetException, IllegalAccessException {
    
        //创建 长度为avgs   类型为Class  的数组, (可变参数可以看作一个数组)
        Class[] classes = new Class[avgs.length];
        //循环 avgs.length 次 , 把每个入参的Class映射,传递进 Class数组中  ,得到的这个Class类型数组,就是getDeclaredMethod需要的第二个入参
        for (int i=0;i<avgs.length;i++){
           classes[i] = avgs[i].getClass();
        }
        Class clazz = obj.getClass();
        /*
        @CallerSensitive
        public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
         可以看到他的第二个参数是Class类型的,但是具体是啥类型,不知道,所以使用上面啊那个for循环推到
     * */
        try {
            Method method = clazz.getDeclaredMethod(methodName,classes);
            //若 方法是私有的可访问
            method.setAccessible(true);
            return method.invoke(obj,avgs);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return  null;
    }
    

    2, 带包名的全路径+方法名+可变参数

    /*
     * 工具方法
     * @Param String objName 对象
     * @Param methodName 方法名
     * @Param avgs 方法参数,可变参数
     *
     * 要求,执行执行指定对象的指定方法
     * */
    @Test
    public Object invoke2(String objName,String methodName,Object ... avgs) throws Exception{
        Object o1 = Class.forName(objName).newInstance();
       // return  invoke1(o1,methodName,avgs);
        Class[] classes = new Class[avgs.length];
        for(int i=0;i<avgs.length;i++){
            classes[i]=avgs[i].getClass();
        }
        //获取Method 对象
       Method declaredMethod = o1.getClass().getDeclaredMethod(methodName, classes);
        System.out.println("方法名=="+declaredMethod.getName());
        //暴力反射
        declaredMethod.setAccessible(true);
       return declaredMethod.invoke(o1, avgs);
    
    }
    
    1. 如果本类无传递进来方法名,去父类中查找
     //在 本类中查找 ,找不到,去父类中查找
        public Method getMethod(Class clazz, String name, Class... avgs){
            for(;clazz!=Object.class;clazz = clazz.getSuperclass()){
                try {
                  return  clazz.getDeclaredMethod(name,avgs);
                } catch (Exception e) {
                    e.printStackTrace();
                }
    
            }       return null;
        }
    

    五.动态代理

    • 为啥要代理?

    代理的目的就是给现有的类的对象的功能进行增强, 代理对象 = 原对象 + 增强方法

    • 难道只有代理才能增强吗?

    当然不是,如果这个类是我们自己写的,手上有整套源码,我们直接改源代码就好了,问题是,大多数情况下,类是我们继承来的,没有源码让咱改,想增强?只能代理

    • 框架中的典型代表

    Spring AOP 的面向切面编程就把它使用的淋漓尽致, 来多少对象我不关心,让他们排队站好.我可以在他们身上横切一刀, 给他们集体增强

    注意点:

    • 为谁代理? --> 我们自己创建被代理对象(这个被代理的对象必须是 一个接口的实现类)

    下面看看如何玩转动态代理

    API

     /**
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     */
    Proxy.newProxyInstance (ClassLoader loader,
                            Class<?>[] interfaces,
                            InvocationHandler h)  //根据传递进去的被代理类的信息,生成代理对象
    
    • 参数1: 被代理的对象用到什么类加载器,编译的时候是不知道的,所以第一个参数我们要把被代理对象使用的类加载器告诉代理对象

    • 参数2: 被代理的对象要至少要实现了一个接口,大家看上面newProxyInstance的第二个参数,是一个Class<?>[]未知类型的Class对象的数组,代理对象要通过它找到他所实现的接口中有什么方法, 换句话说,我们只能代理接口中的方法(进而增强它的实现类中的方法)

    实例:

    接口

    public interface Calcullator {
        public float add(Integer i,Integer j);
        public float sub(Integer i,Integer j);
    }
    

    实现类

    public class CalcullatorImpl implements Calcullator {
        @Override
        public float add(Integer i, Integer j) {
        
            return i+j;
        }
        @Override
        public float sub(Integer i, Integer j) {
            return i*j;
        }
    }
    

    代理

    @Test
    public void lastProxy(){
        Calcullator calcullator = new CalcullatorImpl();
      Calcullator c =  (Calcullator) Proxy.newProxyInstance(calcullator.getClass().getClassLoader(), new Class[]{Calcullator.class},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    logger.info("开启事务");
                        // 执行方法
                     try {
                         Object result = method.invoke(calcullator, args);
                         logger.info("提交事务");
                         return result;
                      }catch (Exception e){
                         logger.info("回滚事务");
                     }
                     return null;
                }
            });
        System.out.println(c.add(1,2));
    }
    

    划重点!这里有个坑

    千万不要在 InvocationHander里面使用 proxy对象,不然就是个死循环

    原因:
    当我们执行完c.add(1,2)时,c是我们增强后的对象,调用add,触发的回调函数,下一步就会执行InvocationHandler的invoke方法,而它的参数Object类型的proxy就是它最终要返回的对象,我们如果在这里面使用它了,会重新触发invoke方法,方法里面有调用它,再触发invoke,无限循环


    六. 泛型与反射

    • 反射越过泛型检查
    //反射越过泛型检验
    @Test
    public void text2() throws Exception {
        //ArrayList<Integer>在这个集合中添加一个 String 字符串....
        //思路:泛型只是在编译期才有,但是真正运行起来就被擦出了...反射ArrayList<Integer>   拿到他的字节码文件(运行期)再添加字符串
            ArrayList <Integer> list = new ArrayList<>();
            list.add(111);   //无错
           //list.add("abc");  //报错
            Class clazz = Class.forName("java.util.ArrayList");
            Method add = clazz.getMethod("add" , Object.class);
            add.invoke(list,"abc");  //泛型反射
        for (Object a: list) {
            logger.info(a);
        }
    }
    
    • 反射在框架中的使用

    假设这样的情景 ,首先,我们拿着id去数据库中查询一个实体,我们的都知道,直接出来的数据肯定是 String字符串,但是我们使用的那些持久层的框架却能给我们返回一个 javaBean(查出来的数据封装在javaBean里面),模拟下它的实现

    首先准备下面的javaBean以及持久层的Dao

    @lombok
    public class person {
    
        @namePro(name="张三")
        private  String neme;
        private  Integer age;
        public person() {
            System.out.println("无参的构造器");
        }
    
        public person(String neme, Integer age) {
            System.out.println("有参的构造器");
            this.neme = neme;
            this.age = age;
        }
    }
    

    personDao

    这里有个比较有意思的事,原来我记得看到下面的personDao的时候,想都没想为什么它会这么写? 现在看看,好套路啊! 突然想起来 SpringDataElasticsearch,SpringDataJpa里面的Repository,Mybatis里面的通用mapper,类似让我们这样写, 只不过他们哪里都是接口,而我们现在是class类型. 底层全是反射,智慧与套路并存

    public class PersonDao extends  Dao<person> { }
    

    我的表演,从测试类入手

    测试类

    ///泛型与反射组合,很多框架经常使用下面这个方法
    @Test
    public void text1() throws Exception {
        PersonDao personDao = new PersonDao();
        
        //根据id 问dao要 person
        //从数据库中按照 i  把对象的信息找出来,但是这个时候, 从数据库里面拿出来是信息仅仅的一些字段的信息,
        // 我们的要求是:  当service层调用get()方法的时候, 我返回给他的是一个包装好的对象, 也就是说, 我通过反射创建T类型 的对象,然后返回给他
        person p = personDao.get(1);
        logger.info("p=="+p);
    }
    

    get方法,主要做两件事 1. 创建实例 2. 查询数据库把数据添加到实例对象中

    • 先看看上面的测试类,我们直接new的是personDao,而它继承了Dao,在继承的时候我们指定了它的类型是person类型,为什么这么设计? 因为设计框架的人,根本不知道你会传递进来一个person还是一个animal,所以他在写Dao时,一定会给它加上泛型 ( Dao就在下面)
    • 思考一下,它是入给创建实例的

    使用反射创建实例?我们知道一准是 通过Class.newInstance()方法,那么问题来了,1 .首先想想我们要谁的Class,person的class ,2. Class我咋整出来? 总不能使用T.class吧? 编译都过不去啊. 大家通过观察,发现person唯一出现的位置就是personDao的泛型的位置, 我们要做的就是把Dao的泛型取出来,赋给我们提前声明的Class clazz,这样一直全ok

    具体怎么做的?
    大家可以看下面的解决方法: 在Dao的构造方法中着手

    持久层

    public class Dao<T> {
    public static final Logger logger = Logger.getLogger(Dao.class);
    //目标就是获取 T  对应的Class 对象 , 为什么像下面那么写呢? 因为  T.class 不存在
    
    private Class<T> clazz;
    
    public Dao() {
        logger.info("dao 的无参构造器");
    
        //结果:10:52:37,546  INFO Dao:14 - Dao's Constructor 's  this ==com.atGongDa.entity.PersonDao@56ef9176  全类名
        //为啥是personDao  因为this是当前调用这个函数的对象,而当前对象是我 new  PersonDao()
        logger.info("打印this的全类名 =="+this);
    
        logger.info("this的 Class对象"+this.getClass());
    
        //获取Dao子类的父类
        Class clazz1 = this.getClass().getSuperclass();
    
        logger.info("this的父类的Class对象=="+clazz1);
        //this的父类的Class对象==class com.atGongDa.entity.Dao   结果还是没有T 泛型
    
        //获取Dao子类带泛型参数的父类,Dao<person>
        Type type = this.getClass().getGenericSuperclass();
    
        logger.info("type=="+type);  //com.atGongDa.entity.Dao<com.atGongDa.entity.person>
        //注意: Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型
        //我们要使用的是他的子接口  ParameterizedType  里面的
        /*Type[] getActualTypeArguments()返回表示此类型实际类型参数的 Type 对象的数组。
            注意,在某些情况下,返回的数组为空。如果此类型表示嵌套在参数化类型中的非参数化类型,则会发生这种情况。
            返回 :表示此类型的实际类型参数的 Type 对象的数组
        * */
    
        //获取具体的泛型参数
        if(type instanceof ParameterizedType){     //这是
            ParameterizedType parameterizedType = (ParameterizedType)type;
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            //这有个技巧,假如说我们仅仅想看看数组里面有什么,用工具Arrays.asList()
            logger.info("T=="+ Arrays.asList(actualTypeArguments));//[class com.atGongDa.entity.person]
    
            if(actualTypeArguments!=null&&actualTypeArguments.length>0){
                Type arg = actualTypeArguments[0];
                logger.info("arg=="+arg);
    
                if (arg instanceof Class){
                    clazz= (Class<T>) arg;
                }
            }
        }
    }
    
     //从数据库中按照 i  把对象的信息找出来,但是这个时候, 从数据库里面拿出来是信息仅仅的一些字段的信息,
    // 我们的要求是:  当service层调用get()方法的时候, 我返回给他的是一个包装好的对象, 也就是说, 我通过反射创建T类型 的对象,然后返回给他
    public T get(Integer i) throws Exception {
        //问题是啥呢?  那么如何得到 Dao<T> T所对象的Class对象
        // T.class   没有
        logger.info("Dao get==>"+clazz);
        
        //创建实例
        T t1 = clazz.newInstance();
        //这里直接手动写属性名, 获取它的属性,把查出来的值放进去      
        Field name = clazz.getDeclaredField("neme");
        /**
         *  当然框架的设计者肯定不会这么做,因为它根本不知道现在的clazz是个啥! 
         *  他们获取全部的属性,和结合数据库中查询出来的属性 封装clazz对象里面 (这也是为什么PO的属性名不能乱写,和数据表字段是有关联的)
         *  Field[] declaredFields = clazz.getDeclaredFields();
         */
    
        name.setAccessible(true);
        name.set(t1,"张三");
        //返回Class对象
        return t1;
    }
    
    

    Class的另一种用法

    • 直接读取类路径下的配置文件
       Object o = forName.newInstance();
      InputStream resourceAsStream = o.getClass().getClassLoader().getResourceAsStream("123.properties");
    
  • 相关阅读:
    121. Best Time to Buy and Sell Stock
    70. Climbing Stairs
    647. Palindromic Substrings
    609. Find Duplicate File in System
    583. Delete Operation for Two Strings
    556 Next Greater Element III
    553. Optimal Division
    539. Minimum Time Difference
    537. Complex Number Multiplication
    227. Basic Calculator II
  • 原文地址:https://www.cnblogs.com/ZhuChangwu/p/11150413.html
Copyright © 2011-2022 走看看