java高级进阶-------反射

java反射是java中功能很强大的一个功能,很多的框架都使用到了反射的机制,所以学习反射是java进阶必不可少的步骤。
下面的内容就是反射中一些常见的问题和使用,能力属于初级,所以很多很深的东西根本写不了,只能带大家入门学习。
1、在学习java反射之前有必要了解一些概念:
类的初始化:类的初始化过程分为三步 系统会通过加载、连接、初始化三步来实现类的初始化
加载:将指定的class文件读取到内存中,并且为它创建一个Class对象 任何类被用时系统都会创建一个Class对象
连接: 验证 是否有正确的内部结构,并和其它类协调一致
准备 负责为类的静态成员变量分配内存,并且设置初始值
初始化:初始化成员变量等

类的加载的时机:
类的实例化
调用类的静态变量 或者为静态变量赋值
调用类的静态方法
初始化某个类的子类
使用反射原理强制创建某一类或接口对应的Class对象

java反射概念:自己理解的就是在程序运行的时候,可以动态的获取一个类中的属性和方法,而且可以调用以及修改类中方法和属性的一种机制我们成为java反射机制。
要想使用java反射机制,就必须有字节码文件。字节码文件获取的方法有三种:
1、Class.forName(“类路径”);
2、类名.class
3、对象.getClass();
2、使用反射
注意如果同时用三种方法创建的字节码对象,三个对象都是相同的
有了字节码文件就可以通过它来创建对象,通过对象就可以调用类中的方法和属性。

package com.yxc.domain;

/**
 * 实体类 后面通过反射拿到这个类中的所有方法和所有属性
 * 不管是私有的还是公有的,都可以获取,这就是反射强大之处。
 */
public class User  {

    //定义两个方法
    public void print(){
        System.out.println("我是公有打印方法");
    }
    private void print1(){
        System.out.println("我是私有的打印方法");
    }
}

package com.yxc.reflect;

import com.yxc.domain.User;

/**
 * 第一个简单的反射类
 */
public class ReflectDemo {
    public static void main(String[] args) {
        //三种获取字节码文件的方法
        /**
         * 第一种方法,通过forName()方法获取字节码文件
         */
        Class clazz1=null;
        try {
            clazz1=Class.forName("com.yxc.domain.User");
            //通过字节码文件的newInstance可以创建对象,返回的Object类型,我们需要强转
            User user = (User)clazz1.newInstance();
            user.print();
            //在这里私有方法是不可以直接调用的,但是等会我们可以通过反射获取所有的方法,包括私有的字段
            //user.print1();
        } catch (Exception e) {
            e.printStackTrace();
        }

        /**
         * 第二种,直接通过类名.class获取字节码文件;
         */
        Class clazz2 = User.class;
        try {
            User user =(User)clazz2.newInstance();
            user.print();
        }catch (Exception e) {
            e.printStackTrace();
        }

        /**
         * 通过对象的getClass()方法获取字节码文件
         */
        User user=new User();
        Class clazz3 = user.getClass();
        try {
            User user1 = (User)clazz3.newInstance();
            user1.print();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        //这里的对象都是相同的,对于同一个类而言,字节码类是相同的
        System.out.println(clazz2==clazz3);
        System.out.println(clazz1==clazz2);
    }
}

上面的User类就是通过反射来执行它里面的方法和属性的,我在学的时候有一个疑问,既然都知道是User类,要调用它里面的方法和属性,干嘛不直接创建对象,通过对象调用呢?那是因为刚开始学,没有体会反射真正强大的地方,如果类中的私有属性和私有方法呢,我们在其它类中是无法调用的,这时候通过反射就可以解决问题。这些我们马上接着说。
3、下面我们通过一个案列来实现公有属性,私有属性,公有方法,公有有参数的方法,私有方法,私有有参数的方法的获取。

package com.yxc.domain;

/**
 * 实体类 后面通过反射拿到这个类中的所有方法和所有属性
 * 不管是私有的还是公有的,都可以获取,这就是反射强大之处。
 */
public class User  {
    /**
     * 定义一个私有属性和公共属性
     */
    private String name;
    public int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void print(){
        System.out.println("我是公有打印方法");
    }
    public void print(String name){
        System.out.println("我是有参公有方法"+name);
    }
    private void print1(){
        System.out.println("我是私有的打印方法");
    }

