Java高新技术3(框架,JavaBean与内省(Introspector))
1.Java框架(frame)
/* 通俗例子: 我做房子(框架)卖给用户住,由用户自己安装门窗和空调(用户自定义类/用户自定义其它信息) 用户需要使用我的房子(框架),把符合框架中结构的门窗插入进我提供的框架中. 框架与工具类区别: 框架调用用户提供的类 工具类被用户的类调用 示例: 利用反射运行指定的某个类中的main方法, 通过arg[0]来接收要运行的类名,也就是说 我已写好这个功能,而你要运行的类还不存在 我这个功能可以提前编译,你只需在运行时提供给我要运行的类即可. 框架要解决的核心问题: 我若干年前写的程序调用你若干年后写的程序->反射机制 为什么要用框架? 框架相当于半成品->也就是说提高开发效率 */模拟框架:利用反射机制读取配置文件
public class FrameMN { public static String loadProp()throws IOException{ Properties property=new Properties(); BufferedInputStream bis=null; InputStream is=null; try{ /* bis=new BufferedInputStream (new FileInputStream("config.properties"));//这里的根目录为工程名称(JavaEnhance) //"config.properties"相当于".\\config.properties" */ System.out.println(System.getProperty("java.class.path"));//classpath路径 /* bis=new BufferedInputStream(FrameMN.class.getClassLoader().getResourceAsStream ("com/itheima/day2/config.properties"));//将会在classpath+指定的路径(com/itheima/day2/config.properties) //下查找,com前面不能有/->将不再是相对路径 */ bis=new BufferedInputStream(FrameMN.class.getResourceAsStream("/com/itheima/day2/config.properties")); property.load(bis); return property.getProperty("className"); } finally{ if(bis!=null) bis.close(); } } public static void main(String[] args)throws Exception{ String className=loadProp(); Collection collections=(Collection)Class.forName(className).newInstance(); collections.add("3"); collections.add(2); System.out.println(collections);//[3, 2] System.out.println(System.getProperty("user.dir")); } }目录结构:
注意几点:
/* config.properties 该如何配置其位置? 1.相对路径 其实就是System.getProperty("user.dir"); 相对路径随时有可能在变,但是可以通过 System.getProperty("user.dir")来获取当前程序执行所在的相对路径. 但是如果这个文件不再当前路径下->找不到引发IO异常 2.绝对路径 从盘符开始路径:d:\abc\1.txt 不建议使用这样做缺点:用户没有d:盘符呢? 解决办法: 例如:把某个软件安装到某个目录->通过方法获取到其安装路径->在与内部的配置文件拼接形成绝对路径 一个错误: Frame.class.getClass() *//* 1.eclipse在你src下新建.java文件后它会自动编译成.class文件放在bin目录下 2.当在src下创建文件->eclipse将该文件拷贝一份到bin目录下 在src某个包下创建文件->eclipse将该文件拷贝一份到bin目录的相同包下 */ /* 注意: Class类: public InputStream getResourceAsStream(String name) 此方法委托此对象的类加载器。如果此对象通过引导类加载器加载, 则此方法将委托给 ClassLoader.getSystemResourceAsStream(java.lang.String)。 在委托前,使用下面的算法从给定的资源名构造一个绝对资源名: 如果 name 以 '/' 开始 ('\u002f'),则绝对资源名是 '/' 后面的 name 的一部分。 否则,绝对名具有以下形式: modified_package_name/name 其中 modified_package_name 是此对象的包名,该名用 '/' 取代了 '.' ('\u002e')。 例如: <截图> 配置文件和运行类在同一个包下: 1. getResourceAsStream("/com/itheima/day2/config.properties") 相当于调用ClassLoader.getResoureceAsStream("com/itheima/day2/config.properties") 2.getResourceAsStream("config.properties") ->com/itheima/day2/config.properties(此字节码对象的包名+指定的路径)-> ClassLoader.getResoureceAsStream("com/itheima/day2/config.properties") 配置文件和运行类不在同一包下: 1.在com.itheima.day2.resources下 依然用Class的getResourceAsStream("resource/com/itheima/day2/config.properties"); 2.如果在com.ithmeima.day1下 "/com/itheima/day1/config.properties" */
2.JavaBean与Introspector
/* JavaBean与内省(Introspector): 1.JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法 主要用于访问私有字段,且方法名符合某种命名规则. 2.如果在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例 通常称之为值对象(Value Object).这些信息在类中用私有字段来存储,如果读取货设置这些字段的值 则需要通过一些相应的方法来访问,这些方法该如何命名? JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量.如果方法 名为setId,意为设置id,至于你存到哪个变量上,用管吗?getId意为获取id,至于从哪个变量上得到的,用管吗? 去掉set前缀后,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小写. setId属性名->id isLast属性名->last setCPU属性名->CPU 总而言之:一个类被当做JavaBean使用时,JavaBean的属性是根据方法名推断出来的 它根本看不到java类内部的成员变量 一个符合JavaBean特点的类可以被当做普通类一样进行使用,但把它当做JavaBean用肯定需要带来一些额外的好处 好处: 1.在JavaEE开发中,经常使用JavaBean.很多环境要求按JavaBean方式进行操作 2.JDK提供了对JavaBean进行操作的一些API,这套API就称为内省. *///eclipse 4.3下自动生成getter和setter方法//JavaBean的属性是由getter或setter方法决定
//只要含有其中的一个方法就是JavaBean的属性
//User类继承Object的方法getClass,因此还有一个属性为classclass User{ private String userName;//字段 private String uName;//字段 private String CPU; private String controlProcessUnited; private int x; public int getX() { return x; } public void setX(int x) { this.x = x; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getuName() { return uName; } public void setuName(String uName) { this.uName = uName; } public String getCPU() { return CPU; } public void setCPU(String cPU) { CPU = cPU; } public String getControlProcessUnited() { return controlProcessUnited; } public void setControlProcessUnited(String controlProcessUnited) { this.controlProcessUnited = controlProcessUnited; } }对JavaBean内省操作:
用一个测试类:Car
package com.itheima.day2; import java.util.Date; public class Car { private String color; private int number;//轮胎个数 private Date birthday=new Date();//为了测试 设置级联属性 public Car(String color, int number) { super(); this.color = color; this.number = number; } public String getColor(){ return color; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public void setColor(String color) { this.color = color; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } }
package com.itheima.day2; import java.lang.reflect.Method; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; public class IntrospectorDemo { public static void main(String[] args)throws Exception{ // TODO 自动生成的方法存根 //方式一:使用反射操作JavaBean //获取颜色->color->color有一个单词第二个字母小写->推断出方法名getColor Car car=new Car("红色",4); Method method=car.getClass().getMethod("getColor"); Object retVal=method.invoke(car); System.out.println(retVal);//"红色" //方式二:使用内省操作JavaBean->不用再推断方法名 PropertyDescriptor pd=new PropertyDescriptor("color",car.getClass());//第一个参数属性名,第二个参数把哪一个类当成JavaBean类 method=pd.getReadMethod();//将获取到的getColor方法封装成Method对象 retVal=method.invoke(car); System.out.println(retVal);//"红色" method=pd.getWriteMethod();//将获取到的setColor方法封装成Method对象 method.invoke(car,"黑色"); System.out.println(car.getColor());//验证是否改掉//"黑色" //测试封装后的方法 System.out.println(getProperty("number",car));//4 setProperty("number",car,10); System.out.println(car.getNumber());//10 } //对上面的操作步骤进行封装提高复用性 public static Object getProperty(String property,Object obj) throws Exception{//获取指定对象的属性值 PropertyDescriptor pd=new PropertyDescriptor(property,obj.getClass()); Method method=pd.getReadMethod(); return method.invoke(obj); /*//方法二: BeanInfo bi=Introspector.getBeanInfo(obj.getClass());//在 Java Bean 上进行内省,了解其所有属性、公开的方法和事件,描述目标 bean 的 BeanInfo 对象。 PropertyDescriptor[] pdArrs=bi.getPropertyDescriptors();//获取到该JavaBean中所有属性信息,BeanInfo没有获取单个属性的方法 for(PropertyDescriptor pdArr : pdArrs) if(pdArr.getName().equals(property)){//获取到属性描述的属性名称(getName),遍历 Method method=pdArr.getReadMethod(); return method.invoke(obj); } return null;*/ } public static void setProperty(String Property,Object obj,Object value)throws Exception{ PropertyDescriptor pd=new PropertyDescriptor(Property,obj.getClass()); Method method=pd.getWriteMethod(); method.invoke(obj,value); } }
使用开源工具BeanUtils来操作JavaBean:
/* 使用开源BeanUtils来更方便操作JavaBean 1.从http://commons.apache.org/beanutils/下载commons-beanutils-1.8.3-bin.zip 2.将其中的.jar导入工程 3.不采用BuildPath->添加外部归档方式导入工程,这样做.jar并不在工程目录下(例如在d:\下) 一旦将工程拷贝到其它机器下,还需要把该.jar拷贝到d:\下,不然用不了 解决方式:在工程下新建lib文件夹->将.jar拷贝到lib下->Build Path */
/* public static String getProperty(Object bean,String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException public static void setProperty(Object bean,String name,Object value) throws IllegalAccessException, InvocationTargetException*/ public class BeanUtilsDemo { public static void main(String[] args)throws Exception { // TODO 自动生成的方法存根 Car car=new Car("black",20); //使用BeanUtils来set/get属性 String number=BeanUtils.getProperty(car,"number");//获取car对象color属性的值,注意返回值固定为String BeanUtils.setProperty(car,"color","red");//设置car对象number属性的值为4 BeanUtils.setProperty(car,"number","2");//传入2(自动装箱,内部需要拆箱)或"2"均可,"2"内部涉及到从String->int转换 System.out.println(car.getColor()+" "+car.getNumber()+"\n"); //级联属性设置与获取 BeanUtils.setProperty(car,"birthday.time","1000");//在Date类中一个public void setTime(long time)方法->属性time //通俗例子:设置person.head.face.eye.color System.out.println(BeanUtils.getProperty(car,"birthday.time")+"\n"); //JavaBean与Map相互转换 Map map=BeanUtils.describe(car);//会将JavaBean中 所有属性值 存入到Map中,前提是该属性值有对应get方法 System.out.println(map);//里面还有一个属性为字节码文件对象,也就是指定的JavaBean类 map=new HashMap(); map.put("color","blue"); map.put("number",2); map.put("birthday.time",200); BeanUtils.populate(car, map);//填充:将map中key对应javabean中的property,将其value赋给property System.out.println(car.getColor()+" "+car.getNumber()); System.out.println(BeanUtils.getProperty(car,"birthday.time")+"\n"); //PropertiesUtils工具类 PropertyUtils.setProperty(car,"number",13);//属性值的类型必须和Car中number类型一致均为int Object obj=PropertyUtils.getProperty(car, "number"); System.out.println(obj+" "+obj.getClass().getName());//说明上面的getProperty以Integer类型返回 } } /*不导入commons-logging-1.1.3.jar之前的运行结果:
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory 相当于有了电视机,但遥控器是第三方->电视机没法用 需要导入遥控器->下载commons-logging(通用日志)并build path*/
1: package cn.itcast.feature;
2:
3: import java.lang.reflect.InvocationTargetException;
4: import java.text.ParseException;
5: import java.text.SimpleDateFormat;
6: import java.util.Date;
7:
8: import org.apache.commons.beanutils.BeanUtils;
9: import org.apache.commons.beanutils.ConversionException;
10: import org.apache.commons.beanutils.ConvertUtils;
11: import org.apache.commons.beanutils.Converter;
12: import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;
13: import org.junit.Test;
14:
15: /*16: 虽然使用BeanUtils简化了代码量17: 但是BeanUtils默认只能帮你进行八大基本类型间的转换或18: String到八大基本类型转换.19: 如果我需要用到String->Date的转换,需要我们自定义转换器20: */21: public class CustomConvert {22: private Person p=new Person();23: @Test24: public void convertTest_1() throws IllegalAccessException, InvocationTargetException{25: BeanUtils.setProperty(p,"birthday","1980-3-1");26: System.out.println(p.getBirthday());27: }
28:
JUnit测试:2.这时候我们需要自定义转换器完成String->Date1: @Test2: public void convertTest_2() throws IllegalAccessException, InvocationTargetException{3: ConvertUtils.register(//注册一个自定义转换器完成String->Date转换4: new Converter() {5: @Override6: public Object convert(Class type, Object value) {7: // TODO Auto-generated method stub8: if (value == null)9: return null;10: if (!(value instanceof String))11: throw new ConversionException("不支持从"12: + value.getClass().getName() + "到"13: + type.getName() + "的转换");14: String valueStr = (String) value;
15: if ((valueStr = valueStr.trim()).equals(""))// 去除字符串首尾的空格,同时判断是否是空串16: return null;17:
18: // 开始转换工作,以上做的判断完全为了程序的健壮性19: SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");20: try {21: return sdf.parse(valueStr);// 该方法有异常声明但是我不能在convert方法上进行声明,只能try...catch22: // 因为该匿名子类复写的Converter中的convert方法上没有任何异常声明23: } catch (ParseException e) {24: // TODO Auto-generated catch block25: throw new RuntimeException(e);26: }
27:
28: }
29:
30: }, Date.class);31: BeanUtils.setProperty(p,"birthday","1980-3-1");32: System.out.println(p.getBirthday());33: }
34:
3.如果把BeanUtils.setProperty(p,"birthday","1980-3-1");换成BeanUtils.setProperty("birthday",1980-3-1);不能通过测试:4.使用已提供的Sting->Date的转换器:1: @Test2: public void convertTest_3() throws IllegalAccessException, InvocationTargetException{3: //使用API提供的转换器:DateLocaleConverter4: ConvertUtils.register(new DateLocaleConverter(),Date.class);5: BeanUtils.setProperty(p,"birthday","1980-3-1");6: System.out.println(p.getBirthday());7:
8: //但是该转换器有Bug,那就是传入一个空串9: BeanUtils.setProperty(p,"birthday","");10: System.out.println(p.getBirthday());11: }
5.最后来看下throw new RuntimeException(e);打印的异常信息:1: @Test2: //throw new RuntimeException(Throwable cause);3: /*用指定的原因和详细消息 (cause==null ? null :cause.toString())4: 构造一个新的运行时异常(它通常包含类和 cause 的详细消息)。5: */6: //下面我们故意让parse抛出ParseException看下打印信息7: public void ParseExceptionTest(){8: try {9: System.out.println(new SimpleDateFormat("yyyy/MM/dd").parse("2014-1-17"));10: } catch (ParseException e) {11: // TODO Auto-generated catch block12: throw new RuntimeException(e);13: }
14: }