Java基础之内省

Wesley13
• 阅读 653

Java基础之内省

什么是内省

  首先,我们要知道什么是内省。按我自己的理解就是在反射的原理上进行封装,来更方便的操作JavaBean

JavaBean就是特殊格式的类,其规范为:

  1. JavaBean 类必须是一个公共类,即使用关键字 public 声明类。
  2. JavaBean 类中必须有一个声明为公共的无参构造函数。
  3. JavaBean 类中的实例变量必须为私有的,即所有的实例变量都使用关键字 private 声明。
  4. 必须为 JavaBean 类中的实例变量提供公共的 getter / setter 方法。(在 getter / setter 方法中,可以做一些权限控制,数据校验等工作,以保证数据的安全,合法性。)
  5. JavaBean 类中实例属性的命名规则:
  1. 属性名前两个字母都小写:将属性名的首字母大写,然后用作 getter / setter 方法中 get / set 的后部分,如属性名为 name, 它的 getter / setter 方法为 getName / setName。
  2. 属性名的第二个字母大写: 将属性名直接用作 getter / setter 方法中 get / set 的后部分,即属性名大小写不变。如属性名为 uName,它的 getter / setter 方法为 getuName / setuName。
  3. 属性名前两个字母都大写:将属性名直接用作 getter / setter 方法中 get / set 的后部分,即属性名大小写不变。如属性名为 IDcode, 它的 getter / setter 方法为 getIDcode / setIDcode。
  4. 属性名首字母大写:将属性名直接用作 getter / setter 方法中 get / set 的后部分,即属性名大小写不变。如属性名为 Ucode, 它的 getter / setter 方法为 getUcode / setUcode。但是这种情况,在应用中会出现找不到属性的错误

参考此文

如何使用内省

内省的主要类(接口)有:Introspector(类)、BeanInfo(接口)、PropertyDescriptor(类),这三个都在java.bean包下。

Introspector是内省的入口,是一个工具类,用来获得BeanInfo,即一个JavaBean的信息。

测试Bean:Student
public class Student {
    private String name;
    private String address;

    public Student() {
    }

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
//static BeanInfo getBeanInfo(Class<?> beanClass):在 Java Bean 上进行内省,了解其所有属性、公开的方法和事件
BeanInfo beanInfo = Introspector.getBeanInfo(Student.class);

获取到一个bean的信息之后就可以获得所有的属性描述器了:

PropertyDescriptor[] p = beanInfo.getPropertyDescriptors();

遍历打印一下看看结果:

BeanInfo beanInfo = Introspector.getBeanInfo(Student.class);
PropertyDescriptor[] p = beanInfo.getPropertyDescriptors();
System.out.println(p.length);
for (PropertyDescriptor descriptor : p) {
    System.out.println(descriptor);
}

//输出--------------
p.length = 3
java.beans.PropertyDescriptor[name=address; propertyType=class java.lang.String; readMethod=public java.lang.String nei_xing.Student.getAddress(); writeMethod=public void nei_xing.Student.setAddress(java.lang.String)]
//为了方便观察加了注释隔开
java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()]
//
java.beans.PropertyDescriptor[name=name; propertyType=class java.lang.String; readMethod=public java.lang.String nei_xing.Student.getName(); writeMethod=public void nei_xing.Student.setName(java.lang.String)]

这里,Student只有两个属性但是其对象描述器却又三个,因为:

Object声明了getClass()方法来获得对象本身类型的Class对象,由于所有的类都继承自Object,所以所有的类都有getClass()方法。但类中并没有class字段(class是关键字,不可以作为类名),而且也没有setClass()方法,所以在使用内省编程的时候一般需要过滤掉“class”属性
参考

解决方法:
使用getBeanInfo(Class<?> beanClass)方法的重载:
static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass):给定断点类,获得其BeanInfo
比如:

BeanInfo beanInfo1 = Introspector.getBeanInfo(Student.class, Object.class);

此时在获取其属性描述器就只能得到Student的两个,而过滤掉了class属性。

