Hello!
I have simple interface like this:
public interface ITest
{
}
and the simple event:
public class TestEvent<T extends ITest> extends ApplicationEvent
{
public TestEvent(Object source)
{
super(source);
}
}
and two listeners:
@Component
public class MethodListener
{
@EventListener
public void handleTestEvent(TestEvent<ITest> iTest)
{
System.out.println(this.getClass() + " DONE!");
}
}
2.
@Component
public class SimpleTestListener implements ApplicationListener<TestEvent<ITest>>
{
@Override
public void onApplicationEvent(TestEvent<ITest> event)
{
System.out.println(this.getClass() + " DONE!");
}
}
and when i trying to send an event via the spring context like this:
public class MySpringApplication
{
public static void main(String[] args)
{
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.publishEvent(new TestEvent<>(""));
}
}
i have different result in depends on the version Spring. When i use Spring 6.2 i can see in the log this:
class ... MethodListener DONE!
when i use Spring 6.1.15 i see both results:
class ...SimpleTestListener DONE!
class ...MethodListener DONE!
I guess it's a bug.
Comment From: sbrannen
If you make the parameterized type for TestEvent
concrete as follows, you get the expected result.
context.publishEvent(new TestEvent<ITest>("") {});
Note the use of {}
which makes it a subclass of TestEvent
bound to ITest
instead of T
.
So this might have been a bug fix instead of a bug/regression.
@jhoeller, thoughts?
Comment From: sbrannen
The following all-in-one test class demonstrates this.
@SpringJUnitConfig
class GenericApplicationEventListenerTests {
@Test
void test(ApplicationContext context) {
context.publishEvent(new TestEvent<ITest>("") {
});
}
@Configuration
@Import({MethodListener.class, SimpleTestListener.class})
static class Config {
}
interface ITest {
}
static class TestEvent<T extends ITest> extends ApplicationEvent {
TestEvent(Object source) {
super(source);
}
}
static class MethodListener {
@EventListener
public void handleTestEvent(TestEvent<ITest> iTest) {
System.err.println(getClass().getSimpleName() + " DONE!");
}
}
static class SimpleTestListener implements ApplicationListener<TestEvent<ITest>> {
@Override
public void onApplicationEvent(TestEvent<ITest> event) {
System.err.println(getClass().getSimpleName() + " DONE!");
}
}
}
Output:
SimpleTestListener DONE!
MethodListener DONE!
Comment From: anaconda875
Hi @sbrannen, I'm a Java developer who want to contribute to the framework. How can I get this issue/task assigned (to me)? Thanks
Comment From: denAbramoff
@sbrannen thanks for your reaction! Yep, this version works:
context.publishEvent(new TestEvent<ITest>("") {});
But it's not convenient and there are a lot of places where old version works.
And there is also one weird case, if i add "? extends" my listenerer will work:
@Configuration
public class WildcardSimpleTestListener implements ApplicationListener<TestEvent<? extends ITest>>
{
@Override
public void onApplicationEvent(TestEvent<? extends ITest> event)
{
System.out.println(this.getClass() + " DONE!");
}
}
It works on Spring 6.2!
class ...WildcardSimpleTestListener$$SpringCGLIB$$0 DONE!
Comment From: theigl
Probably related to #33968.
Comment From: sbrannen
Thanks for the feedback, @denAbramoff.
In light of the above, I believe this is a regression, and we'll look into it.
@jhoeller, I have tentatively assigned this to you due to your work on related issues.
Potentially Related Issues
-
20727
-
22902
-
32327
-
33968
Comment From: sbrannen
Hi @sbrannen, I'm a Java developer who want to contribute to the framework. How can I get this issue/task assigned (to me)? Thanks
Hi @anaconda875,
Thanks for the offer!
As you can see above, I've assigned to this @jhoeller, since he has done the most recent work in this area. However, if you feel confident in fixing this regression, please post your ideas here first before submitting a PR.
Comment From: anaconda875
Hi @sbrannen ,
In Spring Core , ResolvableType.isAssignableFrom
line 336-337:
WildcardBounds ourBounds = WildcardBounds.get(this); //ourBounds IS NULL SINCE this PASSED TO WildcardBounds.get() IS A ResolvableType OF ITest
WildcardBounds typeBounds = WildcardBounds.get(other); //typeBounds IS NOT NULL SINCE other PASSED TO WildcardBounds.get() IS ResolvableType OF <T extends ITest>
My proposal:
Inside red circle is newly added. Plz take a look!! Thanks
Comment From: jhoeller
This turned out to be rather involved, a consequence of the partial generics matching that we do as of 6.2 now where we also take the bounds of an unresolvable type variable into account. The solution that I went with is a rather minimal tweaking of the existing strict
flag when we go into nested generics. I hope that this covers the reported case here. @denAbramoff please give an upcoming 6.2.1 snapshot an early try...
@anaconda875 I ended up with a different approach but thanks for the PR in any case!
Comment From: sbrannen
I hope that this covers the reported case here.
I tested with context.publishEvent(new TestEvent<>(""));
on main
, and that now works as expected.
Thanks, Juergen!
Comment From: denAbramoff
Hi! This fix doesn't fix all cases. For example:
- Add to our project new interface
public interface ITest2
{
}
- Add new listener
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class DoubleListener<T extends ITest & ITest2> implements ApplicationListener<TestEvent<T>>
{
@Override
public void onApplicationEvent(TestEvent<T> event)
{
System.out.println(this.getClass() + " DONE!");
}
}
and on Spring 6.2.1 i can see in the log:
class ....SimpleTestListener DONE! class ....WildcardSimpleTestListener$$SpringCGLIB$$0 DONE! class ....MethodListener DONE!
but on Spring 6.1.16 other result:
class ....DoubleListener DONE! class ....SimpleTestListener DONE! class ....WildcardSimpleTestListener$$SpringCGLIB$$0 DONE! class ....MethodListener DONE!
Comment From: denAbramoff
@sbrannen @jhoeller can you reopen this issue?
Comment From: bclozel
@denAbramoff this issue is already fixed and released. Please create a new issue with your sample.
Comment From: denAbramoff
@bclozel but the bad behavior it's the same ... but ok - i did the new issue
Comment From: bclozel
Thanks for creating a new issue. We have 26K+ closed issues and people comment on them all the time. If you have a clear case and the relevant issue has been closed and released already, creating a new one is the best course of action.