java内省机制及PropertyUtils使用方法

背景html

       通常状况下,在Java中你能够经过get方法轻松获取beans中的属性值。可是,当你事先不知道beans的类型或者将要访问或修改的属性名时,该怎么办?Java语言中提供了一些像java.beans.Introspector这 样类,实现了在运行时检测Java类并肯定属性get和set方法的名称,结合Java中的反射机制就能够调用这些方法了。然而,这些APIs使用起来比 较困难,而且将Java类中一些没必要要的底层结构暴露给了开发人员。BeanUtils包中的APIs试图简化动态获取和设置bean属性的过程。java

      BeanUtils包中的PropertyUtils类中的一些静态方法实现了上面的功能,稍后会详细介绍。首先,介绍一些有用的定义:ios

     JavaBean支持的属性类型通常能够划分红三类--标准的JavaBeans规范支持其中的一些,也有一部分只有BeanUtils包支持:web

  • Simple(单值)  --  单值或量,有个一能够访问或修改的属性。值的类型多是Java语言的原生类型(如:int型),简单的类(如:java.lang.String),或者一个复杂类的对象,这个类可能来自Java语言或者来自应用程序再或者来自应用程序中的一个类库。
  • Indexed(索 引)  --   索引的属性,属性中存放有序对象(都是同类型的)的集合,每一个对象均可以经过一个非负的整数值(或下标)来获取。另外,全部值的集合可使用一个数组来设 置或者获取。做为一个JavaBeans规范的扩展,BeanUtils包认为全部底层数据类型为java.util.List(或List的一个实现) 的属性均可以被索引。
  • Mapped(映射)  --  做为一个标准JavaBeans APIs的扩展,  BeanUtils包认为全部底层数据类型为java.util.Map的属性均可以被"映射"。你能够经过String类型的key值来设置或者获取对应的值。

      PropertyUtils类中提供了各类API方法用来获取和设置上述三种类型的属性。在下面的程序片断中,假设在bean类中都定义了以下的方法:spring

  1. public class Employee {  
  2.     public Address getAddress(String type);  
  3.     public void setAddress(String type, Address address);  
  4.     public Employee getSubordinate(int index);  
  5.     public void setSubordinate(int index, Employee subordinate);  
  6.     public String getFirstName();  
  7.     public void setFirstName(String firstName);  
  8.     public String getLastName();  
  9.     public void setLastName(String lastName); 

访问基本属性
      获取和设置simple属性很简单。在Javadocs中查看下面两个方法:
数据库

  • PropertyUtils.getSimpleProperty(Object bean, String name)
  • PropertyUtils.setSimpleProperty(Object bean, String name, Object value)

     使用这两个方法,你能够动态地修改employee的name属性:编程

        

  1. Employee employee = ...;  
  2. String firstName = (String) PropertyUtils.getSimpleProperty(employee, "firstName");  
  3. String lastName = (String) PropertyUtils.getSimpleProperty(employee, "lastName");  
  4.   
  5.  ... manipulate the values ...  
  6. PropertyUtils.setSimpleProperty(employee, "firstName", firstName);  
  7. PropertyUtils.setSimpleProperty(employee, "lastName", lastName); 

    对于indexed(索引)属性,你有两种选择 - 你既能够在属性名后面添加方括号在里面放上一个下标,也能够在调用方法时将其做为一个独立参数:
数组

  • PropertyUtils.getIndexedProperty(Object bean, String name)
  • PropertyUtils.getIndexedProperty(Object bean, String name, int index)
  • PropertyUtils.setIndexedProperty(Object bean, String name, Object value)
  • PropertyUtils.setIndexedProperty(Object bean, String name, int index, Object value)
      属性名的下标只能是整数常量。若是你想获取的项的索引是计算出来的,你能够将属性名和索引做为字符串组合起来。例如,你能够向下面这样作:
    Employee employee = ...;
    int index = ...;
    String name = "subordinate[" + index + "]";
    Employee subordinate = (Employee) PropertyUtils.getIndexedProperty(employee, name);

    Employee employee = ...;
    int index = ...;
    Employee subordinate = (Employee) PropertyUtils.getIndexedProperty(employee, "subordinate", index);

相似的,获取和设置mapped(映射)属性的方法一样有两对。与indexed(索引)不一样的是额外的属性是用括号括起来的(“(”和“)”)而不是方括号,而且获取和设置值时如同从底层的map中获取和设置值同样。
  • PropertyUtils.getMappedProperty(Object bean, String name)
  • PropertyUtils.getMappedProperty(Object bean, String name, String key)
  • PropertyUtils.setMappedProperty(Object bean, String name, Object value)
  • PropertyUtils.setMappedProperty(Object bean, String name, String key, Object value)

例如,你可使用下面两种方法设置employee的家庭住址:缓存

    Employee employee = ...;
    Address address = ...;
    PropertyUtils.setMappedProperty(employee, "address(home)", address);

    Employee employee = ...;
    Address address = ...;
    PropertyUtils.setMappedProperty(employee, "address", "home", address);
服务器


访问嵌套属性
      在上面的例子中,咱们假设你将bean做为第一个参数传入PropertyUtils方法,并但愿获取指定属性的值。然而,若是属性的值是一个Java对象,而且你但愿进一步获取这个Java对象的某个属性的值?

      例如,假设你事实上想要获取的值是employee家庭住址中的city属性。使用标准的Java编程技术直接获取bean的对应属性,你能够这样写:

   String city = employee.getAddress("home").getCity();


      使用PropertyUtils类中的等效机制被称为嵌套属性访问。使用这种方法,你将访问路径上的属性的名称用“.”拼接起来 --与你在JavaScript执行嵌套属性访问的方式很是类似。

  • PropertyUtils.getNestedProperty(Object bean, String name)
  • PropertyUtils.setNestedProperty(Object bean, String name, Object value)
      PropertyUtils中等效于上面的Java代码将是这样:

   String city = (String) PropertyUtils.getNestedProperty(employee, "address(home).city");


最后,方便起见,PropertyUtils提供了以下一组方法,它们接收simple、indexed和mapped属性的任意组合方法,支持任意层次的嵌套:

  • PropertyUtils.getProperty(Object bean, String name)
  • PropertyUtils.setProperty(Object bean, String name, Object value)
      你能够像这样使用:

    Employee employee = ...;
    String city = (String) PropertyUtils.getProperty(employee,"subordinate[3].address(home).city");

2、java反射和内省
概述;
1.什么是反射
反射就是在运行状态把 Java 类中的各类成分映射成相应相应的 Java 类,能够动态得获取全部的属性以及动态调用任意一个方法。
1).一段java代码在程序的运行期间会经历三个阶段:source-->class-->runtime
2).Class对象
在java中用一个Class对象来表示一个java类的class阶段
Class对象封装了一个java类定义的成员变量、成员方法、构造方法、包名、类名等。
2.反射怎么用
1).得到java类的各个组成部分,首先须要得到表明java类的Class对象
得到Class对象有如下三种方式:
Class.forname(className) 用于作类加载
obj.getClass() 用于得到对象的类型
类名.class 用于得到指定的类型,传参用
2).反射类的构造方法,得到实例
Class clazz = 类名.class;
Constuctor con = clazz.getConstructor(new Class[]{paramClazz1,paramClazz2,.....});
con.newInstance(params....);
3).反射类的成员方法
Method m = clazz.getMethod(methodName,new Class[]{paramClazz1,paramClazz2,.....});
m.invoke();
4).反射类的属性
Field field = clazz.getField(fieldName);
field.setAccessible(true);//设置为可访问
filed.setObject(value); //设置值
Object value = field.get(clazz); //得到值
Object staticValue = filed.get(Class); //得到静态值