得到每个属性的描述器之后便可以获取和设置其Getter/Setter方法了:

Student stu = new Student();
BeanInfo beanInfo1 = Introspector.getBeanInfo(Student.class, Object.class);
PropertyDescriptor[] p1 = beanInfo1.getPropertyDescriptors();
System.out.println(p1.length);
for (PropertyDescriptor d : p1) {
    String name = d.getName();
    System.out.println("name = " + name);
    Method getter = d.getReadMethod();//获取getter方法
    Method setter = d.getWriteMethod();//获取setter方法
    if ("name".equals(name)){
        setter.invoke(stu, "张三");//使用反射调用该方法
    }
    if ("address".equals(name)){
        setter.invoke(stu, "上海");
    }
    System.out.println(getter.invoke(stu));
}

输出结果:

2
name = address
上海
name = name
张三

上面是内省实现方式的一种,除了使用BeanInfo获取属性描述器对象,还可以直接创建属性描述器:

PropertyDescriptor pd = new PropertyDescriptor("address", Student.class);
Method getter = pd.getReadMethod();
Method setter = pd.getWriteMethod();

放两个Servlet中内省Demo:

Demo1

前端页面:

<form action="/introspectorServlet" method="post">
    姓名 <input type="text" name="name" /> <br/>
    住址 <input type="text" name="address"/> <br/>
    <input type="submit" value="提交"/>
</form>

IntrospectorServlet:

@WebServlet("/introspectorServlet")
public class IntrospectorServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //处理请求乱码
        request.setCharacterEncoding("UTF-8");
        //获取页面提交的所有的数据
        /*
            map集合的key是form表单标签的name属性值
            map集合的value是浏览器输入框输入的值,以String类型的数组形式接收
            举例:
            住址 <input type="text" name="address"/> <br/>
            key:address
            value:{"上海"}
         */
        Map<String, String[]> m = request.getParameterMap();
//        System.out.println(m);
        //创建封装属性的目标对象person
        Person p = new Person();
        try {
            //调用方法
            setProperty(p,m);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(p.getName());
        System.out.println(p.getAddress());
    }

    private void setProperty(Object obj, Map<String, String[]> m) throws Exception {
        // 将请求参数中 map的key 与传入对象属性名称 比较,如果一致,将参数的值赋值给对象属性
        //使用内省类获取BeanInfo类的对象
        BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
        //获取所有的属性描述器
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        //遍历数组取出每一个属性描述器
        for (PropertyDescriptor descriptor : propertyDescriptors) {
            //获取Person类中的属性
            String property_name = descriptor.getName();
            //判断属性在map中是否存在对应值
            if(m.containsKey(property_name)){
                //包含
                //获取对应的value值
                String value = m.get(property_name)[0];
                /*
                     Method getWriteMethod() 获得应该用于写入属性值的方法。
                 */
                Method setter = descriptor.getWriteMethod();
                //将value 写到属性中
                setter.invoke(obj, value);
            }
        }
    }
}

使用request.getParameterMap();从前台获取的数据都是字符串,那么如何使用内省技术将数据封装到javabean中的基本类型的字段上?

Demo1

Student Bean类

public class Person {
    private String name;
    private String address;
    private int age;
    private double money;


    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", age=" + age +
                ", money=" + money +
                '}';
    }
}

Servlet 代码:

