diff --git a/src/main/java/org/springframework/beans/LegacySpringBeanUtil.java b/src/main/java/org/springframework/beans/LegacySpringBeanUtil.java new file mode 100644 index 0000000..2851319 --- /dev/null +++ b/src/main/java/org/springframework/beans/LegacySpringBeanUtil.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans; + +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; + +/** + * Legacy implement for {@link BeanUtils#copyProperties(Object, Object)}, fork from spring-framework 5.2.24, + *

the copy property method will not check ResolvableType. + * + * @see BeanUtils + * @author huzijie + * @version LegacySpringBeanUtil.java, v 0.1 2023年05月25日 5:36 PM huzijie Exp $ + */ +public class LegacySpringBeanUtil { + + /** + * Copy the property values of the given source bean into the target bean. + *

Note: The source and target classes do not have to match or even be derived + * from each other, as long as the properties match. Any bean properties that the + * source bean exposes but the target bean does not will silently be ignored. + *

This is just a convenience method. For more complex transfer needs, + * consider using a full BeanWrapper. + * @param source the source bean + * @param target the target bean + * @throws BeansException if the copying failed + */ + public static void copyProperties(Object source, Object target) throws BeansException { + copyProperties(source, target, null, (String[]) null); + } + + /** + * Copy the property values of the given source bean into the given target bean, + * only setting properties defined in the given "editable" class (or interface). + *

Note: The source and target classes do not have to match or even be derived + * from each other, as long as the properties match. Any bean properties that the + * source bean exposes but the target bean does not will silently be ignored. + *

This is just a convenience method. For more complex transfer needs, + * consider using a full BeanWrapper. + * @param source the source bean + * @param target the target bean + * @param editable the class (or interface) to restrict property setting to + * @throws BeansException if the copying failed + */ + public static void copyProperties(Object source, Object target, Class editable) + throws BeansException { + copyProperties(source, target, editable, (String[]) null); + } + + /** + * Copy the property values of the given source bean into the given target bean, + * ignoring the given "ignoreProperties". + *

Note: The source and target classes do not have to match or even be derived + * from each other, as long as the properties match. Any bean properties that the + * source bean exposes but the target bean does not will silently be ignored. + *

This is just a convenience method. For more complex transfer needs, + * consider using a full BeanWrapper. + * @param source the source bean + * @param target the target bean + * @param ignoreProperties array of property names to ignore + * @throws BeansException if the copying failed + */ + public static void copyProperties(Object source, Object target, String... ignoreProperties) + throws BeansException { + copyProperties(source, target, null, ignoreProperties); + } + + /** + * Copy the property values of the given source bean into the given target bean. + *

Note: The source and target classes do not have to match or even be derived + * from each other, as long as the properties match. Any bean properties that the + * source bean exposes but the target bean does not will silently be ignored. + * @param source the source bean + * @param target the target bean + * @param editable the class (or interface) to restrict property setting to + * @param ignoreProperties array of property names to ignore + * @throws BeansException if the copying failed + */ + 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; + } + PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); + List ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) + : null); + + for (PropertyDescriptor targetPd : targetPds) { + Method writeMethod = targetPd.getWriteMethod(); + if (writeMethod != null + && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { + PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), + targetPd.getName()); + if (sourcePd != null) { + Method readMethod = sourcePd.getReadMethod(); + if (readMethod != null + && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], + readMethod.getReturnType())) { + try { + if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { + readMethod.setAccessible(true); + } + Object value = readMethod.invoke(source); + if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { + writeMethod.setAccessible(true); + } + writeMethod.invoke(target, value); + } catch (Throwable ex) { + throw new FatalBeanException("Could not copy property '" + + targetPd.getName() + + "' from source to target", ex); + } + } + } + } + } + } + + /** + * Retrieve the JavaBeans {@code PropertyDescriptor}s of a given class. + * @param clazz the Class to retrieve the PropertyDescriptors for + * @return an array of {@code PropertyDescriptors} for the given class + * @throws BeansException if PropertyDescriptor look fails + */ + private static PropertyDescriptor[] getPropertyDescriptors(Class clazz) + throws BeansException { + return CachedIntrospectionResults.forClass(clazz).getPropertyDescriptors(); + } + + /** + * Retrieve the JavaBeans {@code PropertyDescriptors} for the given property. + * @param clazz the Class to retrieve the PropertyDescriptor for + * @param propertyName the name of the property + * @return the corresponding PropertyDescriptor, or {@code null} if none + * @throws BeansException if PropertyDescriptor lookup fails + */ + private static PropertyDescriptor getPropertyDescriptor(Class clazz, String propertyName) + throws BeansException { + return CachedIntrospectionResults.forClass(clazz).getPropertyDescriptor(propertyName); + } +} diff --git a/src/test/java/com/alipay/sofa/common/utils/LegacySpringBeanUtilTest.java b/src/test/java/com/alipay/sofa/common/utils/LegacySpringBeanUtilTest.java new file mode 100644 index 0000000..99d1b18 --- /dev/null +++ b/src/test/java/com/alipay/sofa/common/utils/LegacySpringBeanUtilTest.java @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.common.utils; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.BeanUtils; + +/** + * @author huzijie + * @version LegacySpringBeanUtilTest.java, v 0.1 2023年05月26日 10:42 AM huzijie Exp $ + */ +public class LegacySpringBeanUtilTest { + + @Test + public void testCopyProperties() { + TestBean tb = new TestBean(); + tb.setName("rod"); + tb.setAge(32); + tb.setTouchy("touchy"); + TestBean tb2 = new TestBean(); + Assert.assertNull(tb2.getName()); + Assert.assertEquals(0, tb2.getAge()); + Assert.assertNull(tb2.getTouchy()); + BeanUtils.copyProperties(tb, tb2); + Assert.assertEquals(tb2.getName(), tb.getName()); + Assert.assertEquals(tb2.getAge(), tb.getAge()); + Assert.assertEquals(tb2.getTouchy(), tb.getTouchy()); + } + + @Test + public void testCopyPropertiesWithIgnore() { + TestBean tb = new TestBean(); + Assert.assertNull(tb.getName()); + tb.setAge(32); + tb.setTouchy("bla"); + TestBean tb2 = new TestBean(); + tb2.setName("rod"); + Assert.assertEquals(tb2.getAge(), 0); + Assert.assertNull(tb2.getTouchy()); + + // "spouse", "touchy", "age" should not be copied + BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age"); + Assert.assertNull(tb.getName()); + Assert.assertEquals(tb2.getAge(), 0); + Assert.assertNull(tb2.getTouchy()); + } + + @Test + public void testCopyPropertiesWithIgnoredNonExistingProperty() { + NameAndSpecialProperty source = new NameAndSpecialProperty(); + source.setName("name"); + TestBean target = new TestBean(); + BeanUtils.copyProperties(source, target, "specialProperty"); + Assert.assertEquals("name", target.getName()); + } + + @Test + public void testCopyPropertiesWithInvalidProperty() { + InvalidProperty source = new InvalidProperty(); + source.setName("name"); + source.setFlag1(true); + source.setFlag2(true); + InvalidProperty target = new InvalidProperty(); + BeanUtils.copyProperties(source, target); + Assert.assertEquals(target.getName(), "name"); + Assert.assertTrue(target.getFlag1()); + Assert.assertTrue(target.getFlag2()); + } + + private static class TestBean { + + private String name; + + private int age; + + private String touchy; + + 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 String getTouchy() { + return touchy; + } + + public void setTouchy(String touchy) { + this.touchy = touchy; + } + } + + @SuppressWarnings("unused") + private static class NameAndSpecialProperty { + + private String name; + + private int specialProperty; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public void setSpecialProperty(int specialProperty) { + this.specialProperty = specialProperty; + } + + public int getSpecialProperty() { + return specialProperty; + } + } + + @SuppressWarnings("unused") + private static class InvalidProperty { + + private String name; + + private String value; + + private boolean flag1; + + private boolean flag2; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public void setValue(int value) { + this.value = Integer.toString(value); + } + + public String getValue() { + return this.value; + } + + public void setFlag1(boolean flag1) { + this.flag1 = flag1; + } + + public Boolean getFlag1() { + return this.flag1; + } + + public void setFlag2(Boolean flag2) { + this.flag2 = flag2; + } + + public boolean getFlag2() { + return this.flag2; + } + } +}