前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring读源码系列番外篇08---BeanWrapper没有那么简单--中

Spring读源码系列番外篇08---BeanWrapper没有那么简单--中

作者头像
大忽悠爱学习
发布2022-05-10 16:37:18
4390
发布2022-05-10 16:37:18
举报
文章被收录于专栏:c++与qt学习

Spring读源码系列番外篇08---BeanWrapper没有那么简单--中


引子

这篇文章需要依赖于对属性访问器PropertyAccessor的理解,也就是上篇文章的内容:Spring读源码系列番外篇08—BeanWrapper没有那么简单–上

如果说上篇文章所说的PropertyAccessor你没有接触过和听过,那么本文即将要说的重点:BeanWrapper你应该多少有所耳闻吧~

BeanWrapper可以简单的把它理解为:一个方便开发人员使用字符串来对Java Bean的属性执行get、set操作的工具。关于它的数据转换使用了如下两种机制:

  • PropertyEditor:隶属于Java Bean规范。PropertyEditor只提供了String <->Object的转换。
  • ConversionService:Spring自3.0之后提供的替代PropertyEditor的机制(BeanWrapper在Spring的第一个版本就存在了~)

按照Spring官方文档的说法,当容器内没有注册ConversionService的时候,会退回使用PropertyEditor机制。言外之意:首选方案是ConversionService 其实了解的伙伴应该知道,这不是BeanWrapper的内容,而是父接口PropertyAccessor的内容,更具体应该是TypeConverterDelegate的内容

类型转换相关的文章:

Spring读源码系列番外篇—01–PropertyValue相关类

Spring读源码系列番外篇—02—PropertyResolver的结构体系剖析—上

Spring读源码系列番外篇—03—PropertyResolver的结构体系剖析—下

Spring读源码系列番外篇—04----类型转换–上

Spring读源码系列番外篇—05----类型转换—中

Spring读源码系列番外篇—06----类型转换—下—ConversionService相关家族


BeanWrapper

圈出来的一部分已经全部都讲解过了,不清楚的可以去看上集,对于BeanWrapper来说,bean属性操作,类型转换,转换器管理相关功能都已经实现了,那么BeanWrapper接口又增加了哪些额外的功能呢?

官方解释:Spring低级JavaBeans基础设施的中央接口。通常来说并不直接使用BeanWrapper,而是借助BeanFactory或者DataBinder来一起使用~

提供分析和操作标准 JavaBeans 的操作:获取和设置属性值(单独或批量)、获取属性描述符以及查询属性的可读性和可写性的能力。

此接口支持嵌套属性,可以将子属性的属性设置为无限深度。

BeanWrapper 的“extractOldValueForEditor”设置默认为“false”,以避免由 getter 方法调用引起的副作用。将此设置为“true”以将当前属性值公开给自定义编辑器。

代码语言:javascript
复制
//@since 13 April 2001  很清晰的看到,它也是个`PropertyAccessor`属性访问器
public interface BeanWrapper extends ConfigurablePropertyAccessor {

	// @since 4.1
	void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);
	int getAutoGrowCollectionLimit();


	Object getWrappedInstance();
	Class<?> getWrappedClass();

	// 获取属性们的PropertyDescriptor  获取属性们
	PropertyDescriptor[] getPropertyDescriptors();
	// 获取具体某一个属性~
	PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
}

BeanWrapper相当于一个代理器,Spring委托BeanWrapper完成Bean属性的填充工作。关于此接口的实现类,简单的说它只有唯一实现类:BeanWrapperImpl


BeanWrapperImpl

它作为BeanWrapper接口的默认实现,它足以满足所有的典型应用场景,它会缓存Bean的内省结果而提高效率。

在Spring2.5之前,此实现类是非public的,但在2.5之后给public了并且还提供了工厂:PropertyAccessorFactory帮助第三方框架能快速获取到一个实例~

注意:自动注册来自 org.springframework.beans.propertyeditors 包的默认属性编辑器,除了 JDK 的标准 PropertyEditors 之外,它还适用。应用程序可以调用 registerCustomEditor(Class, java.beans.PropertyEditor) 方法来为特定实例注册一个编辑器(即它们不在应用程序之间共享)