二:内省
1.什么是内省
经过反射的方式操做JavaBean的属性,jdk提供了PropertyDescription类来操做访问JavaBean的属性,Beantils工具基于此来实现。
2.内省怎么用
1).操做一个属性
Object obj = new Object();
PropertyDescriptor pd = new PropertyDescriptor(propertyName,Class); //声明属性描述对象,一次只可描述一个属性
Method m = pd.getWriterMethod();//获取setter方法
m.invoke(obj,value);
Method m = pd.getReaderMethod();//获取getter方法
Object value = m.invoke(obj);
2).操做多个属性
BeanInfo bi = Instospector.getBeanInfo(beanClass);//获取Bean描述对象
PropertyDescriptor[] pds = bi.getPropertyDescriptors();//获取属性描述对象数组
拿到属性描述对象数组以后再循环数组,剩余的操做就跟"操做一个属性"相同了。

反射

相对而言,反射比内省更容易理解一点。用一句比较白的话来归纳,反射就是让你能够经过名称来获得对象(类,属性,方法)的技术。例如咱们能够经过类 名来生成一个类的实例;知道了方法名,就能够调用这个方法;知道了属性名就能够访问这个属性的值,仍是写两个例子让你们更直观的了解反射的使用方法:

  1. //经过类名来构造一个类的实例  
  2. ClassClasscls_str=Class.forName("java.lang.String");  
  3. //上面这句很眼熟,由于使用过JDBC访问数据库的人都用过J  
  4. Objectstr=cls_str.newInstance();  
  5. //至关于Stringstr=newString();  
  6.  
  7. //经过方法名来调用一个方法  
  8. StringmethodName="length";  
  9. Methodm=cls_str.getMethod(methodName,null);  
  10. System.out.println("lengthis"+m.invoke(str,null));  
  11. //至关于System.out.println(str.length());  