@WebServlet("/introspector2Servlet")
public class IntrospectorServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //处理中文乱码
        request.setCharacterEncoding("UTF-8");
        Person p = new Person();
        //获取所有的数据
        //key 标签 name属性值  value 输入的值
        Map<String, String[]> map = request.getParameterMap();
        try {
            setProperty(p, map);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(p.getName());
        System.out.println(p.getAddress());
        System.out.println(p.getAge());
        System.out.println(p.getMoney());
    }

    private void setProperty(Object obj, Map<String, String[]> map) throws Exception {
        //内省
        //获取所有的属性封装到BeanInfo对象中
        BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
        //获取所有的属性描述器
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        //遍历
        for (PropertyDescriptor descriptor : propertyDescriptors) {
            //获取属性名
            String propertyName = descriptor.getName();
            //判断集合中是否包含当前属性名
            if (map.containsKey(propertyName)) {
                //包含
                //获取get方法
                Method getterMethod = descriptor.getReadMethod();
                //获取get方法返回值类型 String int double
                String returnTypeName = getterMethod.getReturnType().getSimpleName();

                //获取set方法
                Method setterMethod = descriptor.getWriteMethod();
                //获取mapvalue
                String value = map.get(propertyName)[0];//"18"
                //多分支语句
                switch (returnTypeName) {
                    case "int":
                        int age = Integer.parseInt(value);
                        //执行set方法
                        setterMethod.invoke(obj, age);
                        break;
                    case "double":
                        double v = Double.parseDouble(value);
                        //执行set方法
                        setterMethod.invoke(obj, v);
                        break;
                    case "float":
                        float v1 = Float.parseFloat(value);
                        //执行set方法
                        setterMethod.invoke(obj, v1);
                        break;
                    case "long":
                        long v2 = Long.parseLong(value);
                        //执行set方法
                        setterMethod.invoke(obj, v2);
                        break;
                    case "boolean":
                        boolean v3 = Boolean.parseBoolean(value);
                        //执行set方法
                        setterMethod.invoke(obj, v3);
                        break;
                    default:
                        //执行set方法
                        setterMethod.invoke(obj, value);
                        break;
                }
            }
        }
    }
}

BeanUtils工具类

可以看到直接使用内省还是有一些复杂的,因此大多时候可以使用apache的工具类:org.apache.commons.beanutils.BeanUtils,_(使用需要导入commons-beanutils-1.9.2.jarcommons-logging-1.1.1.jar两个包)_,这个是maven坐标:

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.3</version>
</dependency>

这个工具类中有个populate()方法:static populate(Object bean, Map<String,String[]> properties),Map中的key即String类型的要和JavaBean中的属性名一致才能完成封装(或者说和Getter/Setter方法的名字有关系,至少Spring的JdbcTemplate就是这样,修改属性名但不修改Getter/Setter方法依旧能完成封装)

接着一行代码就可以完成封装:

BeanUtils.populate(new Student,request.getParameterMap());
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java基础知识随身记
2018年11月12日20:51:35一、基础知识:1、JVM、JRE和JDK的区别:JVM(JavaVirtualMachine):java虚拟机,用于保证java的跨平台的特性。  java语言是跨平台,jvm不是跨平台的。JRE(JavaRuntimeEnvironment):java的运行环境,包括jvmjava的核心类
Wesley13 Wesley13
3年前
Java常见面试题总结
一、Java基础1、String类为什么是final的。2、HashMap的源码,实现原理,底层结构。3、说说你知道的几个Java集合类:list、set、queue、map实现类咯。。。4、描述一下ArrayList和LinkedList各自实现和区别5、Java中的队列都有哪些,有什么区别。6、反射中,Class.forName和
Wesley13 Wesley13
3年前
Java爬虫之JSoup使用教程
title:Java爬虫之JSoup使用教程date:201812248:00:000800update:201812248:00:000800author:mecover:https://imgblog.csdnimg.cn/20181224144920712(https://www.oschin
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
Java 语言的类、属性、方法各有哪些修饰符?简述各修饰符的区别
1、类的修饰符分为:可访问控制符和非访问控制符两种。可访问控制符是:公共类修饰符public非访问控制符有:抽象类修饰符abstract;最终类修饰符final     1、公共类修饰符public:Java语言中类的可访问控制符只有一个:public即公共的。每个Java程序的主类都必须是public类作为公共工
Wesley13 Wesley13
3年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
Wesley13 Wesley13
3年前
Java反射之对JavaBean的内省操作
上一篇我们说了Java反射之数组的反射应用(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fblog.csdn.net%2FOooops_%2Farticle%2Fdetails%2F100176965)这篇我们来模拟实现那些javabean的框架(BeanUtils)的基本操