spring 4.x源码分析-BeanWrapper

本文深入解析Spring框架中的BeanWrapper接口,介绍了BeanWrapper的基本概念、使用方法及其源码解析,包括属性访问、设置以及递归处理内嵌属性的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、BeanWrapper简介

    BeanWrapper接口,作为spring内部的一个核心接口,正如其名,它是bean的包裹类,即在内部中将会保存该bean的实例,提供其它一些扩展功能。同时,BeanWrapper接口还继承了PropertyAccessor, propertyEditorRegistry, TypeConverter、ConfigurablePropertyAccessor接口,所以它还提供了访问bean的属性值、属性编辑器注册、类型转换等功能。

    下面我们一起回顾一下bean的实例化过程,看一下spring是怎么使用BeanWrapper。

                                                         bean的实例化过程

(1)ResourceLoader加载配置信息

(2)BeanDefinitionReader读取并解析<bean>标签,并将<bean>标签的属性转换为BeanDefinition对应的属性,并注册到BeanDefinitionRegistry注册表中。

(3)容器扫描BeanDefinitionRegistry注册表,通过反射机制获取BeanFactoryPostProcessor类型的工厂后处理器,并用这个工厂后处理器对BeanDefinition进行加工。

(4)根据处理过的BeanDefinition,实例化bean。然后BeanWrapper结合BeanDefinitionRegistry和PropertyEditorRegistry对Bean的属性赋值。

    在上面的bean实例化过程中,BeanWrapper取出XML中定义的值,然后通过属性编辑器或类型转换器把xml中的字符串值转换成bean中属性对应的类型,最后以内省的方式设置到bean的属性。

    其实,在BeanWrapper接口中,最核心的功能就是读取和设置bean的属性,它是通过java内省的方式完成bean属性的访问的。为了能够感性认识BeanWrapper接口,下面将通过一个例子看一下怎么使用BeanWrapper访问bean的属性。

2、BeanWrapper的使用

    前面介绍过,BeanWrapper接口继承了PropertyAccessor接口,所以它具备了访问bean的属性的能力。我们看一下PropertyAccessor接口的一些方法:

public interface PropertyAccessor {

	String NESTED_PROPERTY_SEPARATOR = ".";
	char NESTED_PROPERTY_SEPARATOR_CHAR = '.';

	String PROPERTY_KEY_PREFIX = "[";
	char PROPERTY_KEY_PREFIX_CHAR = '[';

	String PROPERTY_KEY_SUFFIX = "]";
	char PROPERTY_KEY_SUFFIX_CHAR = ']';

	boolean isReadableProperty(String propertyName);

	boolean isWritableProperty(String propertyName);

	Class<?> getPropertyType(String propertyName) throws BeansException;

	TypeDescriptor getPropertyTypeDescriptor(String propertyName) throws BeansException;

	Object getPropertyValue(String propertyName) throws BeansException;

	void setPropertyValue(String propertyName, Object value) throws BeansException;

	void setPropertyValue(PropertyValue pv) throws BeansException;

	void setPropertyValues(Map<?, ?> map) throws BeansException;

	void setPropertyValues(PropertyValues pvs) throws BeansException;

	void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown)
			throws BeansException;

	void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
			throws BeansException;

}

    PropertyAccessor接口提供了访问bean属性的方法,还定义了访问嵌套属性的访问表达式的切割符,重点看一下下面两个方法:

Object getPropertyValue(String propertyName) throws BeansException;

void setPropertyValue(String propertyName, Object value) throws BeansException;

    getPropertyValue和setPropertyValue是分别用于获取和设置bean的属性值的。这里的propertyName支持表达式:

表达式说明

name

指向属性name,与getName() 或 isName() 和 setName()相对应。
account.name指向属性account的嵌套属性name,与之对应的是getAccount().setName()和getAccount().getName()
account[2]指向索引属性account的第三个元素,索引属性可能是一个数组(array),列表(list)或其它天然有序的容器。
account[COMPANYNAME]指向一个Map实体account中以COMPANYNAME作为键值(key)所对应的值

    举个栗子。我们需要使用BeanWrapper访问它们的属性。


