Affects: 6.1.1


We are migrating from Spring 5.3.x to 6.1.1 and found seems not working in the XML definition. Consider the following example:

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="testReplaceMethodBean" class="test.spring.TestReplaceMethodBean">
        <replaced-method name="doSomething"
            replacer="doSomethingMethodReplacer" />
    </bean>

    <bean id="doSomethingMethodReplacer"
        class="test.spring.DoSomethingMethodReplacer" />
</beans>

TestReplaceMethodBean.java

package test.spring;

public interface TestReplaceMethodBean {
    String doSomething(Object... parameters);
}

DoSomethingMethodReplacer.java

package test.spring;

import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.beans.factory.support.MethodReplacer;

public class DoSomethingMethodReplacer implements MethodReplacer {
    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        return Arrays.toString((Object[]) args[0]);
    }
}

TestReplacedMethod.java

package test.spring;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestReplacedMethod {

    public static void main(String[] args) {
        try (AbstractApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                "classpath*:path/to/applicationContext.xml")) {
            TestReplaceMethodBean testBean = applicationContext.getBean(TestReplaceMethodBean.class);
            // expecting to print "[value1, value2]"
            System.out.println(testBean.doSomething(new Object[] { "value1", "value2" }));
        }
    }
}

It is expected to print [value1, value2] when run TestReplacedMethod but got AbstractMethodError:

Exception in thread "main" java.lang.AbstractMethodError: Receiver class test.spring.TestReplaceMethodBean$$SpringCGLIB$$0 does not define or inherit an implementation of the resolved method 'abstract java.lang.String doSomething(java.lang.Object[])' of interface test.spring.TestReplaceMethodBean.
    at test.spring.TestReplacedMethod.main(TestReplacedMethod.java:12)

Comment From: sammyhk

Further tested with <arg-type match="java.lang.Object[]" /> still not working:

Updated but still not working applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="testReplaceMethodBean" class="test.spring.TestReplaceMethodBean">
        <replaced-method name="doSomething"
            replacer="doSomethingMethodReplacer">
            <arg-type match="java.lang.Object[]" />
        </replaced-method>
    </bean>

    <bean id="doSomethingMethodReplacer"
        class="test.spring.DoSomethingMethodReplacer" />
</beans>

While adding <arg-type match="java.lang.Object" /> works, maybe this is a hint to debug:

Workaround working applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="testReplaceMethodBean" class="test.spring.TestReplaceMethodBean">
        <replaced-method name="doSomething"
            replacer="doSomethingMethodReplacer">
            <arg-type match="java.lang.Object" />
        </replaced-method>
    </bean>

    <bean id="doSomethingMethodReplacer"
        class="test.spring.DoSomethingMethodReplacer" />
</beans>

Comment From: sbrannen

Hi @sammyhk,

Thanks for raising the issue.

I've confirmed it behaves the way you've explained with Spring Framework 6.0.x using the following standalone test class that I based on your example.

package demo;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.support.MethodReplacer;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;

import static org.assertj.core.api.Assertions.assertThat;

class ReplacedMethodTests {

    @Test
    void test() {
        String xmlConfig = """
                <?xml version="1.0" encoding="UTF-8"?>
                <beans xmlns="http://www.springframework.org/schema/beans"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

                    <bean id="testReplaceMethodBean" class="demo.ReplacedMethodTests.TestReplaceMethodBean">
                        <replaced-method name="doSomething" replacer="doSomethingMethodReplacer">
                            <!--
                            <arg-type match="java.lang.Object" />
                            -->
                        </replaced-method>
                    </bean>

                    <bean id="doSomethingMethodReplacer"
                        class="demo.ReplacedMethodTests.DoSomethingMethodReplacer" />
                </beans>
                """;

        Resource xmlResource = new ByteArrayResource(xmlConfig.getBytes());

        try (GenericApplicationContext context = new GenericApplicationContext()) {
            new XmlBeanDefinitionReader(context).loadBeanDefinitions(xmlResource);
            context.refresh();

            TestReplaceMethodBean testBean = context.getBean(TestReplaceMethodBean.class);
            assertThat(testBean.doSomething("value1", "value2")).isEqualTo("[value1, value2]");
        }
    }


    interface TestReplaceMethodBean {

        String doSomething(Object... parameters);
    }

    static class DoSomethingMethodReplacer implements MethodReplacer {

        @Override
        public Object reimplement(Object obj, Method method, Object[] args) {
            return Arrays.toString((Object[]) args[0]);
        }
    }

}

I'll investigate whether this is a regression and report back here.

Comment From: sbrannen

I have confirmed that the above test class passes on 5.3.x unmodified.

So this is indeed a regression, and we are investigating the issue.

Comment From: sbrannen

While adding <arg-type match="java.lang.Object" /> works, maybe this is a hint to debug:

Yes, that was a very good tip. Thanks! 👍

After several rounds of debugging, I determined that ReplaceOverride.matches(Method) is now invoked before AbstractBeanDefinition.prepareMethodOverrides() is invoked. Consequently, the overloaded flag in ReplaceOverride has an incorrect (default) value of true which results in the exception you shared.

That's why <replaced-method /> now requires an explicit arg-type since 6.0, and I've updated the title of this issue to reflect that.

I believe that this is a regression that was introduced in commit b31a15851e7aaabf3629cc101d285e751e535927.

I have a local prototype for a fix, so hopefully we'll be able to address this in time for tomorrow's 6.0.x and 6.1.x releases.

Comment From: sbrannen

This has been fixed and backported for inclusion in 6.1.2 to 6.0.15, respectively.