From 132d8c7f453f6770ec5ca08e72446b65fc0c8528 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 4 Feb 2022 23:19:06 +0100 Subject: [PATCH 1/3] Support for CGLIB BeanMap utility on JDK 17 Closes gh-27802 --- spring-core/spring-core.gradle | 1 + .../springframework/cglib/beans/BeanMap.java | 330 ++++++++++++++++++ .../cglib/beans/package-info.java | 10 + .../cglib/core/package-info.java | 2 +- .../cglib/proxy/package-info.java | 2 +- 5 files changed, 343 insertions(+), 2 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/beans/package-info.java diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index e4c568f84b1..69f78fc7306 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -76,6 +76,7 @@ jar { dependsOn cglibRepackJar from(zipTree(cglibRepackJar.archivePath)) { include "org/springframework/cglib/**" + exclude "org/springframework/cglib/beans/BeanMap*.class" exclude "org/springframework/cglib/core/AbstractClassGenerator*.class" exclude "org/springframework/cglib/core/AsmApi*.class" exclude "org/springframework/cglib/core/KeyFactory.class" diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java b/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java new file mode 100644 index 00000000000..cfe84363599 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/beans/BeanMap.java @@ -0,0 +1,330 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cglib.beans; + +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.asm.ClassVisitor; +import org.springframework.cglib.core.AbstractClassGenerator; +import org.springframework.cglib.core.KeyFactory; +import org.springframework.cglib.core.ReflectUtils; + +/** + * A Map-based view of a JavaBean. The default set of keys is the + * union of all property names (getters or setters). An attempt to set + * a read-only property will be ignored, and write-only properties will + * be returned as null. Removal of objects is not a + * supported (the key set is fixed). + * @author Chris Nokleberg + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class BeanMap implements Map { + /** + * Limit the properties reflected in the key set of the map + * to readable properties. + * @see BeanMap.Generator#setRequire + */ + public static final int REQUIRE_GETTER = 1; + + /** + * Limit the properties reflected in the key set of the map + * to writable properties. + * @see BeanMap.Generator#setRequire + */ + public static final int REQUIRE_SETTER = 2; + + /** + * Helper method to create a new BeanMap. For finer + * control over the generated instance, use a new instance of + * BeanMap.Generator instead of this static method. + * @param bean the JavaBean underlying the map + * @return a new BeanMap instance + */ + public static BeanMap create(Object bean) { + Generator gen = new Generator(); + gen.setBean(bean); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(BeanMap.class.getName()); + + private static final BeanMapKey KEY_FACTORY = + (BeanMapKey)KeyFactory.create(BeanMapKey.class, KeyFactory.CLASS_BY_NAME); + + interface BeanMapKey { + public Object newInstance(Class type, int require); + } + + private Object bean; + private Class beanClass; + private int require; + + public Generator() { + super(SOURCE); + } + + /** + * Set the bean that the generated map should reflect. The bean may be swapped + * out for another bean of the same type using {@link #setBean}. + * Calling this method overrides any value previously set using {@link #setBeanClass}. + * You must call either this method or {@link #setBeanClass} before {@link #create}. + * @param bean the initial bean + */ + public void setBean(Object bean) { + this.bean = bean; + if (bean != null) { + beanClass = bean.getClass(); + setContextClass(beanClass); + } + } + + /** + * Set the class of the bean that the generated map should support. + * You must call either this method or {@link #setBeanClass} before {@link #create}. + * @param beanClass the class of the bean + */ + public void setBeanClass(Class beanClass) { + this.beanClass = beanClass; + } + + /** + * Limit the properties reflected by the generated map. + * @param require any combination of {@link #REQUIRE_GETTER} and + * {@link #REQUIRE_SETTER}; default is zero (any property allowed) + */ + public void setRequire(int require) { + this.require = require; + } + + protected ClassLoader getDefaultClassLoader() { + return beanClass.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(beanClass); + } + + /** + * Create a new instance of the BeanMap. An existing + * generated class will be reused if possible. + */ + public BeanMap create() { + if (beanClass == null) + throw new IllegalArgumentException("Class of bean unknown"); + setNamePrefix(beanClass.getName()); + return (BeanMap)super.create(KEY_FACTORY.newInstance(beanClass, require)); + } + + public void generateClass(ClassVisitor v) throws Exception { + new BeanMapEmitter(v, getClassName(), beanClass, require); + } + + protected Object firstInstance(Class type) { + return ((BeanMap)ReflectUtils.newInstance(type)).newInstance(bean); + } + + protected Object nextInstance(Object instance) { + return ((BeanMap)instance).newInstance(bean); + } + } + + /** + * Create a new BeanMap instance using the specified bean. + * This is faster than using the {@link #create} static method. + * @param bean the JavaBean underlying the map + * @return a new BeanMap instance + */ + abstract public BeanMap newInstance(Object bean); + + /** + * Get the type of a property. + * @param name the name of the JavaBean property + * @return the type of the property, or null if the property does not exist + */ + abstract public Class getPropertyType(String name); + + protected Object bean; + + protected BeanMap() { + } + + protected BeanMap(Object bean) { + setBean(bean); + } + + public Object get(Object key) { + return get(bean, key); + } + + public Object put(Object key, Object value) { + return put(bean, key, value); + } + + /** + * Get the property of a bean. This allows a BeanMap + * to be used statically for multiple beans--the bean instance tied to the + * map is ignored and the bean passed to this method is used instead. + * @param bean the bean to query; must be compatible with the type of + * this BeanMap + * @param key must be a String + * @return the current value, or null if there is no matching property + */ + abstract public Object get(Object bean, Object key); + + /** + * Set the property of a bean. This allows a BeanMap + * to be used statically for multiple beans--the bean instance tied to the + * map is ignored and the bean passed to this method is used instead. + * @param key must be a String + * @return the old value, if there was one, or null + */ + abstract public Object put(Object bean, Object key, Object value); + + /** + * Change the underlying bean this map should use. + * @param bean the new JavaBean + * @see #getBean + */ + public void setBean(Object bean) { + this.bean = bean; + } + + /** + * Return the bean currently in use by this map. + * @return the current JavaBean + * @see #setBean + */ + public Object getBean() { + return bean; + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public boolean containsKey(Object key) { + return keySet().contains(key); + } + + public boolean containsValue(Object value) { + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object v = get(it.next()); + if (((value == null) && (v == null)) || (value != null && value.equals(v))) + return true; + } + return false; + } + + public int size() { + return keySet().size(); + } + + public boolean isEmpty() { + return size() == 0; + } + + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + public void putAll(Map t) { + for (Iterator it = t.keySet().iterator(); it.hasNext();) { + Object key = it.next(); + put(key, t.get(key)); + } + } + + public boolean equals(Object o) { + if (o == null || !(o instanceof Map)) { + return false; + } + Map other = (Map)o; + if (size() != other.size()) { + return false; + } + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + if (!other.containsKey(key)) { + return false; + } + Object v1 = get(key); + Object v2 = other.get(key); + if (!((v1 == null) ? v2 == null : v1.equals(v2))) { + return false; + } + } + return true; + } + + public int hashCode() { + int code = 0; + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + Object value = get(key); + code += ((key == null) ? 0 : key.hashCode()) ^ + ((value == null) ? 0 : value.hashCode()); + } + return code; + } + + // TODO: optimize + public Set entrySet() { + HashMap copy = new HashMap(); + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + copy.put(key, get(key)); + } + return Collections.unmodifiableMap(copy).entrySet(); + } + + public Collection values() { + Set keys = keySet(); + List values = new ArrayList(keys.size()); + for (Iterator it = keys.iterator(); it.hasNext();) { + values.add(get(it.next())); + } + return Collections.unmodifiableCollection(values); + } + + /* + * @see java.util.AbstractMap#toString + */ + public String toString() + { + StringBuffer sb = new StringBuffer(); + sb.append('{'); + for (Iterator it = keySet().iterator(); it.hasNext();) { + Object key = it.next(); + sb.append(key); + sb.append('='); + sb.append(get(key)); + if (it.hasNext()) { + sb.append(", "); + } + } + sb.append('}'); + return sb.toString(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/beans/package-info.java b/spring-core/src/main/java/org/springframework/cglib/beans/package-info.java new file mode 100644 index 00000000000..51cffc06e70 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/beans/package-info.java @@ -0,0 +1,10 @@ +/** + * Spring's repackaging of the + * CGLIB beans package + * (for internal use only). + * + *