代码语言:javascript
复制
public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {

	/**
缓存此对象的自省结果,以防止每次遇到 JavaBeans 自省的成本。
	 */
	@Nullable
	//CachedIntrospectionResults:为 Java 类缓存 JavaBeans PropertyDescriptor 信息的内部类。不适合应用程序代码直接使用。
	private CachedIntrospectionResults cachedIntrospectionResults;

	/**
	 * The security context used for invoking the property methods.
	 */
	@Nullable
	private AccessControlContext acc;

	public BeanWrapperImpl() {
		this(true);
	}

	/**
	registerDefaultEditors:是否注册默认的Editors
	 */
	public BeanWrapperImpl(boolean registerDefaultEditors) {
		super(registerDefaultEditors);
	}

//object是被包裹的对象
	public BeanWrapperImpl(Object object) {
		super(object);
	}


	public BeanWrapperImpl(Class<?> clazz) {
		super(clazz);
	}

	public BeanWrapperImpl(Object object, String nestedPath, Object rootObject) {
		super(object, nestedPath, rootObject);
	}

	private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl parent) {
		super(object, nestedPath, parent);
		setSecurityContext(parent.acc);
	}


	/**
	 * Set a bean instance to hold, without any unwrapping of {@link java.util.Optional}.
	 */
	public void setBeanInstance(Object object) {
		this.wrappedObject = object;
		this.rootObject = object;
		//真正做类型转换的委托类
		this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
		setIntrospectionClass(object.getClass());
	}

	@Override
	public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {
		super.setWrappedInstance(object, nestedPath, rootObject);
		setIntrospectionClass(getWrappedClass());
	}

	/**
	  Set the class to introspect.
	  Needs to be called when the target object changes.
	 */
	protected void setIntrospectionClass(Class<?> clazz) {
		if (this.cachedIntrospectionResults != null && this.cachedIntrospectionResults.getBeanClass() != clazz) {
			this.cachedIntrospectionResults = null;
		}
	}

	/**
	 * Obtain a lazily initialized CachedIntrospectionResults instance
	 * for the wrapped object.
	 * spring很喜欢懒加载这个功能
	 */
	private CachedIntrospectionResults getCachedIntrospectionResults() {
		if (this.cachedIntrospectionResults == null) {
			this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());
		}
		return this.cachedIntrospectionResults;
	}

	/**
	 * Set the security context used during the invocation of the wrapped instance methods.
	 * Can be null.
	 */
	public void setSecurityContext(@Nullable AccessControlContext acc) {
		this.acc = acc;
	}

	/**
	 * Return the security context used during the invocation of the wrapped instance methods.
	 * Can be null.
	 */
	@Nullable
	public AccessControlContext getSecurityContext() {
		return this.acc;
	}


	/**
	 * Convert the given value for the specified property to the latter's type.
此方法仅用于 BeanFactory 中的优化。使用 convertIfNecessary 方法进行编程转换。
	 */
	@Nullable
	public Object convertForProperty(@Nullable Object value, String propertyName) throws TypeMismatchException {
	   //CachedIntrospectionResults:为 Java 类缓存 JavaBeans PropertyDescriptor 信息的内部类。不适合应用程序代码直接使用。
		CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults();
		//先从缓存中获取当前属性对应的属性描述符
		PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName);
		if (pd == null) {
			throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
					"No property '" + propertyName + "' found");
		}
		//再从缓存中获取类型描述符
		TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd);
		if (td == null) {
			td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd)));
		}
		//进行属性转换操作
		return convertForProperty(propertyName, null, value, td);
	}

	private Property property(PropertyDescriptor pd) {
		GenericTypeAwarePropertyDescriptor gpd = (GenericTypeAwarePropertyDescriptor) pd;
		return new Property(gpd.getBeanClass(), gpd.getReadMethod(), gpd.getWriteMethod(), gpd.getName());
	}

	@Override
	@Nullable
	protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
       //先获取到对应的属性描述符
		PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
		return (pd != null ? new BeanPropertyHandler(pd) : null);
	}

	@Override
	protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) {
		return new BeanWrapperImpl(object, nestedPath, this);
	}

	@Override
	protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) {
		PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass());
		throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName,
				matches.buildErrorMessage(), matches.getPossibleMatches());
	}

	@Override
	public PropertyDescriptor[] getPropertyDescriptors() {
		return getCachedIntrospectionResults().getPropertyDescriptors();
	}

	@Override
	public PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException {
		BeanWrapperImpl nestedBw = (BeanWrapperImpl) getPropertyAccessorForPropertyPath(propertyName);
		String finalPath = getFinalPath(nestedBw, propertyName);
		PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(finalPath);
		if (pd == null) {
			throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
					"No property '" + propertyName + "' found");
		}
		return pd;
	}

    //bean属性管理器
	private class BeanPropertyHandler extends PropertyHandler {

		private final PropertyDescriptor pd;

		public BeanPropertyHandler(PropertyDescriptor pd) {
		//当前属性class类型,当前属性是否可读:即是否存在对应的get方法,是否可写:是否存在对应的set方法
			super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);
			this.pd = pd;
		}

       //获取的是当前属性get方法返回值,即当前属性类型相关信息的封装
		@Override
		public ResolvableType getResolvableType() {
			return ResolvableType.forMethodReturnType(this.pd.getReadMethod());
		}

        //拿到当前属性的类型描述符   
		@Override
		public TypeDescriptor toTypeDescriptor() {
			return new TypeDescriptor(property(this.pd));
		}

		@Override
		@Nullable
		public TypeDescriptor nested(int level) {
			return TypeDescriptor.nested(property(this.pd), level);
		}
        
        //获取当前属性的值 
		@Override
		@Nullable
		public Object getValue() throws Exception {
		//还是先定位get方法
			Method readMethod = this.pd.getReadMethod();
			if (System.getSecurityManager() != null) {
				AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
					ReflectionUtils.makeAccessible(readMethod);
					return null;
				});
				try {
					return AccessController.doPrivileged((PrivilegedExceptionAction<Object>)
							() -> readMethod.invoke(getWrappedInstance(), (Object[]) null), acc);
				}
				catch (PrivilegedActionException pae) {
					throw pae.getException();
				}
			}
			else {
			//即使get方法是私有的也可以调用
				ReflectionUtils.makeAccessible(readMethod);
				//本质就是调用get方法获取当前属性的值,如果没有get方法,那么属性值就无法获取到
				return readMethod.invoke(getWrappedInstance(), (Object[]) null);
			}
		}

