-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: “HzjNeverStop” <“441627022@qq.com”>
- Loading branch information
1 parent
efa6342
commit 99d321b
Showing
2 changed files
with
357 additions
and
0 deletions.
There are no files selected for viewing
172 changes: 172 additions & 0 deletions
172
src/main/java/org/springframework/beans/LegacySpringBeanUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
* <p> 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. | ||
* <p>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. | ||
* <p>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). | ||
* <p>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. | ||
* <p>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". | ||
* <p>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. | ||
* <p>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. | ||
* <p>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<String> 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); | ||
} | ||
} |
185 changes: 185 additions & 0 deletions
185
src/test/java/com/alipay/sofa/common/utils/LegacySpringBeanUtilTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} | ||
} |