public class Car {
    private String name;
    private Wheel[] wheels;

    private Driver driver;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Wheel[] getWheels() {
        return wheels;
    }

    public void setWheels(Wheel[] wheels) {
        this.wheels = wheels;
    }

    public Driver getDriver() {
        return driver;
    }

    public void setDriver(Driver driver) {
        this.driver = driver;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", wheels=" + Arrays.toString(wheels) +
                ", driver=" + driver +
                '}';
    }
}
public class Driver {
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Driver{" +
                "age=" + age +
                '}';
    }
}
public class Wheel {
    private String position;

    public String getPosition() {
        return position;
    }

    public void setPosition(String position) {
        this.position = position;
    }

    @Override
    public String toString() {
        return "Wheel{" +
                "position='" + position + '\'' +
                '}';
    }
}

 

   public static void main(String[] args) {

        //左边轮子的BeanWrapper
        Wheel leftWheel = new Wheel();
        BeanWrapper beanWrapperOfLeftWheel = PropertyAccessorFactory.forBeanPropertyAccess(leftWheel);
        PropertyValue leftPosition = new PropertyValue("position", "左边");
        beanWrapperOfLeftWheel.setPropertyValue(leftPosition);
        System.out.println(beanWrapperOfLeftWheel.getWrappedInstance());

        //左边轮子的BeanWrapper
        Wheel rightWheel = new Wheel();
        BeanWrapper beanWrapperOfRightWheel = PropertyAccessorFactory.forBeanPropertyAccess(rightWheel);
        PropertyValue rightPosition = new PropertyValue("position", "右边");
        beanWrapperOfRightWheel.setPropertyValue(rightPosition);
        System.out.println(beanWrapperOfRightWheel.getWrappedInstance());

        // 驾驶员
        Driver driver = new Driver();
        BeanWrapper beanWrapperOfDriver = PropertyAccessorFactory.forBeanPropertyAccess(driver);
        PropertyValue age = new PropertyValue("age", 20);
        beanWrapperOfDriver.setPropertyValue(age);
        System.out.println(beanWrapperOfDriver.getWrappedInstance());


        // 车子
        Car car = new Car();
        BeanWrapper beanWrapperOfCar = PropertyAccessorFactory.forBeanPropertyAccess(car);
        beanWrapperOfCar.setPropertyValue("name", "奔驰"); // 车名
        Wheel[] wheels = {leftWheel, rightWheel}; //轮子数组
        beanWrapperOfCar.setPropertyValue("wheels", wheels); //轮子
        beanWrapperOfCar.setPropertyValue("driver", driver); //驾驶员
        System.out.println(beanWrapperOfCar.getWrappedInstance());

        // 获取驾驶员的年龄
        int retrievedAge = (Integer) beanWrapperOfCar.getPropertyValue("driver.age");
        System.out.println("driver age : " + retrievedAge);


        // 通过表达式间接设置car的wheel的width
        beanWrapperOfCar.setPropertyValue("wheels[0].position", "修改过的左边");
        System.out.println(beanWrapperOfCar.getWrappedInstance());

    }

    上面的代码已经很清楚地演示了如何使用BeanWrapper设置和获取bean的属性,有了初步的认识之后,接下来将会解析该源码。

3、解析BeanWrapper源码

    BeanWrapper的实现类是BeanWrapperImpl,它包装了bean对象,缓存了bean的内省结果,并可以访问bean的属性、设置bean的属性值。除此之外,BeanWrapperImpl类提供了许多默认属性编辑器,支持多种不同类型的类型转换,例如,可以将数组、集合类型的属性转换成指定特殊类型的数组或集合。用户也可以注册自定义的属性编辑器在BeanWrapperImpl中。

    另外,BeanWrapperImpl有一个cachedIntrospectionResults成员变量,它保存了被包装bean的内省分析结果。cachedIntrospectionResults有两个成员变量,一个是beanInfo,它是被包裹类的BeanInfo;另一个是propertyDescriptorCache,它缓存了被包裹类的所有属性的属性描述器PropertyDescriptor。

    

    我们看一下BeanWrapperImpl的结构类图。