//设置属性值
		@Override
		public void setValue(@Nullable Object value) throws Exception {
			//定位set方法
			Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
					((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
					this.pd.getWriteMethod());
			if (System.getSecurityManager() != null) {
				AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
					ReflectionUtils.makeAccessible(writeMethod);
					return null;
				});
				try {
					AccessController.doPrivileged((PrivilegedExceptionAction<Object>)
							() -> writeMethod.invoke(getWrappedInstance(), value), acc);
				}
				catch (PrivilegedActionException ex) {
					throw ex.getException();
				}
			}
			else {
			//调用set方法设置新的值
				ReflectionUtils.makeAccessible(writeMethod);
				writeMethod.invoke(getWrappedInstance(), value);
			}
		}
	}

}

从继承体系上,首先我们应该能看出来BeanWrapperImpl的三重身份:

  • Bean包裹器
  • 属性访问器(PropertyAccessor)
  • 属性编辑器注册表(PropertyEditorRegistry)

从源码中继续分析还能再得出如下两个结论:

  • 它给属性赋值调用的是Method方法,如readMethod.invoke和writeMethod.invoke
  • 它对Bean的操作,大都委托给CachedIntrospectionResults去完成~
  • 因此若想了解它,必然主要是要先了解java.beans.PropertyDescriptor和
  • org.springframework.beans.CachedIntrospectionResults,首当其冲的自然还有Java内省。

Java内省Introspector

首先可以先了解下JavaBean的概念:一种特殊的类,主要用于传递数据信息。这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(Value Object),或“VO”。

因此JavaBean都有如下几个特征:

  • 属性都是私有的;
  • 有无参的public构造方法;
  • 对私有属性根据需要提供公有的getXxx方法以及setXxx方法;
  • getters必须有返回值没有方法参数;setter值没有返回值,有方法参数;