上面的两个例子是比较经常使用方法。看到上面的例子就有人要发问了:为何要这么麻烦呢?原本一条语句就完成的事情干嘛要整这么复杂?没错,在上面的例 子中确实没有必要这么麻烦。不过你想像这样一个应用程序,它支持动态的功能扩展,也就是说程序不从新启动可是能够自动加载新的功能,这个功能使用一个具体 类来表示。首先咱们必须为这些功能定义一个接口类,而后咱们要求全部扩展的功能类必须实现我指定的接口,这个规定了应用程序和可扩展功能之间的接口规则, 可是怎么动态加载呢?咱们必须让应用程序知道要扩展的功能类的类名,好比是test.Func1,当咱们把这个类名(字符串)告诉应用程序后,它就可使 用咱们第一个例子的方法来加载并启用新的功能。这就是类的反射,请问你有别的选择吗?

内省

内省是Java语言对Bean类属性、事件的一种缺省处理方法。例如类A中有属性name,那咱们能够经过getName,setName来获得其 值或者设置新的值。经过getName/setName来访问name属性,这就是默认的规则。Java中提供了一套API用来访问某个属性的 getter/setter方法,经过这些API可使你不须要了解这个规则,这些API存放于包java.beans中。

通常的作法是经过类Introspector来获取某个对象的BeanInfo信息,而后经过BeanInfo来获取属性的描述器 (PropertyDescriptor),经过这个属性描述器就能够获取某个属性对应的getter/setter方法,而后咱们就能够经过反射机制来 调用这些方法。下面咱们来看一个例子,这个例子把某个对象的全部属性名称和值都打印出来:

  1. /*  
  2. *Createdon2004-6-29  
  3. */  
  4.  
  5. packagedemo;  
  6.  
  7. importjava.beans.BeanInfo;  
  8. importjava.beans.Introspector;  
  9. importjava.beans.PropertyDescriptor;  
  10.  
  11. publicclassIntrospectorDemo{  
  12. Stringname;  
  13. publicstaticvoidmain(String[]args)throwsException{  
  14. IntrospectorDemodemo=newIntrospectorDemo();  
  15. demo.setName("WinterLau");  
  16.  
  17. //若是不想把父类的属性也列出来的话,  
  18. //那getBeanInfo的第二个参数填写父类的信息  
  19. BeanInfobi=Introspector.getBeanInfo(demo.getClass(),Object.class);  
  20. PropertyDescriptor[]props=bi.getPropertyDescriptors();  
  21. for(inti=0;i<props.length;i++){  
  22. System.out.println(props[i].getName()+"="+  
  23. props[i].getReadMethod().invoke(demo,null));  
  24. }  
  25.  
  26. }  
  27.  
  28. publicStringgetName(){  
  29. returnname;  
  30. }  
  31.  
  32. publicvoidsetName(Stringname){  
  33. this.name=name;  
  34. }  
  35. }  

Web开发框架Struts中的FormBean就是经过内省机制来将表单中的数据映射到类的属性上,所以要求FormBean的每一个属性要有 getter/setter方法。但也并不老是这样,什么意思呢?就是说对一个Bean类来说,我能够没有属性,可是只要有getter/setter方 法中的其中一个,那么Java的内省机制就会认为存在一个属性,好比类中有方法setMobile,那么就认为存在一个mobile的属性,这样能够方便 咱们把Bean类经过一个接口来定义而不用去关心具体实现,不用去关心Bean中数据的存储。好比咱们能够把全部的getter/setter方法放到接 口里定义,可是真正数据的存取则是在具体类中去实现,这样可提升系统的扩展性。

总结

将Java的反射以及内省应用到程序设计中去能够大大的提供程序的智能化和可扩展性。有不少项目都是采起这两种技术来实现其核心功能,例如咱们前面 提到的Struts,还有用于处理XML文件的Digester项目,其实应该说几乎全部的项目都或多或少的采用这两种技术。在实际应用过程当中两者要相互 结合方能发挥真正的智能化以及高度可扩展性。


