Java程序员都需要懂的「反射」

栏目: IT技术 · 发布时间: 5年前

内容简介:今天来简单写一下Java的反射。本来没打算写反射这个知识点的,只是不少的读者都问过我:“你的知识点好像缺了反射阿。能不能补一下?”这周末也有点空了,所以来写写我对反射的简单理解。这篇是入门文章,没有高深的知识点,希望能对新人有帮助。如果文章有错的地方,麻烦在评论区友善评论指出~

前言

只有光头才能变强。

文本已收录至我的GitHub精选文章,欢迎Star: https://github.com/ZhongFuCheng3y/3y

今天来简单写一下 Java 的反射。本来没打算写反射这个知识点的,只是不少的读者都问过我:“你的知识点好像缺了反射阿。能不能补一下?”

Java程序员都需要懂的「反射」

这周末也有点空了,所以来写写我对反射的简单理解。这篇是入门文章,没有高深的知识点,希望能对新人有帮助。如果文章有错的地方,麻烦在评论区友善评论指出~

Java常用和重要的知识点我都写过( 现在已有200+篇技术原创 ),如果想看的同学,不妨关注我的 GitHub ,即可获取我的所有原创文章。

一、序言

在学习Java基础的时候,一般都会学过反射。我在初学反射的时候,并不能理解反射是用来干嘛的。学了一些API发现:“ 明明我自己能直接new一个对象,为什么它要绕一个圈子,先拿到Class对象,再调用Class对象的方法来创建对象呢,这不是多余吗?

相信很多人在初学反射的时候也都会有这个想法(我就不相信就只有我一个人这么蠢!!)

而且在搜索相关资料的时候,一般也仅仅是讲解反射的一系列API,始终是不了解反射究竟是有什么用,这篇文章来告诉你吧。 觉得不错,给我点个赞呗

Java程序员都需要懂的「反射」

二、引出Class对象

首先我们来看一段代码:

public class Demo {
    // 自建了一个Student类
    class Student{
    }
    public static void main(String[] args) {
        // 将Object 强转成Student类
        Object o = new Object();
        Student s = (Student) o;
    }
}

我们在IDE编写这一段代码的时候,不会出现任何的错误。但是等我们执行的时候,我们会知道这肯定 强转失败了

Java程序员都需要懂的「反射」

那么“Java”(实质上JVM)是怎么知道我们写的 强转 有没有问题的呢?可以依赖 Class 对象来协助判断。

如果看过我写 JVM 的那篇文章的同学应该都知道一个对象的加载过程,如果没看过的同学可以再去看看,顺便在这里给大家复习一下:

  • 一个 .java 的文件经过 javac 命令编译成功后,得到一个 .class的文件
  • 当我们执行了初始化操作(有可能是new、有可能是子类初始化 父类也一同被初始化、也有可能是反射...等),会将 .class 文件通过 类加载器 装载到 jvm
  • .class 文件加载器加载到jvm中,又分了 好几个步骤 ,其中包括 加载、连接和初始化
  • 其中在加载的时候,会在Java堆中 创建一个java.lang.Class类的对象 ,这个Class对象代表着 类相关的信息

既然说,Class对象代表着类相关的信息,那说明只要类有什么东西,在Class对象我都能找得到。我们打开IDE看看里边的方法:

Java程序员都需要懂的「反射」

于是我们可以 通过Class对象来判断对象的真正类型

Java程序员都需要懂的「反射」

三、反射介绍

其实反射就是围绕着 Class 对象和 java.lang.reflect 类库来学习,就是各种的API

比如上面截图的 Method / Field / Constructor 这些都是在 java.lang.reflect 类库下,正是因为这些类库的学习并不难,所以我才一直没写反射的文章。

Java程序员都需要懂的「反射」

我并不是说这些API我都能记住,只是这些API教程在网上有非常非常多,也足够通俗易懂了。在入门的时候,其实掌握以下几种也差不多了:

  • 知道获取Class对象的几种途径
  • 通过Class对象创建出对象,获取出构造器,成员变量,方法
  • 通过反射的API修改成员变量的值,调用方法
/*
    下面是我初学反射时做的笔记,应该可以帮到大家,代码我就不贴了。(Java3y你值得关注)
*/
想要使用反射,我先要得到class文件对象,其实也就是得到Class类的对象
Class类主要API:
        成员变量  - Field
        成员方法  - Constructor
        构造方法  - Method
获取class文件对象的方式:
        1:Object类的getClass()方法
        2:数据类型的静态属性class
        3:Class类中的静态方法:public static Class ForName(String className)
--------------------------------  
获取成员变量并使用
        1: 获取Class对象
        2:通过Class对象获取Constructor对象
        3:Object obj = Constructor.newInstance()创建对象
        4:Field field = Class.getField("指定变量名")获取单个成员变量对象
        5:field.set(obj,"") 为obj对象的field字段赋值
如果需要访问私有或者默认修饰的成员变量
        1:Class.getDeclaredField()获取该成员变量对象
        2:setAccessible() 暴力访问  
