Java基础之内省

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());

以上是 Java基础之内省 的全部内容, 来源链接: utcz.com/z/393708.html

回到顶部