3、缺点及优化

在web.xml中注册IntrospectorCleanupListener监听器以解决struts等框架可能产生的内存泄露问题

增长方式以下:

  1.     <listener>  
  2.         <listener-class>  
  3.             org.springframework.web.util.IntrospectorCleanupListener  
  4.         </listener-class>  
  5.     </listener>  
[html] view plain copy 在CODE上查看代码片派生到个人代码片
  1.     <listener>  
  2.         <listener-class>  
  3.             org.springframework.web.util.IntrospectorCleanupListener  
  4.         </listener-class>  
  5.     </listener>  

org.springframework.web.util.IntrospectorCleanupListener源代码中对其的解释以下:

        Listener that flushes the JDK's JavaBeans Introspector cache on web app shutdown. Register this listener in your web.xml to guarantee proper release of the web application class loader and its loaded classes.

        在Web应用程序关闭时IntrospectorCleanupListener将会刷新JDK的JavaBeans的Introspector缓存。在 你的web.xml中注册这个listener来确保Web应用程序的类加载器以及其加载的类正确的释放资源。

        If the JavaBeans Introspector has been used to analyze application classes, the system-level Introspector cache will hold a hard reference to those classes. Consequently, those classes and the web application class loader will not be garbage-collected on web app shutdown! This listener performs proper cleanup, to allow for garbage collection to take effect.      

       若是JavaBeans的Introspector已被用来分析应用程序类,系统级的Introspector缓存将持有这些类的一 个硬引用。所以,这些类和Web应用程序的类加载器在Web应用程序关闭时将不会被垃圾收集器回收!而 IntrospectorCleanupListener则会对其进行适当的清理,已使其可以被垃圾收集器回收。

       Unfortunately, the only way to clean up the Introspector is to flush the entire cache, as there is no way to specifically determine the application's classes referenced there. This will remove cached introspection results for all other applications in the server too.

       不幸的是,惟一可以清理Introspector的方法是刷新整个Introspector缓存,没有其余办法来确切指定应用程序所引用的类。这将删除全部其余应用程序在服务器的缓存的Introspector结果。

       spring's beans infrastructure within the application, as Spring's own introspection results cache will immediately flush an analyzed class from the JavaBeans Introspector cache and only hold a cache within the application's own ClassLoader. Although Spring itself does not create JDK Introspector leaks, note that this listener should nevertheless be used in scenarios where the Spring framework classes themselves reside in a 'common' ClassLoader (such as the system ClassLoader). In such a scenario, this listener will properly clean up Spring's introspection cache.

       请注意,在使用Spring内部的bean机制时,不须要使用此监听器,由于Spring本身的introspection results cache将会当即刷新被分析过的JavaBeans Introspector cache,而仅仅会在应用程序本身的ClassLoader里面持有一个cache。虽然Spring自己不产生泄漏,注意,即便在Spring框架的 类自己驻留在一个“共同”类加载器(如系统的ClassLoader)的状况下,也仍然应该使用使用 IntrospectorCleanupListener。在这种状况下,这个IntrospectorCleanupListener将会妥善清理 Spring的introspection cache。

       Application classes hardly ever need to use the JavaBeans Introspector directly, so are normally not the cause of Introspector resource leaks. Rather, many libraries and frameworks do not clean up the Introspector: e.g. Struts and Quartz.

       应用程序类,几乎不须要直接使用JavaBeans Introspector,因此,一般都不是Introspector resource形成内存泄露。相反,许多库和框架,不清理Introspector,例如: Struts和Quartz。

       Note that a single such Introspector leak will cause the entire web app class loader to not get garbage collected! This has the consequence that you will see all the application's static class resources (like singletons) around after web app shutdown, which is not the fault of those classes!

        须要注意的是一个简单Introspector泄漏将会致使整个Web应用程序的类加载器不会被回收!这样作的结果,将会是在web应用程序关闭时,该应 用程序全部的静态类资源(好比:单实例对象)都没有获得释放。而致使内存泄露的根本缘由其实并非这些未被回收的类!

This listener should be registered as the first one in web.xml, before any application listeners such as Spring's ContextLoaderListener. This allows the listener to take full effect at the right time of the lifecycle. 

       IntrospectorCleanupListener应该注册为web.xml中的第一个Listener,在任何其余 Listener以前注册,好比在Spring's ContextLoaderListener注册以前,才能确保IntrospectorCleanupListener在Web应用的生命周期适当时机 生效。

相关文章
相关标签/搜索