Java基础加强第三讲 内省

内省

内省的概述

内省对应的英文单词为IntroSpector,它主要用于对JavaBean进行操作。那什么又是JavaBean呢?

JavaBean的概述

JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果一个Java类中的一些方法符合某种命名规则,则可以把它当作JavaBean来使用。
那么问题出来了,一个JavaBean可以当做普通Java类来使用吗?一个普通Java类可以当做JavaBean来使用吗?答案是一个JavaBean可以当做普通Java类来使用,但一个普通Java类不一定可以当做JavaBean来使用。
如果要在两个模块之间传递多个信息,那么可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉get/set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的,如果剩余部分的第二个字母是大写的,则把剩余部分的首字母保持原样。
在这里插入图片描述
总之,一个类被当作JavaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到Java类内部的成员变量。

JavaBean带来的好处

一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean!好处如下:

  • 在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!
  • 开发框架时,经常需要使用Java对象的属性来封装程序的数据,每次都使用反射技术完成此类操作过于麻烦,所以sun公司开发了一套API,专门用于操作Java对象的属性;
  • JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套API操作JavaBean比用普通类的方式更方便。

内省综合案例

内省访问JavaBean的属性有两种方式:

  1. 通过PropertyDescriptor类来操作JavaBean的属性;
  2. 通过Introspector类获得JavaBean对象的BeanInfo,然后通过BeanInfo来获取属性的描述器类(PropertyDescriptor),通过这个属性描述器类就可以获取某个属性对应的getter/setter方法,然后通过反射机制来调用这些方法。

首先定义一个ReflectPoint类,使其符合JavaBean的定义。

package cn.liayun.introspector;

public class ReflectPoint {
	private int x;
	public int y;
	public String str1 = "ball";
	public String str2 = "basketball";
	public String str3 = "liayun";

	public ReflectPoint(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}

	@Override
	public String toString() {
		return str1 + ":" + str2 + ":" + str3;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ReflectPoint other = (ReflectPoint) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}

}

我首先直接new一个PropertyDescriptor(属性描述器)对象的方式来让大家了解JavaBean API的价值,先用一段代码读取JavaBean的属性,然后再用一段代码设置JavaBean的属性。
在这里插入图片描述
然后,我再演示用Eclipse将读取属性和设置属性的流水帐代码分别抽取成方法。
在这里插入图片描述
最后,我采用遍历BeanInfo的所有属性方式来查找和设置某个ReflectPoint对象的x属性。在程序中把一个类当作JavaBean来看,就是调用IntroSpector.getBeanInfo方法,得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息。
在这里插入图片描述
得到BeanInfo最好采用obj.getClass()方式,而不要采用类名.class方式,这样程序更通用。

使用BeanUtils工具包来操纵JavaBean

Sun公司的内省API过于繁琐,所以Apache组织结合很多实际开发中的应用场景开发了一套简单、易用的API操作Bean的属性,它就是BeanUtils工具包,在BeanUtils中可以直接进行类型的自动转换。BeanUtils工具包中的常用类有:
在这里插入图片描述

BeanUtils工具包下载

  1. 登录官网进行下载;
  2. 点击Download;
  3. 点击commons-beanutils-1.9.2-bin.zip(本人使用的是这个版本,我重新登录官网看了一下,现在最新的版本是commons-beanutils-1.9.3-bin.zip了)进行下载就OK了,若要查看源码也可下载commons-beanutils-1.9.2-src.zip。

使用BeanUtils类

首先需要在项目中导入commons-beanutils-1.9.2.jar包(PS:把此jar包复制到项目的lib文件夹下,然后右击包→Build Path→Add to Build Path)
在这里插入图片描述
最后,如果jar包导入到项目成功的话,就会在所导入的jar包前显示一个个小奶瓶哟!
在这里插入图片描述
注意:commons-beanutils-1.9.2.jar要与commons-logging-1.2.Jar共用。
在前面内省例子的基础上,用BeanUtils类先get原来设置好的属性,再将其set为一个新值。
在这里插入图片描述
注意,BeanUtils操作属性是以String类型设置进去的,自动进行类型转换(String→int),但实际上JavaBean的属性是int,获取属性时也以String返回。所以,可得出结论,get属性时返回的结果为字符串,set属性时可以接受任意类型的对象,但通常使用字符串。
BeanUtils还支持属性的级联操作(属性链),即BeanUtils可以操作属性的属性。若类ReflectPoint中有属性birthday,并有其setter/getter方法如下:
在这里插入图片描述
BeanUtils支持属性的级联操作如下,
在这里插入图片描述

