Affects: 6.1.1
We are migrating from Spring 5.3.x to 6.1.1 and found
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.