符合这些特征的类,被称为JavaBean;JDK中提供了一套API用来访问某个属性的getter/setter方法,这些API存放在java.beans中,这就是内省(Introspector)。


内省和反射的区别
  • 反射:Java反射机制是在运行中,对任意一个类,能够获取得到这个类的所有属性和方法;它针对的是任意类
  • 内省(Introspector):是Java语言对JavaBean类属性、事件的处理方法

反射可以操作各种类的属性,而内省只是通过反射来操作JavaBean的属性

内省设置属性值肯定会调用seter方法,反射可以不用(反射可直接操作属性Field)

反射就像照镜子,然后能看到.class的所有,是客观的事实。

内省更像主观的判断:比如看到getName()内省就会认为这个类中有name字段,但事实上并不一定会有name;

通过内省可以获取bean的getter/setter

既然反射比内省比内省强大这么多,那内省用在什么时候场景呢?下面给出一个示例来说明它的用武之地:

代码语言:javascript
复制
	// 就这样简单几步,就完成了表单到User对象的封装~
    public void insertUser(HttpServletRequest request) throws Exception {
        User user = new User();

        // 遍历:根据字段名去拿值即可(此处省略判空、类型转换等细节,不在本文讨论范围)
        PropertyDescriptor[] pds = Introspector.getBeanInfo(User.class).getPropertyDescriptors();
        for (PropertyDescriptor pd : pds) {
            pd.getWriteMethod().invoke(user, request.getParameter(pd.getName()));
        }
    }

通过内省可以很轻松的将form表单的内容填充进对象里面,比反射轻松省力多了。其实像MyBatis这种框架,底层都用到了Java的内省机制。

内省的API主要有Introspector、BeanInfo、PropertyDescriptor等,下面就以他三为例来操作一个JavaBean:

代码语言:javascript
复制
@Getter
@Setter
@ToString
public class Child {

    private String name;
    private Integer age;
    
}

使用Introspector + BeanInfo:

代码语言:javascript
复制
    public static void main(String[] args) throws IntrospectionException {
        BeanInfo beanInfo = Introspector.getBeanInfo(Child.class);

        BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
        MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

        // 打印
        System.out.println(beanDescriptor);
        System.out.println("------------------------------");
        Arrays.stream(methodDescriptors).forEach(x -> System.out.println(x));
        System.out.println("------------------------------");
        Arrays.stream(propertyDescriptors).forEach(x -> System.out.println(x));
        System.out.println("------------------------------");
    }

输入内容如下:

代码语言:javascript
复制
java.beans.BeanDescriptor[name=Child; beanClass=class com.fsx.bean.Child]
------------------------------

java.beans.MethodDescriptor[name=getClass; method=public final native java.lang.Class java.lang.Object.getClass()]
java.beans.MethodDescriptor[name=getName; method=public java.lang.String com.fsx.bean.Child.getName()]
java.beans.MethodDescriptor[name=setAge; method=public void com.fsx.bean.Child.setAge(java.lang.Integer)]
java.beans.MethodDescriptor[name=setName; method=public void com.fsx.bean.Child.setName(java.lang.String)]
java.beans.MethodDescriptor[name=getAge; method=public java.lang.Integer com.fsx.bean.Child.getAge()]
java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait() throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=notifyAll; method=public final native void java.lang.Object.notifyAll()]
java.beans.MethodDescriptor[name=notify; method=public final native void java.lang.Object.notify()]
java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=hashCode; method=public native int java.lang.Object.hashCode()]
java.beans.MethodDescriptor[name=wait; method=public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=equals; method=public boolean java.lang.Object.equals(java.lang.Object)]
java.beans.MethodDescriptor[name=toString; method=public java.lang.String com.fsx.bean.Child.toString()]
------------------------------

java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer com.fsx.bean.Child.getAge(); writeMethod=public void com.fsx.bean.Child.setAge(java.lang.Integer)]
java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()]
java.beans.PropertyDescriptor[name=name; propertyType=class java.lang.String; readMethod=public java.lang.String com.fsx.bean.Child.getName(); writeMethod=public void com.fsx.bean.Child.setName(java.lang.String)]
------------------------------

可以看到getMethodDescriptors()它把父类的MethodDescriptor也拿出来了。

