zoukankan      html  css  js  c++  java
  • Java基础扫盲系列(三)— Java内省技术

    前言

    Java内省技术属于Java基础体系的的一部分,但是很多人都不甚了解。笔者也是在学习Spring源码的过程中遇到该技术模块的。为了完善技术体系,本文将全面的学习该技术。在提到Java内省技术,就不得不说Java的反射和JavaBeans技术,相信这两点大家应该都非常熟悉。本文将会从以下几个方面学习Java内省:

    • Java内省和JavaBeans技术
    • Java内省和反射技术的关系
    • Java内省的API介绍
    • Java内省实战

    ### Java内省和JavaBeans技术

    在JavaBeans 101的规范中对于JavaBeans是这样定义的:

    “A Java Bean is a reusable software component that can be manipulated visually in a builder tool.”

    JavaBeans是一种可重复使用的软件组件,并且可以通过可视化的工具的方式进行操作。

    如在NetBeans IDE中创建一个Button按钮的JavaBeans如下:

    以上的Pannel面板中的Button按钮就是一个JavaBean,满足可重复使用的定义。通过点击该按钮将会展示该Button的可视化设计器:

    在设计器中可以编辑该Button的Property和Event。这样就完成了一个JavaBean的创建。

    Note:
    关于NetBeans的创建可视化Button Bean的过程这里不再详细介绍,如果感兴趣可以参考:The Java™ Tutorials

    当然这是Java Client的可视化编程,可以这样做。但是对于Java服务端应用而言,却无法这样使用。且站在程序猿的角度,更偏向使用coding方式解决问题。

    使用coding方式编写JavaBean组件也非常容易,不需要实现任何借口或者任何特定的工具。但是JavaBean有规范约束,主要从Propeties,Method和Events三个方面约定。只有这样遵循这些规定,Bean的构建工具才可以探测检视JavaBean,再以可视化方式展示JavaBean。

    Properties

    属性由公共的Setter和Getter方法定义。对于可视化工具(如NetBeans),将能识别方法名称,然后将该属性展示。如下:

    public class FaceBean {
        private int mMouthWidth = 90;
    
        // getter for mMouthWidth
        public int getMouthWidth() {
            return mMouthWidth;
        }
        
        // setter for mMouthWidth
        public void setMouthWidth(int mw) {
            mMouthWidth = mw;
        }
    }
    

    关于属性的种类非常多,如:Indexed Properties,Bound Properties等等。如果感兴趣可以参考:The Java™ Tutorials

    只需要掌握一点,属性由Setter和Getter标识定义。

    Methods

    方法即是除了属性的Setter和Getter之外的公共方法。如NetBeans的可视化工具将能检视JavaBeans中的方法。

    内省

    在大致介绍完JavaBeans后,再来看内省就简单多了。Java内省是JavaBeans技术架构体系中的一部分,这点可以参考:JavaBeans Spec

    其中是这样描述内省:

    At runtime and in the builder environment we need to be able to figure out which properties, events, and methods a Java Bean supports. We call this process introspection.

    即运行时获取JavaBean的properties,events和methods的过程称为Java内省。简而言之,即检视JavaBean内部的信息,如方法,属性,事件。主要用来运行时获取JavaBean的内部信息。


    ### Java内省和反射技术的关系

    学习上节关于Java内省技术的介绍,有些读者应该很快联想到Java的反射技术。Java内省和反射技术很类似(反射也具有运行获取Java对象的类型信息)。

    文档中是这样描述Java内省和Java反射:

    We therefore provide a composite mechanism. By default we will use a low level reflection mechanism to study the methods supported by a target bean and then apply simple design pat- terns to deduce from those methods what properties, events, and public methods are supported. However, if a bean implementor chooses to provide a BeanInfo class describing their bean then this BeanInfo class will be used to programmatically discover the beans behaviour.

    为了能够提供JavaBean内部的检视,提供了一组机制用于处理获取JavaBean的内部信息。默认使用低级别的反射机制获取JavaBean的信息。但是对于指定了JavaBean的对应BeanInfo类时,将从BeanInfo中描述的JavaBean信息获取JavaBean的行为,这时将不会再使用反射机制。

    所以Java内省和Java反射存在以下关系:

    • Java反射是一组较低级别的API,使用起来比较复杂。而Java内省提供了一套比较简单和有语义的API用于操作JavaBean;
    • Java内省机制更加简单易用;
    • 由于Java内省底层默认使用Java反射机制,所以同样有Java反射机制带来的性能问题;

    ### Java内省的API介绍

    1.Introspector

    The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean

    Introspector译名是内省器,它提供了一系列的方法内省获取JavaBean的properties,method和events。

    Introspector底层将寻找JavaBean对应的BeanInfo,如果寻找不到,将使用反射机制分析JavaBean。

    其中提供getBeanInfo方法可以获取目标JavaBean对应的BeanInfo方法。

    2.BeanInfo

    BeanInfo是描述JavaBean的信息的一个对象。其中描述了JavaBean的properties,method和event等。但是很多时候是不提供明确的BeanInfo,需要由上述的Introspector使用反射机制获取JavaBean信息生成对应的BeanInfo。

    3.BeanDescriptor

    A BeanDescriptor provides global information about a "bean", including its Java class, its displayName, etc.

    BeanDescriptor是对JavaBean的全局信息描述,描述其Class,名称等。

    4.PropertyDescriptor

    A PropertyDescriptor describes one property that a Java Bean exports via a pair of accessor methods.

    PropertyDescriptor通过一对Setter和Getter方法导出JavaBean的property,是对Property的描述。
    PropertyDescriptor提供了获取Setter和Getter方法的接口,获取Property的名称等接口。

    5.MethodDescriptor

    A MethodDescriptor describes a particular method that a Java Bean supports for external access from other components.

    MethodDescriptor用于描述JavaBean中的公共方法,它提供获取该方法对象Method,方法的参数等API。

    Java内息机制还提供了一些其他的API,这里只介绍一些常用的重点接口。关于其他的接口,读者有兴趣可以参考:java.beans

    下节将通过coding方式学习内息,如何使用这些API避免复杂的反射方式来操作JavaBean。


    ### Java内省实战

    首先编写JavaBean类Person:

    /**
     * person for java bean.
     *
     * @author huaijin
     */
    public class Person {
    
        private String name;
        private int age;
        private List<String> address = new ArrayList<>();
    
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setAddress(String[] address) {
            this.address.addAll(Arrays.asList(address));
        }
    
        public String[] getAddress() {
            String[] strs = new String[this.address.size()];
            return this.address.toArray(strs);
        }
    
        public void setAddress(int index, String value) {
            this.address.add(index, value);
        }
    
        public String getAddress(int index) {
            return this.address.get(index);
        }
    }
    

    然后再使用上述介绍的API分别来操作JavaBean,内省:

    public class IntrospectPerson {
    
        public static void main(String[] args) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
            Person person = new Person();
            person.setName("huaijin");
            person.setAge(18);
            List<String> address = new ArrayList<>();
            address.add("hangzhou");
            address.add("anhui");
            person.setAddress(address.toArray(new String[address.size()]));
    
            BeanInfo personBeanInfo = Introspector.getBeanInfo(Person.class, Introspector.USE_ALL_BEANINFO);
            PropertyDescriptor[] propertyDescriptors = personBeanInfo.getPropertyDescriptors();
    		// 获取PropertyDescriptor,读写属性
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                System.out.println("property name: " + propertyDescriptor.getName());
                System.out.println("read before writing: " + propertyDescriptor.getReadMethod().invoke(person, null));
                Class<?> propertyClass = propertyDescriptor.getPropertyType();
                if (propertyClass == String.class) {
                    propertyDescriptor.getWriteMethod().invoke(person, "lxy");
                } else if (propertyClass == int.class) {
                    propertyDescriptor.getWriteMethod().invoke(person, 28);
                }
                if (propertyDescriptor instanceof IndexedPropertyDescriptor) {
                    IndexedPropertyDescriptor indexedPropertyDescriptor =
                            (IndexedPropertyDescriptor) propertyDescriptor;
                    indexedPropertyDescriptor.getIndexedWriteMethod().invoke(person, 0, "2dfire");
                }
                System.out.println("read after writing: " + propertyDescriptor.getReadMethod().invoke(person, null) + "
    ");
            }
            // 获取MethodDescriptor,可以操作JavaBean
            MethodDescriptor[] methodDescriptors = personBeanInfo.getMethodDescriptors();
            for (MethodDescriptor methodDescriptor : methodDescriptors) {
                System.out.println("methodName: " + methodDescriptor.getName());
            }
        }
    }
    

    可以看出,利用Introspector、BeanInfo和PropertyDescriptor等比使用反射机制带来更多的简便性。

    在日常开发中,或许经常使用Java内省而不知,下面就通过日常开发中使用到的案例进一步熟悉Java内息。读者或许经常使用Spring的BeanUtils工具类拷贝JavaBean的属性到另一个目标对象中。BeanUtils.copyProperties内部实现就使用了Java内省机制。

    private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
    		throws BeansException {
    	// 断言源对象和目标对象不能为空
    	Assert.notNull(source, "Source must not be null");
    	Assert.notNull(target, "Target must not be null");
    
    	// 获取目标对象的类型
    	Class<?> actualEditable = target.getClass();
    	if (editable != null) {
    		if (!editable.isInstance(target)) {
    			throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
    					"] not assignable to Editable class [" + editable.getName() + "]");
    		}
    		actualEditable = editable;
    	}
    	// 获取目标对象的所有Properties
    	PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
    	List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
    	// 遍历目标对象的Properties
    	for (PropertyDescriptor targetPd : targetPds) {
    		// 获取Property的Setter方法
    		Method writeMethod = targetPd.getWriteMethod();
    		// 如果Setter方法不为空且Property没有被忽略
    		if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
    			// 获取源对象对应的Property
    			PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
    			// 如果源对象中存在该Property
    			if (sourcePd != null) {
    				// 获取源对象的Property对应的Getter方法
    				Method readMethod = sourcePd.getReadMethod();
    				// 如果读方法不为空,且Getter的返回类型和目标对象的Setter方法参数类型匹配
    				if (readMethod != null &&
    						ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
    					try {
    						// 如果Getter方法非公共可见,则设置Getter方法可访问
    						if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
    							readMethod.setAccessible(true);
    						}
    						// 反射调用源对象的Getter方法,获取属性值
    						Object value = readMethod.invoke(source);
    						// 如果目标对象的Setter方法非公共可见,设置其可访问
    						if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
    							writeMethod.setAccessible(true);
    						}
    						// 反射调用目标对象的Setter方法,设置属性值
    						writeMethod.invoke(target, value);
    					}
    					catch (Throwable ex) {
    						throw new FatalBeanException(
    								"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
    					}
    				}
    			}
    		}
    	}
    }
    

    以上的逻辑便是BeanUtils中利用JavaBeans中的内省技术实现对象的属性拷贝。

    从源码中不难返现,其中使用了大量的反射机制。读者应该都知道,大量使用反射必然有一定的性能问题。性能在于:运行时分析类型信息,如Field、Method等,非常消耗性能。这些信息本应该在编译期间解决,在运行时获取虽然带来更大的灵活性,但是也带来不可避免的性能问题。

    对于反射的性能问题,常见的手段遍是缓存分析结果信息,如缓存Method,Field等。这种手段在Spring和Java动态代理中都有体现。下面再使用Spring内部的BeanWrapper优化使用JavaBean的内省技术来分析其对性能的优化方式。

    @Override
    public PropertyDescriptor[] getPropertyDescriptors() {
    	// 从缓存中获取PropertyDescriptor
    	return getCachedIntrospectionResults().getPropertyDescriptors();
    }
    
    private CachedIntrospectionResults getCachedIntrospectionResults() {
    	// 断言被包装的目标Bean不为空
    	Assert.state(getWrappedInstance() != null, "BeanWrapper does not hold a bean instance");
    	// 如果缓存的内省结果为空则进行内省,并缓存
    	if (this.cachedIntrospectionResults == null) {
    		this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
    	}
    	return this.cachedIntrospectionResults;
    }
    

    Spring中的BeanWrapper对于Java内省的优化,是遵循Java反射的优化的策略的。即缓存PropertyDescriptor:

    private final Map<String, PropertyDescriptor> propertyDescriptorCache;
    

    ### 总结 Java内省技术提供一套高级API的机制针对JavaBean操作,底层默认通过Java反射机制获取JavaBean的信息。内省机制在Spring中有很多使用场景,如BeanUtils,BeanWrapper等。在日常开发中也可以使用简化反射机制带来的复杂性。
    参考

    Java introspection and reflection
    java.beans
    How to use Reflection, Introspection and Customization in JavaBeans
    JavaBeans(TM) Specification 1.01 Final Release
    Trail: JavaBeans(TM)

  • 相关阅读:
    严重: Parse error in application web.xml file at jndi:/localhost/ipws/WEBINF/web.xml java.lang.NoSuchMethodException: org.apache.catalina.deploy.WebXml
    Failed to install .apk on device 'emulator5554': timeout解决方法
    java.lang.NoClassDefFoundError:org.jsoup.Jsoup
    Conversion to Dalvik format failed: Unable to execute dex:解决方法
    apache Digest: generating secret for digest authentication ...
    Description Resource Path Location Type Project has no default.properties file! Edit the project properties to set one.
    android service随机自启动
    MVC3 安装部署
    EF 4.3 CodeBased 数据迁移演练
    SQL Server 2008开启sa账户
  • 原文地址:https://www.cnblogs.com/lxyit/p/10282975.html
Copyright © 2011-2022 走看看