自定义转换器

BeanUtils框架对数据进行转换时,默认只支持8种基本数据类型的转换,碰到复杂类型无法转换!例如要将一个日期字符串赋值到JavaBean的一个Date属性上时,就无法转换了,这个时候就只能给其注册一个日期转换器了。
这里我会举一个案例,有Person类如下:

package cn.liayun.beanutils;

import java.util.Date;

public class Person { //JavaBean
	
	private String name; //字段
	private String password; //字段
	private int age; //字段
	private Date birthday;
	
	public Date getBirthday() {
		return birthday;
	}

	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}

	public String getAb() {
		return null;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
}

使用BeanUtils框架对数据进行转换时,下面的代码是有问题的。

package cn.liayun.beanutils;

import java.lang.reflect.InvocationTargetException;

import org.apache.commons.beanutils.BeanUtils;

public class BeanUtilsDemo {

	public static void main(String[] args) throws IllegalAccessException, InvocationTargetException {
		test();
	}
	
	public static void test() throws IllegalAccessException, InvocationTargetException {
		String name = "aaaa";
		String password = "123";
		String age = "34";
		String birthday = "1980-09-09";
		
		Person p = new Person();
		BeanUtils.setProperty(p, "name", name);
		BeanUtils.setProperty(p, "password", password);
		BeanUtils.setProperty(p, "age", age);//BeanUtils默认只支持8种基本数据类型的转换,碰到复杂类型,就无法转换了。
		BeanUtils.setProperty(p, "birthday", birthday);//BeanUtils默认只支持8种基本数据类型的转换,碰到复杂类型,就无法转换了,
		                                               //只能给其注册一个日期转换器。
		
		System.out.println(p.getName());
		System.out.println(p.getPassword());
		System.out.println(p.getAge());
		System.out.println(p.getBirthday());

	}

}

运行以上代码,Eclipse控制台打印如下异常,这正好验证了一开始提出的结论。
在这里插入图片描述
为了能让日期字符串赋值到JavaBean的Date类型的属性上,我们给BeanUtils注册一个日期转换器。

package cn.liayun.beanutils;

import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;

public class BeanUtilsDemo {

	public static void main(String[] args) throws IllegalAccessException, InvocationTargetException {
		test();
	}
	
	public static void test() throws IllegalAccessException, InvocationTargetException {
		String name = "aaaa";
		String password = "123";
		String age = "34";
		String birthday = "1980-09-09";
		
		//为了能让日期赋值到JavaBean的birthday属性上,我们给BeanUtils注册一个日期转换器。
		ConvertUtils.register(new Converter() {

			@Override
			public <T> T convert(Class<T> type, Object value) {
				if (value == null) {
					return null;
				}
				if (!(value instanceof String)) {
					throw new ConversionException("只支持String类型的转换!");
				}
				String str = (String) value;
				if (str.trim().equals("")) {
					return null;
				}
				
				SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
				try {
					return (T) df.parse(str);
				} catch (ParseException e) {
					/*
	                 * 出现异常,一定要通知上一层程序,抛个异常给上一层
	                 * 不能打印在控制台上
	                 */
	                // e.printStackTrace(); // error
	                // throw new RuntimeException(); // error
					throw new RuntimeException(e);//异常链不能断,
					                              //必须把原来的异常信息封装进去,抛出异常给上一层,上一层就会知道到底出了什么问题
				}
			}
		}, Date.class);
		
		Person p = new Person();
		BeanUtils.setProperty(p, "name", name);
		BeanUtils.setProperty(p, "password", password);
		BeanUtils.setProperty(p, "age", age);//BeanUtils默认只支持8种基本数据类型的转换,碰到复杂类型,就无法转换了。
		BeanUtils.setProperty(p, "birthday", birthday);//BeanUtils默认只支持8种基本数据类型的转换,碰到复杂类型,就无法转换了,
		                                               //只能给其注册一个日期转换器。
		
		System.out.println(p.getName());
		System.out.println(p.getPassword());
		System.out.println(p.getAge());
		/*
		Date date = p.getBirthday();
		System.out.println(date.toLocaleString());
		*/
		System.out.println(p.getBirthday());

	}

}