3.1 构造方法

BeanWrapperImpl重载了很多种构造方法,我们看一下通过传入bean实例作为参数的构造方法。

	public BeanWrapperImpl(Object object) {
		super(object);
	}

调用父类AbstractNestablePropertyAccessor的构造方法。

	protected AbstractNestablePropertyAccessor(Object object) {
		registerDefaultEditors(); // 标识可以使用默认的属性编辑器
		setWrappedInstance(object);
	}
	public void setWrappedInstance(Object object) {
		setWrappedInstance(object, "", null);
	}
	public void setWrappedInstance(Object object, String nestedPath, Object rootObject) {
		
        // bean设置为BeanWrapperImpl的内部变量
		if (object.getClass() == javaUtilOptionalClass) {
			this.wrappedObject = OptionalUnwrapper.unwrap(object);
		}
		else {
			this.wrappedObject = object;
		}

		this.nestedPath = (nestedPath != null ? nestedPath : "");
		this.rootObject = (!"".equals(this.nestedPath) ? rootObject : this.wrappedObject);
		this.nestedPropertyAccessors = null;
		 
        // 新建类型转换器的委托类,这里BeanWrapperImpl的实例为propertyEditorRegistry,bean为targetObject
        this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);
	}

    很明显,构造方法中做了两件重要的事,一个是把bean设为内部变量,另一个是实例化了一个类型转换器的委托类,由于BeanWrapperImpl同时继承了PropertyEditorRegistrySupport,所以它作为TypeConverterDelegate的属性编辑器注册中心的帮助类存在于TypeConverterDelegate中。

3.2 设置属性

BeanWrapperImpl支持多种设置bean属性的方法。

先看一下 void setPropertyValue(String propertyName, Object value)

public void setPropertyValue(String propertyName, Object value) throws BeansException {
	AbstractNestablePropertyAccessor nestedPa;
	try { 
		//根据属性名获取BeanWrapImpl对象,支持多重属性的递归分析处理
		nestedPa = getPropertyAccessorForPropertyPath(propertyName);
	}
	catch (NotReadablePropertyException ex) {
		throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
				"Nested property in path '" + propertyName + "' does not exist", ex);
	}
    // 经过上面的递归后,获取到最终需要操作的属性的对象,下面将根据该属性对象,获取最终要操作的内嵌对象的属性,
    // 生成PropertyTokenHolder,内省设置属性值
	PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
	nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
}

    getPropertyAccessorForPropertyPath根据属性名的表达式获取访问该属性的属性访问器AbstractNestablePropertyAccessor ,即BeanWrapperImpl 。下面的注释如果没有明白的话,请先往下看,然后再回头看这里,一切豁然开朗。

    下面看一下 getPropertyAccessorForPropertyPath 是怎么处理内嵌属性的,即对象里面包含对象。

protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
	// 获取第一个内嵌属性的位置,分隔符是"."
	int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(propertyPath);
	
	// 递归获取内嵌属性,如果propertyPath不再存在分隔符“.”,返回递归结果
	if (pos > -1) {
		
        // 假设propertyPath为 wheels[0].position
		String nestedProperty = propertyPath.substring(0, pos); // wheels[0].position -> wheels[0]
		
		String nestedPath = propertyPath.substring(pos + 1); // wheels[0].position -> position
		
		// 获取本轮递归中AbstractNestablePropertyAccessor的属性
		AbstractNestablePropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty);
		
		return nestedPa.getPropertyAccessorForPropertyPath(nestedPath);
	}
	else {
		return this;
	}
}

    该方法递归获取需要访问属性的AbstractNestablePropertyAccessor。一起看一下getNestedPropertyAccessor是怎么根据属性名获取到属性访问器的。