As this repackaging happens at the class file level, sources + * and javadocs are not available here... except for a few files + * that have been patched for Spring's purposes on JDK 9-17. + */ +package org.springframework.cglib.beans; diff --git a/spring-core/src/main/java/org/springframework/cglib/core/package-info.java b/spring-core/src/main/java/org/springframework/cglib/core/package-info.java index a2ed94ff2ea..6d43d8c8bcc 100644 --- a/spring-core/src/main/java/org/springframework/cglib/core/package-info.java +++ b/spring-core/src/main/java/org/springframework/cglib/core/package-info.java @@ -5,6 +5,6 @@ * *

As this repackaging happens at the class file level, sources * and javadocs are not available here... except for a few files - * that have been patched for Spring's purposes on JDK 9/10/11. + * that have been patched for Spring's purposes on JDK 9-17. */ package org.springframework.cglib.core; diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/package-info.java b/spring-core/src/main/java/org/springframework/cglib/proxy/package-info.java index 0d651c8f046..9f8cfe268e5 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/package-info.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/package-info.java @@ -5,6 +5,6 @@ * *

As this repackaging happens at the class file level, sources * and javadocs are not available here... except for a few files - * that have been patched for Spring's purposes on JDK 9/10/11. + * that have been patched for Spring's purposes on JDK 9-17. */ package org.springframework.cglib.proxy; From a71a45e71965acd9b4c5d9863bcd6f378990a465 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 4 Feb 2022 23:21:00 +0100 Subject: [PATCH 2/3] Deprecate AsyncTaskExecutor.execute(Runnable task, long startTimeout) Closes gh-27959 --- .../commonj/WorkManagerTaskExecutor.java | 3 ++- .../quartz/SimpleThreadPoolTaskExecutor.java | 3 ++- .../concurrent/ConcurrentTaskExecutor.java | 3 ++- .../concurrent/ThreadPoolTaskExecutor.java | 3 ++- .../concurrent/ThreadPoolTaskScheduler.java | 3 ++- .../core/task/AsyncTaskExecutor.java | 20 ++++++++++++++----- .../core/task/SimpleAsyncTaskExecutor.java | 8 +++++++- .../core/task/TaskRejectedException.java | 3 +-- .../core/task/TaskTimeoutException.java | 5 +++-- .../task/support/TaskExecutorAdapter.java | 3 ++- .../jca/work/SimpleTaskWorkManager.java | 6 +++--- .../jca/work/WorkManagerTaskExecutor.java | 12 ++++++++--- 12 files changed, 50 insertions(+), 22 deletions(-) diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java index a9adcc823d5..8d295684407 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/commonj/WorkManagerTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -171,6 +171,7 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java index 35534962d9f..6cfa717398e 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -76,6 +76,7 @@ public class SimpleThreadPoolTaskExecutor extends SimpleThreadPool } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java index 0b976d3f00a..9c19aad5660 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -147,6 +147,7 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche this.adaptedExecutor.execute(task); } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { this.adaptedExecutor.execute(task, startTimeout); diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java index 928814524c6..6a8334feebf 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -340,6 +340,7 @@ public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java index 718c5833929..946f5bcc6f2 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -282,6 +282,7 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java index 12ddd769cd6..d4cf0324bca 100644 --- a/spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java +++ b/spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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,7 @@ import java.util.concurrent.Future; /** * Extended interface for asynchronous {@link TaskExecutor} implementations, - * offering an overloaded {@link #execute(Runnable, long)} variant with a start - * timeout parameter as well support for {@link java.util.concurrent.Callable}. + * offering support for {@link java.util.concurrent.Callable}. * *

Note: The {@link java.util.concurrent.Executors} class includes a set of * methods that can convert some other common closure-like objects, for example, @@ -41,10 +40,18 @@ import java.util.concurrent.Future; */ public interface AsyncTaskExecutor extends TaskExecutor { - /** Constant that indicates immediate execution. */ + /** + * Constant that indicates immediate execution. + * @deprecated as of 5.3.16 along with {@link #execute(Runnable, long)} + */ + @Deprecated long TIMEOUT_IMMEDIATE = 0; - /** Constant that indicates no time limit. */ + /** + * Constant that indicates no time limit. + * @deprecated as of 5.3.16 along with {@link #execute(Runnable, long)} + */ + @Deprecated long TIMEOUT_INDEFINITE = Long.MAX_VALUE; @@ -58,7 +65,10 @@ public interface AsyncTaskExecutor extends TaskExecutor { * @throws TaskTimeoutException in case of the task being rejected because * of the timeout (i.e. it cannot be started in time) * @throws TaskRejectedException if the given task was not accepted + * @see #execute(Runnable) + * @deprecated as of 5.3.16 since the common executors do not support start timeouts */ + @Deprecated void execute(Runnable task, long startTimeout); /** diff --git a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java index 7d96032ad80..072502a868c 100644 --- a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java +++ b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -174,6 +174,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator * if configured (through the superclass's settings). * @see #doExecute(Runnable) */ + @SuppressWarnings("deprecation") @Override public void execute(Runnable task) { execute(task, TIMEOUT_INDEFINITE); @@ -188,6 +189,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator * @see #TIMEOUT_IMMEDIATE * @see #doExecute(Runnable) */ + @Deprecated @Override public void execute(Runnable task, long startTimeout) { Assert.notNull(task, "Runnable must not be null"); @@ -201,6 +203,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator } } + @SuppressWarnings("deprecation") @Override public Future submit(Runnable task) { FutureTask future = new FutureTask<>(task, null); @@ -208,6 +211,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator return future; } + @SuppressWarnings("deprecation") @Override public Future submit(Callable task) { FutureTask future = new FutureTask<>(task); @@ -215,6 +219,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator return future; } + @SuppressWarnings("deprecation") @Override public ListenableFuture submitListenable(Runnable task) { ListenableFutureTask future = new ListenableFutureTask<>(task, null); @@ -222,6 +227,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator return future; } + @SuppressWarnings("deprecation") @Override public ListenableFuture submitListenable(Callable task) { ListenableFutureTask future = new ListenableFutureTask<>(task); diff --git a/spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java b/spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java index f6294c52136..e77d4df1bc8 100644 --- a/spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java +++ b/spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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. @@ -25,7 +25,6 @@ import java.util.concurrent.RejectedExecutionException; * @author Juergen Hoeller * @since 2.0.1 * @see TaskExecutor#execute(Runnable) - * @see TaskTimeoutException */ @SuppressWarnings("serial") public class TaskRejectedException extends RejectedExecutionException { diff --git a/spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java b/spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java index 3352a622bce..ddec24acb69 100644 --- a/spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java +++ b/spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2022 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. @@ -23,8 +23,9 @@ package org.springframework.core.task; * @author Juergen Hoeller * @since 2.0.3 * @see AsyncTaskExecutor#execute(Runnable, long) - * @see TaskRejectedException + * @deprecated as of 5.3.16 since the common executors do not support start timeouts */ +@Deprecated @SuppressWarnings("serial") public class TaskTimeoutException extends TaskRejectedException { diff --git a/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java index 81da48db8e0..72a485b4eda 100644 --- a/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java +++ b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -97,6 +97,7 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor { } } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { execute(task); diff --git a/spring-tx/src/main/java/org/springframework/jca/work/SimpleTaskWorkManager.java b/spring-tx/src/main/java/org/springframework/jca/work/SimpleTaskWorkManager.java index 90b8214df34..b078744408f 100644 --- a/spring-tx/src/main/java/org/springframework/jca/work/SimpleTaskWorkManager.java +++ b/spring-tx/src/main/java/org/springframework/jca/work/SimpleTaskWorkManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -31,7 +31,6 @@ import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.core.task.TaskRejectedException; -import org.springframework.core.task.TaskTimeoutException; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -143,6 +142,7 @@ public class SimpleTaskWorkManager implements WorkManager { * (or -1 if not applicable or not known) * @throws WorkException if the TaskExecutor did not accept the Work */ + @SuppressWarnings("deprecation") protected long executeWork(TaskExecutor taskExecutor, Work work, long startTimeout, boolean blockUntilStarted, @Nullable ExecutionContext executionContext, @Nullable WorkListener workListener) throws WorkException { @@ -164,7 +164,7 @@ public class SimpleTaskWorkManager implements WorkManager { taskExecutor.execute(workHandle); } } - catch (TaskTimeoutException ex) { + catch (org.springframework.core.task.TaskTimeoutException ex) { WorkException wex = new WorkRejectedException("TaskExecutor rejected Work because of timeout: " + work, ex); wex.setErrorCode(WorkException.START_TIMED_OUT); workListenerToUse.workRejected(new WorkEvent(this, WorkEvent.WORK_REJECTED, work, wex)); diff --git a/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java b/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java index e4fe06bfeb3..496d4ca80d8 100644 --- a/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java +++ b/spring-tx/src/main/java/org/springframework/jca/work/WorkManagerTaskExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -33,7 +33,6 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.core.task.AsyncListenableTaskExecutor; import org.springframework.core.task.TaskDecorator; import org.springframework.core.task.TaskRejectedException; -import org.springframework.core.task.TaskTimeoutException; import org.springframework.jca.context.BootstrapContextAware; import org.springframework.jndi.JndiLocatorSupport; import org.springframework.lang.Nullable; @@ -218,11 +217,13 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport // Implementation of the Spring SchedulingTaskExecutor interface //------------------------------------------------------------------------- + @SuppressWarnings("deprecation") @Override public void execute(Runnable task) { execute(task, TIMEOUT_INDEFINITE); } + @Deprecated @Override public void execute(Runnable task, long startTimeout) { Work work = new DelegatingWork(this.taskDecorator != null ? this.taskDecorator.decorate(task) : task); @@ -254,7 +255,8 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport } catch (WorkRejectedException ex) { if (WorkException.START_TIMED_OUT.equals(ex.getErrorCode())) { - throw new TaskTimeoutException("JCA WorkManager rejected task because of timeout: " + task, ex); + throw new org.springframework.core.task.TaskTimeoutException( + "JCA WorkManager rejected task because of timeout: " + task, ex); } else { throw new TaskRejectedException("JCA WorkManager rejected task: " + task, ex); @@ -265,6 +267,7 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport } } + @SuppressWarnings("deprecation") @Override public Future submit(Runnable task) { FutureTask future = new FutureTask<>(task, null); @@ -272,6 +275,7 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport return future; } + @SuppressWarnings("deprecation") @Override public Future submit(Callable task) { FutureTask future = new FutureTask<>(task); @@ -279,6 +283,7 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport return future; } + @SuppressWarnings("deprecation") @Override public ListenableFuture submitListenable(Runnable task) { ListenableFutureTask future = new ListenableFutureTask<>(task, null); @@ -286,6 +291,7 @@ public class WorkManagerTaskExecutor extends JndiLocatorSupport return future; } + @SuppressWarnings("deprecation") @Override public ListenableFuture submitListenable(Callable task) { ListenableFutureTask future = new ListenableFutureTask<>(task); From bc9cd9a687b598a3d567ebcd9fb4108e0342ce84 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 4 Feb 2022 23:21:27 +0100 Subject: [PATCH 3/3] Find interface method even for late-bound interface declaration in subclass Closes gh-27995 --- .../AbstractAutowireCapableBeanFactory.java | 4 +- .../support/DisposableBeanAdapter.java | 10 ++-- .../org/springframework/util/ClassUtils.java | 60 +++++++++++++------ .../support/ReflectiveMethodExecutor.java | 14 ++++- .../support/ReflectiveMethodResolver.java | 8 +-- .../support/ReflectivePropertyAccessor.java | 24 ++++---- 6 files changed, 78 insertions(+), 42 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index e3908a2a7c3..7a94a91c86a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -1908,7 +1908,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac if (logger.isTraceEnabled()) { logger.trace("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'"); } - Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod); + Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, bean.getClass()); if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction) () -> { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index e6f90239343..b5fdef4dd81 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -138,7 +138,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { beanName + "' has a non-boolean parameter - not supported as destroy method"); } } - destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod); + destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod, bean.getClass()); } this.destroyMethod = destroyMethod; } @@ -252,9 +252,9 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { invokeCustomDestroyMethod(this.destroyMethod); } else if (this.destroyMethodName != null) { - Method methodToInvoke = determineDestroyMethod(this.destroyMethodName); - if (methodToInvoke != null) { - invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke)); + Method destroyMethod = determineDestroyMethod(this.destroyMethodName); + if (destroyMethod != null) { + invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(destroyMethod, this.bean.getClass())); } } } diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index c9fa24824af..0c1cccf1d63 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -1256,7 +1256,7 @@ public abstract class ClassUtils { * (may be {@code null} or may not even implement the method) * @return the specific target method, or the original method if the * {@code targetClass} does not implement it - * @see #getInterfaceMethodIfPossible + * @see #getInterfaceMethodIfPossible(Method, Class) */ public static Method getMostSpecificMethod(Method method, @Nullable Class targetClass) { if (targetClass != null && targetClass != method.getDeclaringClass() && isOverridable(method, targetClass)) { @@ -1289,28 +1289,54 @@ public abstract class ClassUtils { * @param method the method to be invoked, potentially from an implementation class * @return the corresponding interface method, or the original method if none found * @since 5.1 - * @see #getMostSpecificMethod + * @deprecated in favor of {@link #getInterfaceMethodIfPossible(Method, Class)} */ + @Deprecated public static Method getInterfaceMethodIfPossible(Method method) { + return getInterfaceMethodIfPossible(method, null); + } + + /** + * Determine a corresponding interface method for the given method handle, if possible. + *

This is particularly useful for arriving at a public exported type on Jigsaw + * which can be reflectively invoked without an illegal access warning. + * @param method the method to be invoked, potentially from an implementation class + * @param targetClass the target class to check for declared interfaces + * @return the corresponding interface method, or the original method if none found + * @since 5.3.16 + * @see #getMostSpecificMethod + */ + public static Method getInterfaceMethodIfPossible(Method method, @Nullable Class targetClass) { if (!Modifier.isPublic(method.getModifiers()) || method.getDeclaringClass().isInterface()) { return method; } - return interfaceMethodCache.computeIfAbsent(method, key -> { - Class current = key.getDeclaringClass(); - while (current != null && current != Object.class) { - Class[] ifcs = current.getInterfaces(); - for (Class ifc : ifcs) { - try { - return ifc.getMethod(key.getName(), key.getParameterTypes()); - } - catch (NoSuchMethodException ex) { - // ignore - } + // Try cached version of method in its declaring class + Method result = interfaceMethodCache.computeIfAbsent(method, + key -> findInterfaceMethodIfPossible(key, key.getDeclaringClass(), Object.class)); + if (result == method && targetClass != null) { + // No interface method found yet -> try given target class (possibly a subclass of the + // declaring class, late-binding a base class method to a subclass-declared interface: + // see e.g. HashMap.HashIterator.hasNext) + result = findInterfaceMethodIfPossible(method, targetClass, method.getDeclaringClass()); + } + return result; + } + + private static Method findInterfaceMethodIfPossible(Method method, Class startClass, Class endClass) { + Class current = startClass; + while (current != null && current != endClass) { + Class[] ifcs = current.getInterfaces(); + for (Class ifc : ifcs) { + try { + return ifc.getMethod(method.getName(), method.getParameterTypes()); + } + catch (NoSuchMethodException ex) { + // ignore } - current = current.getSuperclass(); } - return key; - }); + current = current.getSuperclass(); + } + return method; } /** diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java index 2de25448b47..5e4b50187f8 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -58,8 +58,18 @@ public class ReflectiveMethodExecutor implements MethodExecutor { * @param method the method to invoke */ public ReflectiveMethodExecutor(Method method) { + this(method, null); + } + + /** + * Create a new executor for the given method. + * @param method the method to invoke + * @param targetClass the target class to invoke the method on + * @since 5.3.16 + */ + public ReflectiveMethodExecutor(Method method, @Nullable Class targetClass) { this.originalMethod = method; - this.methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(method); + this.methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(method, targetClass); if (method.isVarArgs()) { this.varargsPosition = method.getParameterCount() - 1; } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java index 451aee275d9..86889dca73e 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectiveMethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -176,7 +176,7 @@ public class ReflectiveMethodResolver implements MethodResolver { } if (matchInfo != null) { if (matchInfo.isExactMatch()) { - return new ReflectiveMethodExecutor(method); + return new ReflectiveMethodExecutor(method, type); } else if (matchInfo.isCloseMatch()) { if (this.useDistance) { @@ -204,13 +204,13 @@ public class ReflectiveMethodResolver implements MethodResolver { } } if (closeMatch != null) { - return new ReflectiveMethodExecutor(closeMatch); + return new ReflectiveMethodExecutor(closeMatch, type); } else if (matchRequiringConversion != null) { if (multipleOptions) { throw new SpelEvaluationException(SpelMessage.MULTIPLE_POSSIBLE_METHODS, name); } - return new ReflectiveMethodExecutor(matchRequiringConversion); + return new ReflectiveMethodExecutor(matchRequiringConversion, type); } else { return null; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index 5fd48cdad88..304645047db 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 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. @@ -139,7 +139,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { // The readerCache will only contain gettable properties (let's not worry about setters for now). Property property = new Property(type, method, null); TypeDescriptor typeDescriptor = new TypeDescriptor(property); - method = ClassUtils.getInterfaceMethodIfPossible(method); + method = ClassUtils.getInterfaceMethodIfPossible(method, type); this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor)); this.typeDescriptorCache.put(cacheKey, typeDescriptor); return true; @@ -182,7 +182,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { // The readerCache will only contain gettable properties (let's not worry about setters for now). Property property = new Property(type, method, null); TypeDescriptor typeDescriptor = new TypeDescriptor(property); - method = ClassUtils.getInterfaceMethodIfPossible(method); + method = ClassUtils.getInterfaceMethodIfPossible(method, type); invoker = new InvokerPair(method, typeDescriptor); this.lastReadInvokerPair = invoker; this.readerCache.put(cacheKey, invoker); @@ -242,7 +242,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { // Treat it like a property Property property = new Property(type, null, method); TypeDescriptor typeDescriptor = new TypeDescriptor(property); - method = ClassUtils.getInterfaceMethodIfPossible(method); + method = ClassUtils.getInterfaceMethodIfPossible(method, type); this.writerCache.put(cacheKey, method); this.typeDescriptorCache.put(cacheKey, typeDescriptor); return true; @@ -291,7 +291,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { if (method == null) { method = findSetterForProperty(name, type, target); if (method != null) { - method = ClassUtils.getInterfaceMethodIfPossible(method); + method = ClassUtils.getInterfaceMethodIfPossible(method, type); cachedMember = method; this.writerCache.put(cacheKey, cachedMember); } @@ -533,21 +533,21 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { if (target == null) { return this; } - Class clazz = (target instanceof Class ? (Class) target : target.getClass()); - if (clazz.isArray()) { + Class type = (target instanceof Class ? (Class) target : target.getClass()); + if (type.isArray()) { return this; } - PropertyCacheKey cacheKey = new PropertyCacheKey(clazz, name, target instanceof Class); + PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class); InvokerPair invocationTarget = this.readerCache.get(cacheKey); if (invocationTarget == null || invocationTarget.member instanceof Method) { Method method = (Method) (invocationTarget != null ? invocationTarget.member : null); if (method == null) { - method = findGetterForProperty(name, clazz, target); + method = findGetterForProperty(name, type, target); if (method != null) { TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(method, -1)); - method = ClassUtils.getInterfaceMethodIfPossible(method); + method = ClassUtils.getInterfaceMethodIfPossible(method, type); invocationTarget = new InvokerPair(method, typeDescriptor); ReflectionUtils.makeAccessible(method); this.readerCache.put(cacheKey, invocationTarget); @@ -561,7 +561,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { if (invocationTarget == null || invocationTarget.member instanceof Field) { Field field = (invocationTarget != null ? (Field) invocationTarget.member : null); if (field == null) { - field = findField(name, clazz, target instanceof Class); + field = findField(name, type, target instanceof Class); if (field != null) { invocationTarget = new InvokerPair(field, new TypeDescriptor(field)); ReflectionUtils.makeAccessible(field); @@ -600,7 +600,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { private final String property; - private boolean targetIsClass; + private final boolean targetIsClass; public PropertyCacheKey(Class clazz, String name, boolean targetIsClass) { this.clazz = clazz;