背景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
PropertyUtils类中提供了各类API方法用来获取和设置上述三种类型的属性。在下面的程序片断中,假设在bean类中都定义了以下的方法:spring
访问基本属性
获取和设置simple属性很简单。在Javadocs中查看下面两个方法:
数据库
使用这两个方法,你能够动态地修改employee的name属性:编程
对于indexed(索引)属性,你有两种选择 - 你既能够在属性名后面添加方括号在里面放上一个下标,也能够在调用方法时将其做为一个独立参数:
数组
例如,你可使用下面两种方法设置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对象的某个属性的值?
String city = employee.getAddress("home").getCity();
使用PropertyUtils类中的等效机制被称为嵌套属性访问。使用这种方法,你将访问路径上的属性的名称用“.”拼接起来 --与你在JavaScript执行嵌套属性访问的方式很是类似。
String city = (String) PropertyUtils.getNestedProperty(employee, "address(home).city");
最后,方便起见,PropertyUtils提供了以下一组方法,它们接收simple、indexed和mapped属性的任意组合方法,支持任意层次的嵌套:
反射
相对而言,反射比内省更容易理解一点。用一句比较白的话来归纳,反射就是让你能够经过名称来获得对象(类,属性,方法)的技术。例如咱们能够经过类 名来生成一个类的实例;知道了方法名,就能够调用这个方法;知道了属性名就能够访问这个属性的值,仍是写两个例子让你们更直观的了解反射的使用方法:
上面的两个例子是比较经常使用方法。看到上面的例子就有人要发问了:为何要这么麻烦呢?原本一条语句就完成的事情干嘛要整这么复杂?没错,在上面的例 子中确实没有必要这么麻烦。不过你想像这样一个应用程序,它支持动态的功能扩展,也就是说程序不从新启动可是能够自动加载新的功能,这个功能使用一个具体 类来表示。首先咱们必须为这些功能定义一个接口类,而后咱们要求全部扩展的功能类必须实现我指定的接口,这个规定了应用程序和可扩展功能之间的接口规则, 可是怎么动态加载呢?咱们必须让应用程序知道要扩展的功能类的类名,好比是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方法,而后咱们就能够经过反射机制来 调用这些方法。下面咱们来看一个例子,这个例子把某个对象的全部属性名称和值都打印出来:
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等框架可能产生的内存泄露问题
增长方式以下:
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应用的生命周期适当时机 生效。