以上代码中,我们要格外注意异常的处理原则:出现异常,一定要通知上一层程序,抛个异常给上一层,不能打印在控制台上;且异常链不能断,必须把原来的异常信息封装进去,抛出异常给上一层,上一层就会知道到底出了什么问题
在这里插入图片描述
在我们自定义转换器之前,应该看看该工具类有没有给我们提供相应的转换器,查阅API帮助文档,发现该工具类给我们提供了一个转换器DateLocaleConverter。不妨用一下该日期转换器。

package cn.liayun.beanutils;

import java.lang.reflect.InvocationTargetException;
import java.util.Date;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;

public class BeanUtilsDemo {

	public static void main(String[] args) throws IllegalAccessException, InvocationTargetException {
		test();
	}
	
	public static void test() throws IllegalAccessException, InvocationTargetException {
		String name = "aaaa";
		String password = "123";
		String age = "34";
		String birthday = "1980-09-09";
		
		                       //此转换器有bug,写的不健壮
		ConvertUtils.register(new DateLocaleConverter(), Date.class);
		
		Person p = new Person();
		BeanUtils.setProperty(p, "name", name);
		BeanUtils.setProperty(p, "password", password);
		BeanUtils.setProperty(p, "age", age);//BeanUtils默认只支持8种基本数据类型的转换,碰到复杂类型,就无法转换了。
		BeanUtils.setProperty(p, "birthday", birthday);//BeanUtils默认只支持8种基本数据类型的转换,碰到复杂类型,就无法转换了,
		                                               //只能给其注册一个日期转换器。
		
		System.out.println(p.getName());
		System.out.println(p.getPassword());
		System.out.println(p.getAge());
		/*
		Date date = p.getBirthday();
		System.out.println(date.toLocaleString());
		*/
		System.out.println(p.getBirthday());

	}

}

不过,DateLocaleConverter转换器虽然好使,但其有一个严重的bug,对于
在这里插入图片描述
这样的空字符串就会报异常,所报异常如下,即没有我们自定义的转换器健壮。
在这里插入图片描述

使用BeanUtils类操作Map集合

BeanUtils不仅可以操作JavaBean,还可以操作Map集合,因为Map集合的key就相当于JavaBean的属性,并且Map集合可以和JavaBean进行相互转换,BeanUtils提供了这种转换的方式。
在这里插入图片描述
BeanUtils类中有一个很厉害的populate方法,可以用Map集合中的值,填充JavaBean的属性。但要注意一点,Map集合中的键的名称要与JavaBean的属性名称相一致。

package cn.liayun.beanutils;

import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;

public class BeanUtilsDemo {

	public static void main(String[] args) throws IllegalAccessException, InvocationTargetException {
		test();
	}
	
	public static void test() throws IllegalAccessException, InvocationTargetException {
		Map map = new HashMap();
		map.put("name", "aaa");
		map.put("password", "123");
		map.put("age", "23");
		map.put("birthday", "1980-09-09");
		
		                        //此转换器有bug,写的不健壮
		ConvertUtils.register(new DateLocaleConverter(), Date.class);
		Person bean = new Person();
		BeanUtils.populate(bean, map); //用Map集合中的值,填充JavaBean的属性,注意,Map集合中的键的名称要与JavaBean的属性名称相一致
		                               //把Map集合中的数据填充到哪个JavaBean上面去。
		
		System.out.println(bean.getName());
		System.out.println(bean.getPassword());
		System.out.println(bean.getAge());
		System.out.println(bean.getBirthday());

	}

}

使用PropertyUtils类

介绍完BeanUtils类之后,我最后介绍一个PropertyUtils类。先用PropertyUtils类先get原来设置好的属性,再将其set为一个新值。
在这里插入图片描述
若设置属性时,是这样做的,
在这里插入图片描述
可以看到会报异常——java.lang.IllegalArgumentException: argument type mismatch。而且,下面代码会输出java.lang.Integer。
在这里插入图片描述
所以在这里,我们可得出结论,get属性时返回的结果为该属性本来的类型,set属性时只接受该属性本来的类型。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李阿昀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值