From 89d2bd954ab99cc4ad93ccb15f632693675ef712 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 30 Jan 2018 15:53:58 +0100 Subject: [PATCH] Properly analyze Java 9 class cast messages for lambda event listeners Issue: SPR-16435 --- .../SimpleApplicationEventMulticaster.java | 19 ++++++++++++-- .../event/ApplicationContextEventTests.java | 26 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java b/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java index eca9ff7b1ba..eace00211a0 100644 --- a/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java +++ b/spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -173,8 +173,9 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM } catch (ClassCastException ex) { String msg = ex.getMessage(); - if (msg == null || msg.startsWith(event.getClass().getName())) { + if (msg == null || matchesClassCastMessage(msg, event.getClass().getName())) { // Possibly a lambda-defined listener which we could not resolve the generic event type for + // -> let's suppress the exception and just log a debug message. Log logger = LogFactory.getLog(getClass()); if (logger.isDebugEnabled()) { logger.debug("Non-matching event type for listener: " + listener, ex); @@ -186,4 +187,18 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM } } + private boolean matchesClassCastMessage(String classCastMessage, String eventClassName) { + // On Java 8, the message simply starts with the class name: "java.lang.String cannot be cast..." + if (classCastMessage.startsWith(eventClassName)) { + return true; + } + // On Java 9, the message contains the module name: "java.base/java.lang.String cannot be cast..." + int moduleSeparatorIndex = classCastMessage.indexOf('/'); + if (moduleSeparatorIndex != -1 && classCastMessage.startsWith(eventClassName, moduleSeparatorIndex + 1)) { + return true; + } + // Assuming an unrelated class cast failure... + return false; + } + } diff --git a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java index e1c778ad8c5..f7fb80595fc 100644 --- a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -472,6 +472,30 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen context.close(); } + @Test + public void lambdaAsListenerWithJava8StyleClassCastMessage() { + StaticApplicationContext context = new StaticApplicationContext(); + ApplicationListener listener = + event -> { throw new ClassCastException(event.getClass().getName()); }; + context.addApplicationListener(listener); + context.refresh(); + + context.publishEvent(new MyEvent(context)); + context.close(); + } + + @Test + public void lambdaAsListenerWithJava9StyleClassCastMessage() { + StaticApplicationContext context = new StaticApplicationContext(); + ApplicationListener listener = + event -> { throw new ClassCastException("spring.context/" + event.getClass().getName()); }; + context.addApplicationListener(listener); + context.refresh(); + + context.publishEvent(new MyEvent(context)); + context.close(); + } + @Test public void beanPostProcessorPublishesEvents() { GenericApplicationContext context = new GenericApplicationContext();