Skip to content

Commit

Permalink
Add LegacySpringBanUtil (#180)
Browse files Browse the repository at this point in the history
Co-authored-by: “HzjNeverStop” <“441627022@qq.com”>
  • Loading branch information
HzjNeverStop and “HzjNeverStop” committed May 30, 2023
1 parent efa6342 commit 99d321b
Show file tree
Hide file tree
Showing 2 changed files with 357 additions and 0 deletions.
172 changes: 172 additions & 0 deletions src/main/java/org/springframework/beans/LegacySpringBeanUtil.java
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);
}
}
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;
}
}
}

0 comments on commit 99d321b

Please sign in to comment.