private AbstractNestablePropertyAccessor getNestedPropertyAccessor(String nestedProperty) {
	if (this.nestedPropertyAccessors == null) {
		this.nestedPropertyAccessors = new HashMap<String, AbstractNestablePropertyAccessor>();
	}
	
	PropertyTokenHolder tokens = getPropertyNameTokens(nestedProperty); //根据属性名获取PropertyTokenHolder
	
	String canonicalName = tokens.canonicalName;
	
	Object value = getPropertyValue(tokens); // 根据PropertyTokenHolder获取该内嵌属性的实例化对象
	
	// 该属性在bean中为null
	if (value == null || (value.getClass() == javaUtilOptionalClass && OptionalUnwrapper.isEmpty(value))) {
		// 如果允许自动创建属性,调用setDefaultValue创建默认的对象,否则抛异常
		if (isAutoGrowNestedPaths()) { 
			value = setDefaultValue(tokens);
		}
		else {
			throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + canonicalName);
		}
	}

	// 把上面获取到的内嵌对象的实例,包裹为一个新的BeanWrapperImpl,然后把该BeanWrapperImpl缓存到本级的缓存对象nestedPropertyAccessors。
	// 例如,上一节中的Car的driver属性,在car本级,维护了一个缓存对象nestedPropertyAccessors,
	// 第一次访问内嵌属性driver时,将会为driver创建PropertyAccessor,并缓存到nestedPropertyAccessors中。
	AbstractNestablePropertyAccessor nestedPa = this.nestedPropertyAccessors.get(canonicalName);
	if (nestedPa == null || nestedPa.getWrappedInstance() !=
			(value.getClass() == javaUtilOptionalClass ? OptionalUnwrapper.unwrap(value) : value)) {

        // 内嵌对象的实例,包裹为一个新的BeanWrapperImpl
		nestedPa = newNestedPropertyAccessor(value, this.nestedPath + canonicalName + NESTED_PROPERTY_SEPARATOR);

		// 新的BeanWrapperImpl 继承本级的PropertyEditors.
		copyDefaultEditorsTo(nestedPa);
		copyCustomEditorsTo(nestedPa, canonicalName);

        // 缓存
		this.nestedPropertyAccessors.put(canonicalName, nestedPa);
	}
	else {

	}
	return nestedPa;
}

    在上面代码中,getPropertyNameTokens将根据propertyName生成一个统一操作的结构PropertyTokenHolder,此类保存了属性名解析后的结构。该结构的成员如下:

属性类型及名称

说明

String canonicalName

propertyName为wheels[0]时,canonicalName为wheels[0];

propertyName为driver时,canonicalName为driver

String actualName

propertyName为driver时,actualName为driver

String[] keys

propertyName为driver时,keys为null

    上面的代码 Object value = getPropertyValue(tokens) 将根据 PropertyTokenHolder 获取指定property属性的实例,一起看下这段代码。

