diff --git a/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java b/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java index ca0a9d8f6b..747465bbb5 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 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. @@ -21,8 +21,8 @@ import org.springframework.instrument.classloading.LoadTimeWeaver; /** * Interface to be implemented by * {@link org.springframework.context.annotation.Configuration @Configuration} - * classes annotated with {@link EnableLoadTimeWeaving @EnableLoadTimeWeaving} that wish to - * customize the {@link LoadTimeWeaver} instance to be used. + * classes annotated with {@link EnableLoadTimeWeaving @EnableLoadTimeWeaving} + * that wish to customize the {@link LoadTimeWeaver} instance to be used. * *
See {@link org.springframework.scheduling.annotation.EnableAsync @EnableAsync} * for usage examples and information on how a default {@code LoadTimeWeaver} @@ -36,9 +36,9 @@ import org.springframework.instrument.classloading.LoadTimeWeaver; public interface LoadTimeWeavingConfigurer { /** - * Create, configure and return the {@code LoadTimeWeaver} instance to be used. Note - * that it is unnecessary to annotate this method with {@code @Bean}, because the - * object returned will automatically be registered as a bean by + * Create, configure and return the {@code LoadTimeWeaver} instance to be used. + * Note that it is unnecessary to annotate this method with {@code @Bean} + * because the object returned will automatically be registered as a bean by * {@link LoadTimeWeavingConfiguration#loadTimeWeaver()} */ LoadTimeWeaver getLoadTimeWeaver(); diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 55cef951fd..2d45cc104f 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -769,7 +769,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor) - if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { + if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && + beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); } diff --git a/spring-context/src/main/java/org/springframework/context/support/ContextTypeMatchClassLoader.java b/spring-context/src/main/java/org/springframework/context/support/ContextTypeMatchClassLoader.java index 1f17f45719..c4e228c3c1 100644 --- a/spring-context/src/main/java/org/springframework/context/support/ContextTypeMatchClassLoader.java +++ b/spring-context/src/main/java/org/springframework/context/support/ContextTypeMatchClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 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. @@ -21,6 +21,8 @@ import java.security.ProtectionDomain; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.apache.commons.logging.LogFactory; + import org.springframework.core.DecoratingClassLoader; import org.springframework.core.OverridingClassLoader; import org.springframework.core.SmartClassLoader; @@ -45,15 +47,26 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart } - private static Method findLoadedClassMethod; + @Nullable + private static final Method findLoadedClassMethod; static { + // Try to enable findLoadedClass optimization which allows us to selectively + // override classes that have not been loaded yet. If not accessible, we will + // always override requested classes, even when the classes have been loaded + // by the parent ClassLoader already and cannot be transformed anymore anyway. + Method method = null; try { - findLoadedClassMethod = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); + method = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); + ReflectionUtils.makeAccessible(method); } - catch (NoSuchMethodException ex) { - throw new IllegalStateException("Invalid [java.lang.ClassLoader] class: no 'findLoadedClass' method defined!"); + catch (Throwable ex) { + // Typically a JDK 9+ InaccessibleObjectException... + // Avoid through JVM startup with --add-opens=java.base/java.lang=ALL-UNNAMED + LogFactory.getLog(ContextTypeMatchClassLoader.class).debug( + "ClassLoader.findLoadedClass not accessible -> will always override requested class", ex); } + findLoadedClassMethod = method; } @@ -96,13 +109,14 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart if (isExcluded(className) || ContextTypeMatchClassLoader.this.isExcluded(className)) { return false; } - ReflectionUtils.makeAccessible(findLoadedClassMethod); - ClassLoader parent = getParent(); - while (parent != null) { - if (ReflectionUtils.invokeMethod(findLoadedClassMethod, parent, className) != null) { - return false; + if (findLoadedClassMethod != null) { + ClassLoader parent = getParent(); + while (parent != null) { + if (ReflectionUtils.invokeMethod(findLoadedClassMethod, parent, className) != null) { + return false; + } + parent = parent.getParent(); } - parent = parent.getParent(); } return true; } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java index b845a68efc..58c8d8753f 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java @@ -79,7 +79,7 @@ class EnableLoadTimeWeavingTests { @Configuration - @EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.DISABLED) + @EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.DISABLED) static class EnableLTWConfig_withAjWeavingDisabled implements LoadTimeWeavingConfigurer { @Override @@ -88,8 +88,9 @@ class EnableLoadTimeWeavingTests { } } + @Configuration - @EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.AUTODETECT) + @EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.AUTODETECT) static class EnableLTWConfig_withAjWeavingAutodetect implements LoadTimeWeavingConfigurer { @Override @@ -98,8 +99,9 @@ class EnableLoadTimeWeavingTests { } } + @Configuration - @EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.ENABLED) + @EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.ENABLED) static class EnableLTWConfig_withAjWeavingEnabled implements LoadTimeWeavingConfigurer { @Override diff --git a/spring-context/src/test/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaverTests.java b/spring-context/src/test/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaverTests.java index 448f71572a..989b1e196d 100644 --- a/spring-context/src/test/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaverTests.java +++ b/spring-context/src/test/java/org/springframework/instrument/classloading/ReflectiveLoadTimeWeaverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -85,16 +85,13 @@ public class ReflectiveLoadTimeWeaverTests { private int numTimesAddTransformerCalled = 0; - public int getNumTimesGetThrowawayClassLoaderCalled() { return this.numTimesAddTransformerCalled; } - public void addTransformer(ClassFileTransformer transformer) { ++this.numTimesAddTransformerCalled; } - } @@ -102,18 +99,15 @@ public class ReflectiveLoadTimeWeaverTests { private int numTimesGetThrowawayClassLoaderCalled = 0; - @Override public int getNumTimesGetThrowawayClassLoaderCalled() { return this.numTimesGetThrowawayClassLoaderCalled; } - public ClassLoader getThrowawayClassLoader() { ++this.numTimesGetThrowawayClassLoaderCalled; return getClass().getClassLoader(); } - } } diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java index 3efb7663c3..9bd022ccee 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java @@ -22,6 +22,7 @@ import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; @@ -508,14 +509,14 @@ public class ReflectUtils { catch (InvocationTargetException ex) { throw new CodeGenerationException(ex.getTargetException()); } - catch (Throwable ex) { - // Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+ - // (on the module path and/or with a JVM bootstrapped with --illegal-access=deny) - if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) { - throw new CodeGenerationException(ex); - } + catch (InaccessibleObjectException ex) { + // setAccessible failed with JDK 9+ InaccessibleObjectException -> fall through + // Avoid through JVM startup with --add-opens=java.base/java.lang=ALL-UNNAMED t = ex; } + catch (Throwable ex) { + throw new CodeGenerationException(ex); + } } } @@ -525,13 +526,14 @@ public class ReflectUtils { MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(contextClass, MethodHandles.lookup()); c = lookup.defineClass(b); } - catch (IllegalAccessException ex) { + catch (LinkageError | IllegalAccessException ex) { throw new CodeGenerationException(ex) { @Override public String getMessage() { return "ClassLoader mismatch for [" + contextClass.getName() + "]: JVM should be started with --add-opens=java.base/java.lang=ALL-UNNAMED " + - "for ClassLoader.defineClass to be accessible on " + loader.getClass().getName(); + "for ClassLoader.defineClass to be accessible on " + loader.getClass().getName() + + "; consider co-locating the affected class in that target ClassLoader instead."; } }; } diff --git a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java index 18dd5396df..0fdbb75d47 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.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. @@ -507,16 +507,8 @@ public abstract class ReflectionUtils { * @see java.lang.Object#equals(Object) */ public static boolean isEqualsMethod(@Nullable Method method) { - if (method == null) { - return false; - } - if (method.getParameterCount() != 1) { - return false; - } - if (!method.getName().equals("equals")) { - return false; - } - return method.getParameterTypes()[0] == Object.class; + return (method != null && method.getParameterCount() == 1 && method.getName().equals("equals") && + method.getParameterTypes()[0] == Object.class); } /** @@ -524,7 +516,7 @@ public abstract class ReflectionUtils { * @see java.lang.Object#hashCode() */ public static boolean isHashCodeMethod(@Nullable Method method) { - return method != null && method.getParameterCount() == 0 && method.getName().equals("hashCode"); + return (method != null && method.getParameterCount() == 0 && method.getName().equals("hashCode")); } /** diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java index 07ee18b10b..e4b7f1c83c 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java @@ -97,16 +97,18 @@ import org.springframework.util.Assert; * support nested transactions! Hence, do not expect Hibernate access code to * semantically participate in a nested transaction. * + *
NOTE: Hibernate ORM 6.x is officially only supported as a JPA provider. + * Please use {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean} + * with {@link org.springframework.orm.jpa.JpaTransactionManager} there instead. + * * @author Juergen Hoeller * @since 4.2 * @see #setSessionFactory - * @see #setDataSource * @see SessionFactory#getCurrentSession() - * @see DataSourceUtils#getConnection - * @see DataSourceUtils#releaseConnection * @see org.springframework.jdbc.core.JdbcTemplate * @see org.springframework.jdbc.support.JdbcTransactionManager - * @see org.springframework.transaction.jta.JtaTransactionManager + * @see org.springframework.orm.jpa.JpaTransactionManager + * @see org.springframework.orm.jpa.vendor.HibernateJpaDialect */ @SuppressWarnings("serial") public class HibernateTransactionManager extends AbstractPlatformTransactionManager @@ -271,7 +273,11 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana * @see Connection#setHoldability * @see ResultSet#HOLD_CURSORS_OVER_COMMIT * @see #disconnectOnCompletion(Session) + * @deprecated as of 5.3.29 since Hibernate 5.x aggressively closes ResultSets on commit, + * making it impossible to rely on ResultSet holdability. Also, Spring does not provide + * an equivalent setting on {@link org.springframework.orm.jpa.JpaTransactionManager}. */ + @Deprecated(since = "5.3.29") public void setAllowResultAccessAfterCompletion(boolean allowResultAccessAfterCompletion) { this.allowResultAccessAfterCompletion = allowResultAccessAfterCompletion; } @@ -487,7 +493,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana session = txObject.getSessionHolder().getSession().unwrap(SessionImplementor.class); - boolean holdabilityNeeded = this.allowResultAccessAfterCompletion && !txObject.isNewSession(); + boolean holdabilityNeeded = (this.allowResultAccessAfterCompletion && !txObject.isNewSession()); boolean isolationLevelNeeded = (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT); if (holdabilityNeeded || isolationLevelNeeded || definition.isReadOnly()) { if (this.prepareConnection && ConnectionReleaseMode.ON_CLOSE.equals( @@ -500,7 +506,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); txObject.setReadOnly(definition.isReadOnly()); - if (this.allowResultAccessAfterCompletion && !txObject.isNewSession()) { + if (holdabilityNeeded) { int currentHoldability = con.getHoldability(); if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) { txObject.setPreviousHoldability(currentHoldability); diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java index 597562a833..e8c41872ee 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java @@ -66,6 +66,10 @@ import org.springframework.lang.Nullable; * {@link HibernateTransactionManager}, this naturally allows for mixing JPA access code * with native Hibernate access code within the same transaction. * + *
NOTE: Hibernate ORM 6.x is officially only supported as a JPA provider. + * Please use {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean} + * with {@link org.springframework.orm.jpa.JpaTransactionManager} there instead. + * * @author Juergen Hoeller * @since 4.2 * @see #setDataSource