---------------------------------          
通过反射调用成员方法
        1:获取Class对象
        2:通过Class对象获取Constructor对象
        3:Constructor.newInstance()创建对象
        4:通过Class对象获取Method对象  ------getMethod("方法名");
        5: Method对象调用invoke方法实现功能
如果调用的是私有方法那么需要暴力访问
        1: getDeclaredMethod()
        2: setAccessiable();

相信我,去搜索引擎看一会,你就学会了。反射的API并不难学,一般人学不懂反射因为不知道反射究竟能干什么,下面我来讲讲我的讲解。

四、为什么需要反射

在初学Java的时候其实我个人认为还是比较难理解为什么需要反射的,因为没有一定的代码量下,很难理解为什么我要绕一个圈子去搞反射这一套。

我现在认为用反射主要有两个原因:

  • 提高程序的灵活性
  • 屏蔽掉实现的细节,让使用者更加方便好用

我一直在文章中都在强调,学某一项技术之前,一定要理解为什么要学这项技术,所以我的文章一般会花比较长的幅度上讲为什么。

下面我来举几个例子来帮助大家理解

Java程序员都需要懂的「反射」

4.1 案例一(JDBC)

相信大家都写过 jdbc 的代码,我贴一小段,大家回顾一下:

Class.forName("com.mysql.jdbc.Driver");

//获取与数据库连接的对象-Connetcion
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/java3y", "root", "root");

//获取执行 sql 语句的statement对象
statement = connection.createStatement();

//执行sql语句,拿到结果集
resultSet = statement.executeQuery("SELECT * FROM users");

后来为什么要变成下面这种形式呢?

//获取配置文件的读入流
InputStream inputStream = UtilsDemo.class.getClassLoader().getResourceAsStream("db.properties");

Properties properties = new Properties();
properties.load(inputStream);

//获取配置文件的信息
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");

//加载驱动类
Class.forName(driver);

理由很简单, 人们不想修改代码 。只要存在有变动的地方,我写在配置里边,不香吗?但凡有一天,我的username,password,url甚至是数据库都改了,我都能够通过修改配置的方式去实现。

不需要动我丝毫的代码,改下配置就完事了,这就能提供程序的灵活性。

有人可能会问:“那还是要改啊,我改代码也很快啊,你改配置不也是要改吗”。

其实不一样的,我举个例子:

  • 三歪写了一个JDBC组件,把各种配置都写死在代码上,比如上面的driver/username/数据库连接数等等。现在三歪不干了,要跑路了。
  • 敖丙来接手三歪的代码,敖丙刚开始接手项目,公司说要换数据库。敖丙给领导说:这玩意,我改改配置就好了,几分钟完事。
  • 敖丙找了半天都没找到配置的地方,由于三歪写的代码又臭又烂,找了半天才找到入口和对应的位置。

改代码的风险要比改配置大, 即便不知道代码的实现 都能通过改配置来完成要做的事。

这种就 能通过可配 的,其内部很可能就是通过反射来做的。

这里只是说可能,但不全是。有的可配的参数可能就仅仅只是配置,跟反射无关。但上面jdbc的例子,就是通过反射来加载驱动的。

Java程序员都需要懂的「反射」

4.2 案例二(SpringMVC)

相信大家学SpringMVC之前都学过Servlet的吧,如果没学过,建议看我的文章再复复习。

我当时学MVC框架的时候给我带来印象最深的是什么,本来需要各种 getParameter() ,现在只要通过约定好 JavaBean 的字段名,就能把值填充进去了。

还是上代码吧,这是我们当时学Servlet的现状:

//通过html的name属性,获取到值
String username = request.getParameter("username");
String password = request.getParameter("password");
String gender = request.getParameter("gender");

//复选框和下拉框有多个值,获取到多个值
String[] hobbies = request.getParameterValues("hobbies");
String[] address = request.getParameterValues("address");

//获取到文本域的值
String description = request.getParameter("textarea");

//得到隐藏域的值
String hiddenValue = request.getParameter("aaa");

我们学到SpringMVC的时候是怎么样的:

@RequestMapping(value = "/save")
@ResponseBody
public String taskSave(PushConfig pushConfig) {
     // 直接使用  
       String name= pushConfig.getName();
}

为什么SpringMVC能做到?其实就是通过 反射 来做的。

相信你也有过的经历:

  • 如果你的JavaBean的属性名跟传递过来的参数名不一致,那就“自动组装”失败了。因为反射只能根据参数名去找字段名,如果不一致,那肯定 set 不进去了。所以就组装失败了呀~

如果在使用框架的时候,为什么我们往往写上JavaBean,保持字段名与参数名相同,就能“自动”得到对应的值呢。这就是反射的好处。

屏蔽掉实现的细节,让使用者更加方便好用

Java程序员都需要懂的「反射」

五、我们写反射的代码多吗?

大部分 程序员 都是写业务代码的,大部分程序员都是维护老系统的,其实要我们自己写反射的代码的时候,真的不多。