protected Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
	String propertyName = tokens.canonicalName;
	String actualName = tokens.actualName;
	
	// 获取PropertyHandler,内部实现是从cachedIntrospectionResults中取出该属性的PropertyDescriptor,
	// 然后取出属性的PropertyType, ReadMethod , WriteMethod
	PropertyHandler ph = getLocalPropertyHandler(actualName);
	
	// 属性不可读,抛异常
	if (ph == null || !ph.isReadable()) {
		throw new NotReadablePropertyException(getRootClass(), this.nestedPath + propertyName);
	}
	
	try {
		// 内省方式获取属性的实例
		Object value = ph.getValue();
		
		// 如果该属性是数组、list、set,tokens的keys是不为空的,keys将会保存需要访问的索引号,
        // 在map中,keys是一个字符串
		// 下面就是通过该索引号获取特定下标的属性值。
		if (tokens.keys != null) {
			if (value == null) {
				if (isAutoGrowNestedPaths()) {
					value = setDefaultValue(tokens.actualName);
				}
				else {
					throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
							"Cannot access indexed value of property referenced in indexed " +
									"property path '" + propertyName + "': returned null");
				}
			}
			
			String indexedPropertyName = tokens.actualName;
			
			// apply indexes and map keys
			for (int i = 0; i < tokens.keys.length; i++) {
				String key = tokens.keys[i];
				if (value == null) {
					throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,
							"Cannot access indexed value of property referenced in indexed " +
									"property path '" + propertyName + "': returned null");
				}
				else if (value.getClass().isArray()) { //处理数组
					int index = Integer.parseInt(key);
					value = growArrayIfNecessary(value, index, indexedPropertyName);
					value = Array.get(value, index);
				}
				else if (value instanceof List) { //处理list
					int index = Integer.parseInt(key);
					List<Object> list = (List<Object>) value;
					growCollectionIfNecessary(list, index, indexedPropertyName, ph, i + 1);
					value = list.get(index);
				}
				else if (value instanceof Set) { //处理set
					// Apply index to Iterator in case of a Set.
					Set<Object> set = (Set<Object>) value;
					int index = Integer.parseInt(key);

					Iterator<Object> it = set.iterator();
					for (int j = 0; it.hasNext(); j++) {
						Object elem = it.next();
						if (j == index) {
							value = elem;
							break;
						}
					}
				}
				else if (value instanceof Map) { //处理map
					Map<Object, Object> map = (Map<Object, Object>) value;
					Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
					// IMPORTANT: Do not pass full property name in here - property editors
					// must not kick in for map keys but rather only for map values.
					TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
					Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
					value = map.get(convertedMapKey);
				}
				else {

				}
				indexedPropertyName += PROPERTY_KEY_PREFIX + key + PROPERTY_KEY_SUFFIX;
			}
		}
		return value;
	}

	//异常,忽略
}

总结:在通过setPropertyValue设置bean的属性时,首先将会根据propertyName的字符串,递归获取该属性所在的内嵌属性(假如属性不在内嵌属性,获取的就是它自己),然后通过内省的方式设置该属性的值。

3.3 获取属性

BeanWrapperImpl有两个获取属性的方法

public Object getPropertyValue(String propertyName) throws BeansException

protected Object getPropertyValue(PropertyTokenHolder tokens)

    其中 getPropertyValue(PropertyTokenHolder tokens) 在上面设置属性那一节已经出现过了,它是在内部使用的,不对外公开。

    既然如此,看一下Object getPropertyValue(String propertyName)这个方法吧。该方法在AbstractNestablePropertyAccessor中实现。

	public Object getPropertyValue(String propertyName) throws BeansException {
		AbstractNestablePropertyAccessor nestedPa = getPropertyAccessorForPropertyPath(propertyName);
		PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
		return nestedPa.getPropertyValue(tokens);
	}

    其中,getPropertyAccessorForPropertyPath方法在上一节中已经有详细的解析,它将递归获取可以访问该属性的AbstractNestablePropertyAccessor,这里的实现类是BeanWrapperImpl。例如propertyName是driver.age,那么BeanWrapperImpl所包裹的就是Driver的实例。然后,根据PropertyTokenHolder获取属性值。这个流程与设置属性的相似,请参考前面。

 

转载于:https://my.oschina.net/thinwonton/blog/1492224