    private void print1(Integer i){
        System.out.println("我是私有的有参数的打印方法,编号"+i);
    }
}

package com.yxc.reflect;

import com.yxc.domain.User;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * 第一个简单的反射类
 */
public class ReflectDemo {
    public static void main(String[] args) {
        /**
         * 获取属性
         * 获取公有属性和私有属性
         */

        Class clazz=null;
        try {
            //1、首先获取字节码文件
            clazz=Class.forName("com.yxc.domain.User");
            //2、通过字节码文件创建对象
            User user= (User)clazz.newInstance();

            //3、获取公有属性的代码
            Field f1 = clazz.getField("age");//获取字段名
            f1.set(user,23);                      //设置字段新的值
            int age = (int)f1.get(user);          //获取字段
            System.out.println(age);


            //获取私有属性的代码
            Field f2 = clazz.getDeclaredField("name"); //和公有属性的获取一样,只是方法不一样
            f2.setAccessible(true);                          //这一步是核心,需要设置可访问为true
            f2.set(user,"yxc");                              //后面的操作和公有属性一致
            String name = (String)f2.get(user);
            System.out.println(name);

            //获取公有无参数的方法
            Method m1 = clazz.getMethod("print");
            m1.invoke(user);
            //获取有参数的公有方法
            Method m2 = clazz.getMethod("print", String.class);//参数的类型
            m2.invoke(user,"hhh");//那个对象执行这个方法,方法的参数

            //获取私有方法
            Method m3 = clazz.getDeclaredMethod("print1");
            m3.setAccessible(true); //对于方法来说,同样需要设置可访问
            m3.invoke(user);

            //获取有参数的私有方法
            Method m4 = clazz.getDeclaredMethod("print1", Integer.class);
            m4.setAccessible(true);
            m4.invoke(user,23);


        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

通过上面的简单案列,我们可以了解到反射的基本使用方法。反射的强大支持就在于此,不管你是私有还是公有,只要我想要的,没有得不到的,很霸道,很强势。
4、反射的应用
下面通过简单的几个反射的应用来巩固一下反射的知识,当然反射的应用是很广泛的,几乎所有的框架的开发都使用到了反射。

反射使用案例1:越过泛型检测

package com.yxc.reflect;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 反射的案例:越过泛型检测
 * 我们都知道集合可以定义泛型,只要定义了,那么其它的类型是不可以添加进入的
 * 但是学完反射以后我们就可以通过反射来实现,越过泛型检验
 */
public class ArraysCheck {
    public static void main(String[] args) {
        //定义一个list集合,指定类型为整型数据
        ArrayList<Integer> list=new ArrayList<>();
        list.add(23);
        list.add(24);
        list.add(6);
        //此时如果试图添加字符串类型,那么肯定不能通过编译
        //list.add("报错");

        //下面通过反射来
        //获取字节码文件
        Class clazz=ArrayList.class;
        try {
            //获取集合中的add方法,而且将参数设置为Object类型
            Method m1 = clazz.getMethod("add", Object.class);
            m1.setAccessible(true);      //其实这一步可以不用,因为add是公有的方法
            //开始执行方法 使用list对象
            m1.invoke(list,"越过泛型检验");

        } catch (Exception e) {
            e.printStackTrace();
        }

        //通过迭代器将集合中的数据输出
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }
}

反射应用案例2:静态代理
这里又出现了一个新的专业名词,代理。如果我们自己先理解一下,什么是代理就好比是让一个人专门帮你做事,你不需要管,只要将代理权给他,那么他可以帮你完成一些任务,而你不需要关心这些,只要做好自己的事就好。 代理有很多种,常见的静态代理,jdk代理(动态代理)后面学习的第三方的cglib代理。
为什么要使用代理呢?我们都知道在一些操作的前面或者后面需要动态的加入一些功能,比如在查询数据前加一个权限校验,后面加一个日志生成的方法,当然我们可以使用继承的方式去完成这样的功能,继承势必会增加类的数量,这样会导致类的膨胀,当然也可以直接在原方法中直接添加,那么这样的代码是不健全的,不易维护的。我们都知道软件的开发需求是不断变更的,如果没改动一次就去动源码的话,那基本是不理想的。所以我们需要使用代理,让那些增强的方法专门放在一个代理类中,这样就可以在不修改源码的情况下添加新的功能。下面我们通过代码来实现一个简单的静态代理。

package com.yxc.proxy;

/**
 * 对于静态代理来说,必须要有接口
 */
public interface UserDao {
    /**
     * 定义一个抽象方法根据id查询一个用户
     * @param id
     */
    public void findUser(int id);
}

package com.yxc.proxy;



/**
 * 这是真正工作的类
 */
public class ReadWorkUserDaoImpl implements UserDao {

    //在实际开发中这里就可以直接从数据库查询到需要的数据
    @Override
    public void findUser(int id) {
        System.out.println("从数据库查询id为"+id+"的用户");
    }
}

package com.yxc.proxy;

/**
 * 这是代理类
 * 对于代理类来说也需要继承接口,而且要持有接口对象,就好比证明一样,这样才可以拿到代理权
 */
public class ProxyUser implements UserDao{
    //代理类必须持有接口对象,就好比代理要有代理权
    private UserDao userDao;
    //通过构造方法将参数初始化
    public ProxyUser(UserDao userDao){
        this.userDao=userDao;
    }


    @Override
    public void findUser(int id) {
        System.out.println("用户权限检验,可以进行数据库查询");
        userDao.findUser(id);
        System.out.println("完成操作,记录日志");
    }
}
package com.yxc.proxy;

/**
 * 测试静态代理
 */
public class ProxyTest {
    public static void main(String[] args) {
        UserDao userDao=new ReadWorkUserDaoImpl();
        ProxyUser proxyUser = new ProxyUser(userDao);
        proxyUser.findUser(23);
    }
}

说明一下:对于静态代理必须由接口,然后真正执行的类和代理类都要实现接口,对重要的就是在代理类中必须持有接口的对象,就好比代理的人肯定要有代理权,而接口就是权力。我看网上的很多举例说代理类好比就是经纪人,帮你处理一些事情,而你只要授权给他即可,你不用关心那些事情,只要做好自己的事情,其它的包装都有经济人帮你完成,这就好比代理的任务。
静态代理的优点:显然在不改变源码的情况下可以添加新的功能,这样增强代码的可维护性
静态代理的缺点:如果有很多类需要代理,就必须持有多个接口的变量或者写很多的代理类,那样的话代理类又会膨胀,而且毫无意义,所以有没有什么方法可以根据接口对象,动态的生成这一种的代理类,那么就引入的动态代理。
有了上面的说明以后,我们可以理解动态代理只需要在静态代理上加以修饰。代码如下:
这里只贴出不同于静态代理的类,相同的类参考上面所说的静态代理代码

package com.yxc.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 这是代理类
 * 对于代理类来说也需要继承接口,而且要持有接口对象,就好比证明一样,这样才可以拿到代理权
 * 动态代理其实就是实现的接口不一样,我们使用内部实现好的
 */
public class ProxyUser implements InvocationHandler {
    //对于动态代理也需要持有接口的对象
    private UserDao userDao;
    //通过构造方法将参数初始化
    public ProxyUser(UserDao userDao){
        this.userDao=userDao;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         System.out.println("用户权限检验,可以进行数据库查询");
        //invoke方法的前面就是在实际执行方法中的前面添加的功能
        Object invoke = method.invoke(userDao,args);
        //后面就是后面新增的方法
       System.out.println("完成操作,记录日志");
        //最后返回的是一个代理对象
        return invoke;
    }
}

package com.yxc.proxy;

import java.lang.reflect.Proxy;

/**
 * 测试静态代理
 */
public class ProxyTest {
    public static void main(String[] args) {
        //创建一个真正的实现类
        ReadWorkUserDaoImpl userDaoImpl=new ReadWorkUserDaoImpl();
        /**
         * 这里面的参数有点多我们一个个讲解
         * Proxy.newProxyInstance()方法直接创建一个代理对象,这是内部提供好的类和方法,我们直接使用
         * 参数说明:
         * 1、需要一个接口的类加载器ClassLoader
         * 2、需要一个接口
         * 3、需要一个代理对象
         */
        UserDao userDao=(UserDao) Proxy.newProxyInstance(userDaoImpl.getClass().getClassLoader(),
                userDaoImpl.getClass().getInterfaces(),
                new ProxyUser(userDaoImpl)
        );
        userDao.findUser(23);
    }
}

静态代理和动态代理执行的效果图如下:
在这里插入图片描述
最后还有一种代理是使用第三方开发的cglib,这个我们在spring框架中会有所涉及,到时使用注解开发会更加高效。我们可以在一个类中写很多的代理方法,直接通过注解表名这个方法添加哪个类中的哪个方法的哪个位置。
总结:对于反射来说,其实应用还有很多,但是对于初学者来说,后面的东西可能有点深,我自己也还没有深层次的接触过,而且在一般的java开发中,很少接触到反射这一章,都是接触一些框架的源码学习时才会慢慢的看到反射,那个时候才需要更深一步的研究学习。今天的总结就到此结束。有很多不足的地方,欢迎大家指出,一起学习。菜鸟一枚,慢慢的成长。一步一个脚印,走稳咯。

发布了33 篇原创文章 · 获赞 37 · 访问量 4418

猜你喜欢

转载自blog.csdn.net/weixin_42142899/article/details/101508122