而PropertyDescriptor中比较特殊的是因为有getClass()方法,因此class也算是一个PropertyDescriptor,但是它没有writeMethod哦~

关于BeanInfo,Spring在3.1提供了一个类ExtendedBeanInfo继承自它实现了功能扩展,并且提供了BeanInfoFactory来专门生产它~~~(实现类为:ExtendedBeanInfoFactory)

但是如果只想拿某一个属性的话,使用Introspector就不是那么方便了,下面介绍更为常用的PropertyDescriptor来处理某一个属性~


PropertyDescriptor 属性描述器

属性描述符描述了Java bean通过一对访问器方法导出的一个属性。上面的示例此处用PropertyDescriptor试试:

代码语言:javascript
复制
public static void main(String[] args) throws IntrospectionException {
    PropertyDescriptor age = new PropertyDescriptor("age", Child.class);
    System.out.println(age.getPropertyType()); //class java.lang.Integer
    System.out.println(age.getDisplayName()); //age


    // 最重要的两个方法~~~
    System.out.println(age.getReadMethod()); //public java.lang.Integer com.fsx.bean.Child.getAge()
    System.out.println(age.getWriteMethod()); //public void com.fsx.bean.Child.setAge(java.lang.Integer)
}

可以看到它可以实现更加细粒度的控制。将PropertyDescriptor类的一些主要方法描述如下:

  • getPropertyType(),获得属性的Class对象;
  • getReadMethod(),获得用于读取属性值的方法;
  • getWriteMethod(),获得用于写入属性值的方法;
  • setReadMethod(Method readMethod),设置用于读取属性值的方法;
  • setWriteMethod(Method writeMethod),设置用于写入属性值的方法。

CachedIntrospectionResults

Spring如果需要依赖注入那么就必须依靠Java内省这个特性了,说到Spring IOC与JDK内省的结合那么就不得不说一下Spring中的CachedIntrospectionResults这个类了。

它是Spring提供的专门用于缓存JavaBean的PropertyDescriptor描述信息的类,不能被应用代码直接使用。

它的缓存信息是被静态存储起来的(应用级别),

因此对于同一个类型的被操作的JavaBean并不会都创建一个新的CachedIntrospectionResults,因此,这个类使用了工厂模式,

使用私有构造器和一个静态的forClass工厂方法来获取实例。

代码语言:javascript
复制
public final class CachedIntrospectionResults {
	
	// 它可以通过在spring.properties里设置这个属性,来关闭内省的缓存~~~
	public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
	private static final boolean shouldIntrospectorIgnoreBeaninfoClasses = SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);

	// 此处使用了SpringFactoriesLoader这个SPI来加载BeanInfoFactory,唯一实现类是ExtendedBeanInfoFactory
	/** Stores the BeanInfoFactory instances. */
	private static List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(
			BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());

	static final Set<ClassLoader> acceptedClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
	static final ConcurrentMap<Class<?>, CachedIntrospectionResults> strongClassCache = new ConcurrentHashMap<>(64);
	static final ConcurrentMap<Class<?>, CachedIntrospectionResults> softClassCache = new ConcurrentReferenceHashMap<>(64);

	// 被包裹类的BeanInfo~~~也就是目标类
	private final BeanInfo beanInfo;
	// 它缓存了被包裹类的所有属性的属性描述器PropertyDescriptor。
	private final Map<String, PropertyDescriptor> propertyDescriptorCache;



	... // 其它的都是静态方法
	// 只有它会返回一个实例,此类是单例的设计~  它保证了每个beanClass都有一个CachedIntrospectionResults 对象,然后被缓存起来~
	static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException { ... }
}

本处理类的核心内容是Java内省getBeanInfo()以及PropertyDescriptor~注意:为了使此内省缓存生效,有个前提条件请保证了:

  • 确保将Spring框架的Jar包和你的应用类使用的是同一个ClassLoader加载的,这样在任何情况下会允许随着应用的生命周期来清楚缓存。

因此对于web应用来说,Spring建议给web容器注册一个IntrospectorCleanupListener监听器来防止多ClassLoader布局,这样也可以有效的利用caching从而提高效率~


监听器的配置形如这样(此处以web.xml里配置为例):

