From 156b3696a784b84f8b285a4afc622d745f22a7e9 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 7 Aug 2023 14:51:49 +0200 Subject: [PATCH 1/2] Reinstate Introspector.flushFromCaches() call for JDK ClassInfo cache Closes gh-27781 --- .../beans/StandardBeanInfoFactory.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/StandardBeanInfoFactory.java b/spring-beans/src/main/java/org/springframework/beans/StandardBeanInfoFactory.java index 7abdade7c47..d93d8d6a690 100644 --- a/spring-beans/src/main/java/org/springframework/beans/StandardBeanInfoFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/StandardBeanInfoFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -68,9 +68,21 @@ public class StandardBeanInfoFactory implements BeanInfoFactory, Ordered { @Override @NonNull public BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException { - return (shouldIntrospectorIgnoreBeaninfoClasses ? + BeanInfo beanInfo = (shouldIntrospectorIgnoreBeaninfoClasses ? Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) : Introspector.getBeanInfo(beanClass)); + + // Immediately remove class from Introspector cache to allow for proper garbage + // collection on class loader shutdown; we cache it in CachedIntrospectionResults + // in a GC-friendly manner. This is necessary (again) for the JDK ClassInfo cache. + Class classToFlush = beanClass; + do { + Introspector.flushFromCaches(classToFlush); + classToFlush = classToFlush.getSuperclass(); + } + while (classToFlush != null && classToFlush != Object.class); + + return beanInfo; } @Override From 2aae0a4e0cc7fc281408511e28bff023708aabb8 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 7 Aug 2023 14:51:58 +0200 Subject: [PATCH 2/2] Polishing --- .../jdbc/core/BeanPropertyRowMapperTests.java | 6 +-- .../jdbc/core/support/LobSupportTests.java | 49 +++++++++---------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java index 4fc63340bda..b83219be06f 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java @@ -50,7 +50,7 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests { void overridingDifferentClassDefinedForMapping() { BeanPropertyRowMapper mapper = new BeanPropertyRowMapper(Person.class); assertThatExceptionOfType(InvalidDataAccessApiUsageException.class) - .isThrownBy(() -> mapper.setMappedClass(Long.class)); + .isThrownBy(() -> mapper.setMappedClass(Long.class)); } @Test @@ -104,7 +104,7 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests { BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(ExtendedPerson.class, true); Mock mock = new Mock(); assertThatExceptionOfType(InvalidDataAccessApiUsageException.class) - .isThrownBy(() -> mock.getJdbcTemplate().query("select name, age, birth_date, balance from people", mapper)); + .isThrownBy(() -> mock.getJdbcTemplate().query("select name, age, birth_date, balance from people", mapper)); } @Test @@ -112,7 +112,7 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests { BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class); Mock mock = new Mock(MockType.TWO); assertThatExceptionOfType(TypeMismatchException.class) - .isThrownBy(() -> mock.getJdbcTemplate().query(SELECT_NULL_AS_AGE, mapper)); + .isThrownBy(() -> mock.getJdbcTemplate().query(SELECT_NULL_AS_AGE, mapper)); } @Test diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/LobSupportTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/LobSupportTests.java index 3e582ae8a49..9bee729228e 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/LobSupportTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/support/LobSupportTests.java @@ -23,7 +23,6 @@ import java.sql.SQLException; import org.junit.jupiter.api.Test; -import org.springframework.dao.DataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.jdbc.LobRetrievalFailureException; import org.springframework.jdbc.support.lob.LobCreator; @@ -55,11 +54,9 @@ public class LobSupportTests { final SetValuesCalled svc = new SetValuesCalled(); - AbstractLobCreatingPreparedStatementCallback psc = new AbstractLobCreatingPreparedStatementCallback( - handler) { + AbstractLobCreatingPreparedStatementCallback psc = new AbstractLobCreatingPreparedStatementCallback(handler) { @Override - protected void setValues(PreparedStatement ps, LobCreator lobCreator) - throws SQLException, DataAccessException { + protected void setValues(PreparedStatement ps, LobCreator lobCreator) { svc.b = true; } }; @@ -73,46 +70,43 @@ public class LobSupportTests { @Test public void testAbstractLobStreamingResultSetExtractorNoRows() throws SQLException { - ResultSet rset = mock(); + ResultSet rs = mock(); AbstractLobStreamingResultSetExtractor lobRse = getResultSetExtractor(false); - assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() -> - lobRse.extractData(rset)); - verify(rset).next(); + assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class) + .isThrownBy(() -> lobRse.extractData(rs)); + verify(rs).next(); } @Test public void testAbstractLobStreamingResultSetExtractorOneRow() throws SQLException { - ResultSet rset = mock(); - given(rset.next()).willReturn(true, false); + ResultSet rs = mock(); + given(rs.next()).willReturn(true, false); AbstractLobStreamingResultSetExtractor lobRse = getResultSetExtractor(false); - lobRse.extractData(rset); - verify(rset).clearWarnings(); + lobRse.extractData(rs); + verify(rs).clearWarnings(); } @Test - public void testAbstractLobStreamingResultSetExtractorMultipleRows() - throws SQLException { - ResultSet rset = mock(); - given(rset.next()).willReturn(true, true, false); + public void testAbstractLobStreamingResultSetExtractorMultipleRows() throws SQLException { + ResultSet rs = mock(); + given(rs.next()).willReturn(true, true, false); AbstractLobStreamingResultSetExtractor lobRse = getResultSetExtractor(false); - assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class).isThrownBy(() -> - lobRse.extractData(rset)); - verify(rset).clearWarnings(); + assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class) + .isThrownBy(() -> lobRse.extractData(rs)); + verify(rs).clearWarnings(); } @Test - public void testAbstractLobStreamingResultSetExtractorCorrectException() - throws SQLException { - ResultSet rset = mock(); - given(rset.next()).willReturn(true); + public void testAbstractLobStreamingResultSetExtractorCorrectException() throws SQLException { + ResultSet rs = mock(); + given(rs.next()).willReturn(true); AbstractLobStreamingResultSetExtractor lobRse = getResultSetExtractor(true); - assertThatExceptionOfType(LobRetrievalFailureException.class).isThrownBy(() -> - lobRse.extractData(rset)); + assertThatExceptionOfType(LobRetrievalFailureException.class) + .isThrownBy(() -> lobRse.extractData(rs)); } private AbstractLobStreamingResultSetExtractor getResultSetExtractor(final boolean ex) { AbstractLobStreamingResultSetExtractor lobRse = new AbstractLobStreamingResultSetExtractor<>() { - @Override protected void streamData(ResultSet rs) throws SQLException, IOException { if (ex) { @@ -125,4 +119,5 @@ public class LobSupportTests { }; return lobRse; } + }