从上面也看出,什么时候会写反射?写我们自己 组件/框架 的时候。如果想找个地练手一下反射,我觉得 自定义注解 是一个不错的选择。

因为现在用注解的地方很多,主要是够清晰简单(再也不用对着一堆的XML文件了,哈哈哈哈~)。

我初学的时候写过一段,可以简单参考一下,思路都差不多的哈。下面是使用的效果(使用自定义注解给不同的接口增加权限)

@permission("添加分类")
/*添加分类*/ void addCategory(Category category);

/*查找分类*/
void findCategory(String id);

@permission("查找分类")
/*查看分类*/ List<Category> getAllCategory();

返回一个 代理 的Service对象来处理自定义注解:

public class ServiceDaoFactory {

    private static final ServiceDaoFactory factory = new ServiceDaoFactory();

    private ServiceDaoFactory() {
    }

    public static ServiceDaoFactory getInstance() {
        return factory;
    }


    //需要判断该用户是否有权限
    public <T> T createDao(String className, Class<T> clazz, final User user) {

        System.out.println("添加分类进来了!");

        try {
            //得到该类的类型
            final T t = (T) Class.forName(className).newInstance();
            //返回一个动态代理对象出去
            return (T) Proxy.newProxyInstance(ServiceDaoFactory.class.getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() {

                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, PrivilegeException {
                    //判断用户调用的是什么方法
                    String methodName = method.getName();
                    System.out.println(methodName);

                    //得到用户调用的真实方法,注意参数!!!
                    Method method1 = t.getClass().getMethod(methodName,method.getParameterTypes());

                    //查看方法上有没有注解
                    permission permis = method1.getAnnotation(permission.class);

                    //如果注解为空,那么表示该方法并不需要权限,直接调用方法即可
                    if (permis == null) {
                        return method.invoke(t, args);
                    }

                    //如果注解不为空,得到注解上的权限
                    String privilege = permis.value();

                    //设置权限【后面通过它来判断用户的权限有没有自己】
                    Privilege p = new Privilege();
                    p.setName(privilege);

                    //到这里的时候,已经是需要权限了,那么判断用户是否登陆了
                    if (user == null) {

                        //这里抛出的异常是代理对象抛出的,sun公司会自动转换成运行期异常抛出,于是在Servlet上我们根据getCause()来判断是不是该异常,从而做出相对应的提示。
                        throw new PrivilegeException("对不起请先登陆");
                    }

                    //执行到这里用户已经登陆了,判断用户有没有权限
                    Method m = t.getClass().getMethod("findUserPrivilege", String.class);
                    List<Privilege> list = (List<Privilege>) m.invoke(t, user.getId());

                    //看下权限集合中有没有包含方法需要的权限。使用contains方法,在Privilege对象中需要重写hashCode和equals()
                    if (!list.contains(p)) {
                        //这里抛出的异常是代理对象抛出的,sun公司会自动转换成运行期异常抛出,于是在Servlet上我们根据getCause()来判断是不是该异常,从而做出相对应的提示。
                        throw new PrivilegeException("您没有权限,请联系管理员!");
                    }

                    //执行到这里的时候,已经有权限了,所以可以放行了
                    return method.invoke(t, args);
                }
            });

        } catch (Exception e) {
            new RuntimeException(e);
        }
        return null;
    }
}

Java程序员都需要懂的「反射」

最后

这篇反射跟网上的文章不太一样,网上的反射一般都是介绍反射的API如何使用。如果你觉得还不错,给我点赞吧:+1:。想要看其他知识点的同学,可以给我留言,我可以酌情考虑写一下(哈哈哈哈,突然变大牌了)

这篇文章涉及到的其他知识点:JVM类的加载过程、注解、动态代理、SpringMVC、JDBC我都已经写过文章了,想要阅读的同学可以关注我的 GitHub 搜索相关关键字即可。

Java程序员都需要懂的「反射」

如果大家想要 实时 关注我更新的文章以及分享的干货的话,可以关注我的公众号「 Java3y 」。

  • :fire: Java精美脑图
  • :fire: Java学习路线
  • :fire: 开发常用工具

在公众号下回复「 888 」即可获取!!

Java程序员都需要懂的「反射」

本已收录至我的GitHub精选文章,欢迎Starhttps://github.com/ZhongFuCheng3y/3y

求点赞 求关注️ 求分享:busts_in_silhouette: 求留言:speech_balloon: 对我来说真的 非常有用 !!!


以上所述就是小编给大家介绍的《Java程序员都需要懂的「反射」》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Ruby on Rails Tutorial

Ruby on Rails Tutorial

Michael Hartl / Addison-Wesley Professional / 2012-8-6 / USD 44.99

"Ruby on Rails(TM) Tutorial by Michael Hartl has become a must-read for developers learning how to build Rails apps." -Peter Cooper, Editor of Ruby Inside Using Rails, developers can build web applica......一起来看看 《Ruby on Rails Tutorial》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具