代码语言:javascript
复制
<listener>
    <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>

说明:请保证此监听器配置在第一个位置,比ContextLoaderListener还靠前~ 此监听器能有效的防止内存泄漏问题~~~(因为内省的缓存是应用级别的全局缓存,很容易造成泄漏的~)

其实流行框架比如struts, Quartz等在使用JDK的内省时,存在没有释的内存泄漏问题~


DirectFieldAccessFallbackBeanWrapper

说完了BeanWrapperImpl,可以看看它的子类DirectFieldAccessFallbackBeanWrapper,他就像BeanWrapperImpl和DirectFieldAccessor的结合体。

它先用BeanWrapperImpl.getPropertyValue(),若抛出异常了(毕竟内省不是十分靠谱,哈哈)再用DirectFieldAccessor~~~此子类在JedisClusterConnection有被使用到过,比较简单没啥太多好说的~


PropertyAccessorFactory

Spring2.5后提供的快速获取PropertyAccessor两个重要实现类的工厂。

代码语言:javascript
复制
public final class PropertyAccessorFactory {
	private PropertyAccessorFactory() {
	}
	// 生产一个BeanWrapperImpl(最为常用)
	public static BeanWrapper forBeanPropertyAccess(Object target) {
		return new BeanWrapperImpl(target);
	}
	// 生产一个DirectFieldAccessor
	public static ConfigurablePropertyAccessor forDirectFieldAccess(Object target) {
		return new DirectFieldAccessor(target);
	}

}

BeanWrapper使用Demo

代码语言:javascript
复制
// 省略Apple类和Size类,有需要的请参照上篇文章(加上@Getter、@Setter即可

    public static void main(String[] args) {
        Apple apple = new Apple();

        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(apple);

        // ================当作一个普通的PropertyAccessor来使用  默认情况下字段也都必须有初始值才行~===================
        // 设置普通属性
        beanWrapper.setPropertyValue("color", "红色"); //请保证对应字段有set方法才行,否则抛错:Does the parameter type of the setter match the return type of the getter?
        // 设置嵌套属性(注意:此处能够正常work是因为有= new Size(),
        // 否则报错:Value of nested property 'size' is null 下同~)
        beanWrapper.setPropertyValue("size.height", 10);

        // 设置集合/数组属性
        beanWrapper.setPropertyValue("arrStr[0]", "arrStr");
        beanWrapper.setPropertyValue("arrStr[1]", "arrStr1"); // 注意:虽然初始化时初始化过数组了,但是仍以此处的为准
        // =========打印输出
        System.out.println(apple); //Apple(color=红色, size=Size(height=10, width=null), arrStr=[arrStr, arrStr1], listStr=[], map={}, listList=[[]], listMap=[{}])


        // 当作BeanWrapper使用
        PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();
        PropertyDescriptor color = beanWrapper.getPropertyDescriptor("color");
        System.out.println(propertyDescriptors.length); // 8
        System.out.println(color); //org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=color]

        System.out.println(beanWrapper.getWrappedClass()); //class com.fsx.bean.Apple
        System.out.println(beanWrapper.getWrappedInstance()); //Apple(color=红色, size=Size(height=10...
    }

上面代码能够清晰的表示了通过BeanWrapper来操作JavaBean还是非常之简便的。


最后,看一下结构图:属性编辑器、类型转换器、属性解析器、属性访问器大致的一个关系:


总结

BeanWrapper接口,作为Spring内部的一个核心接口,正如其名,它是bean的包裹类,即在内部中将会保存该bean的实例,提供其它一些扩展功能。

Spring对Bean的属性存取都是通过BeanWrapperImpl实现的,BeanWrapperImpl和Bean是一对一的关系,BeanWrapperImpl通过属性的读方法和写方法来存取Bean属性的。为了更加深刻的了解BeanWrapper。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring读源码系列番外篇08---BeanWrapper没有那么简单--中
  • 引子
  • BeanWrapper
  • BeanWrapperImpl
    • Java内省Introspector
      • PropertyDescriptor 属性描述器
        • CachedIntrospectionResults
          • DirectFieldAccessFallbackBeanWrapper
            • PropertyAccessorFactory
              • BeanWrapper使用Demo
              • 总结
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档