<think>我们正在解决一个MyBatis的反射异常问题:ReflectionException: There is no setter for property named 'Step' in class com.web.hospital.dao.PlanVO 这个异常表示MyBatis在尝试设置一个名为'Step'的属性时,在PlanVO类中找不到对应的setter方法。 根据引用[2]和引用[4]的描述,通常出现这个异常的原因有以下几点: 1. 实体类中确实没有为某个属性提供setter方法。 2. 属性名和setter方法名不匹配(例如,属性名为step,setter方法可能是setStep或者setstep,但MyBatis要求严格匹配,即setStep)。 3. 在MyBatis的映射文件(如XML)中,我们使用了某个属性名,但是该属性名在实体类中并不存在(或者大小写不匹配)。 解决方案: 1. 检查PlanVO类中是否存在名为'Step'的属性(注意大小写)以及对应的setter方法。 通常,属性名是驼峰命名的,而setter方法应该是setXxx的形式。例如,如果属性名为step(首字母小写),那么setter方法应该是setStep(首字母大写)。 2. 如果属性名是'step'(小写s),但在映射文件中写成了'Step'(大写S),那么MyBatis在通过反射调用setter方法时就会找不到,因为JavaBean规范是基于方法名的,而方法名中的属性名部分是首字母大写的。 3. 检查映射文件(如Mapper XML)中引用该属性的地方,确保属性名与JavaBean中的属性名一致(注意:MyBatis在通过OGNL表达式访问属性时,通常使用属性名的直接写法,且对大小写敏感)。 4. 如果使用了lombok,请确保使用了正确的注解(如@Setter)并且lombok正常工作(有时IDE需要安装lombok插件并开启注解处理)。 5. 如果属性名是多个单词组成的,比如“myStep”,那么setter方法应该是setMyStep,在映射文件中使用属性名应该是“myStep”(首字母小写)。 具体步骤: 步骤1:检查PlanVO类 打开PlanVO类,检查是否有属性`Step`或`step`,以及对应的setter方法。 例如,如果属性定义如下: private String step; 那么必须有对应的setter方法: public void setStep(String step) { this.step = step; } 步骤2:检查映射文件 在映射文件中,比如在<insert>、<update>、<select>等语句中,我们可能会使用#{Step}或${Step},或者是在<resultMap>中使用了property="Step"。 需要确保这些地方使用的属性名与JavaBean中的属性名一致(注意大小写)。通常,JavaBean属性名是首字母小写的驼峰,所以这里应该使用"step"而不是"Step"。 例如,在参数映射中: 错误:#{Step} 正确:#{step} 或者在resultMap中: <result property="step" column="step_column"/> 步骤3:检查是否使用了类型别名 如果使用了类型别名,确保别名指向正确的类。 步骤4:检查是否有拼写错误 仔细检查属性名和映射文件中的引用是否完全一致。 步骤5:使用@Param注解(如果是多参数) 如果是在一个方法中有多个参数,并且没有使用@Param注解,那么MyBatis会按照param1, param2...来访问,或者使用参数的实际名称(需要开启useActualParamName)。如果参数是一个对象,那么应该通过属性名访问。 例如,如果方法定义为: void updatePlan(@Param("plan") PlanVO plan, @Param("step") String step); 那么在XML中引用第二个参数应该是:#{step},而不是#{Step}。 步骤6:如果使用了包装类(如QueryWrapper) 根据引用[3]和引用[5],有时在MyBatis Plus中使用QueryWrapper时,可能会因为参数名不正确而报错。但这里我们的异常是关于setter的,所以可能是我们在设置一个对象属性时出现了问题。 步骤7:检查是否使用了不恰当的表达式 在动态SQL中,比如<if test="Step != null">,这里的Step会被OGNL解析,同样需要遵循JavaBean的规范,因此需要写为<if test="step != null">。 总结: 这个异常的根本原因是MyBatis在PlanVO类中找不到名为'Step'的属性的setter方法。我们需要确保类中有该属性(按照JavaBean规范)并且有对应的setter方法,同时在映射文件中使用正确的属性名(通常是首字母小写)。 如果按照以上步骤检查后问题仍然存在,请提供PlanVO类的相关代码片段和映射文件中使用到'Step'的地方,以便进一步分析。 相关问题: 1. MyBatis中属性名的大小写敏感吗? 2. 如何使用Lombok为实体类自动生成getter和setter? 3. 在MyBatis的映射文件中,如何正确引用JavaBean的属性? 4. MyBatis的反射异常通常有哪些原因? 5. 如何解决MyBatis Plus中QueryWrapper引起的反射异常?</think>### 解决 MyBatis 反射异常:类中没有名为 'Step' 的 setter 方法 #### 问题原因分析 当 MyBatis 出现 `ReflectionException: There is no setter for property named 'Step' in class com.web.hospital.dao.PlanVO` 异常时,表示 MyBatis 在尝试设置 `PlanVO` 类的 `Step` 属性时找不到对应的 setter 方法。结合引用内容[2][4],主要原因包括: 1. **属性名与 setter 方法不匹配**(最常见) - 属性名 `Step` 与 setter 方法命名规范不一致(如 `setstep()` 而非 `setStep()`) - 实体类中确实缺少 `setStep()` 方法 2. **大小写敏感问题** MyBatis 严格遵循 JavaBean 规范,要求 setter 方法名必须与属性名精确匹配(包括首字母大小写) 3. **映射文件配置错误** 在 XML 映射文件中错误引用了不存在的属性名 --- ### 解决方案步骤 #### 步骤 1:检查实体类 setter 方法 确保 `PlanVO` 类中存在正确命名的 setter 方法: ```java public class PlanVO { private String step; // 属性名应为小写开头 // 正确命名:set + 首字母大写的属性名 public void setStep(String step) { // ✅ 首字母大写 this.step = step; } // 错误示例:方法名与属性名大小写不匹配 public void setstep(String step) { // ❌ 首字母未大写 this.step = step; } } ``` > **关键点**: > - setter 方法名必须是 `set` + **首字母大写的属性名**(如 `setStep`) > - 实际属性名应为小写开头(如 `step`)[^2][^4] #### 步骤 2:验证 XML 映射文件 检查 MyBatis 映射文件中是否正确引用属性名: ```xml <!-- 正确:属性名使用小写开头 --> <resultMap id="planResultMap" type="PlanVO"> <result property="step" column="step_column"/> <!-- ✅ 小写step --> </resultMap> <!-- 错误:属性名首字母大写 --> <resultMap id="planResultMap" type="PlanVO"> <result property="Step" column="step_column"/> <!-- ❌ 大写Step --> </resultMap> ``` #### 步骤 3:检查 Lombok 注解(如使用) 若使用 Lombok 生成 getter/setter,确保注解正确: ```java import lombok.Getter; import lombok.Setter; @Getter @Setter // 自动生成 setStep() 方法 public class PlanVO { private String step; // Lombok 会自动生成 setStep() } ``` > **验证**:编译后查看 `target/classes` 目录下的类文件,确认存在 `setStep()` 方法 #### 步骤 4:处理动态 SQL 中的引用 在 `<if>` 等动态 SQL 标签中,使用 **小写属性名**: ```xml <select id="selectPlans" resultMap="planResultMap"> SELECT * FROM plans <where> <!-- 正确:使用小写属性名 --> <if test="step != null"> <!-- ✅ step小写 --> AND step = #{step} </if> <!-- 错误:属性名首字母大写 --> <if test="Step != null"> <!-- ❌ Step大写 --> AND step = #{step} </if> </where> </select> ``` #### 步骤 5:终极检查清单 | 检查项 | 正确示例 | 错误示例 | |------------------------|---------------------|--------------------| | 类属性定义 | `private String step;` | `private String Step;` | | setter 方法命名 | `setStep(String s)` | `setstep(String s)` | | XML 的 property 属性 | `property="step"` | `property="Step"` | | 动态 SQL 的 test 条件 | `test="step != null"` | `test="Step != null"` | --- ### 技术原理说明 MyBatis 通过 **Java 内省机制(Introspection)** 访问属性[^2]: 1. 根据 `property="step"` 查找 `setStep()` 方法 2. 方法名必须精确匹配:`set` + 首字母大写的属性名 3. 若找不到匹配方法,抛出: ```java ReflectionException: There is no setter for property named 'Step' ``` > **重要提示**:即使使用 Lombok,仍需确保字段命名符合 `lowerCamelCase` 规范[^4]。 --- ### 相关问题 1. MyBatis 中属性名大小写处理有哪些特殊规则? 2. 如何解决 Lombok 生成的 setter 方法与 MyBatis 要求不匹配的问题? 3. 在动态 SQL 中引用嵌套对象属性时要注意什么? 4. MyBatis 的 `#{}` 和 `${}` 在属性解析时有何区别? 5. 如何通过 MyBatis 日志精准定位缺失的 setter 方法? [^2]: MyBatis 通过内省机制严格匹配 setter 方法命名规范 [^4]: Lombok 生成的 setter 方法需符合 JavaBean 规范
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值