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: Spring ApplicationListener no longer invoked for generic ApplicationEvent with 6.2.0

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:

  1. Add to our project new interface
public interface ITest2
{
}
  1. 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.