This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-5537-classloader-leak-fixes in repository https://gitbox.apache.org/repos/asf/struts.git
commit 0bb2cdb6ab4c8a0f79b49ba075a7b3a4bd962372 Author: Lukasz Lenart <[email protected]> AuthorDate: Mon Mar 23 09:59:15 2026 +0100 WW-5537 JSON plugin: add JSONCacheDestroyable for BeanInfo cache cleanup Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../org/apache/struts2/json/DefaultJSONWriter.java | 8 +++++ .../apache/struts2/json/JSONCacheDestroyable.java | 35 ++++++++++++++++++++++ plugins/json/src/main/resources/struts-plugin.xml | 2 ++ 3 files changed, 45 insertions(+) diff --git a/plugins/json/src/main/java/org/apache/struts2/json/DefaultJSONWriter.java b/plugins/json/src/main/java/org/apache/struts2/json/DefaultJSONWriter.java index f911139fc..c17c56fcd 100644 --- a/plugins/json/src/main/java/org/apache/struts2/json/DefaultJSONWriter.java +++ b/plugins/json/src/main/java/org/apache/struts2/json/DefaultJSONWriter.java @@ -76,6 +76,14 @@ public class DefaultJSONWriter implements JSONWriter { private static final ConcurrentMap<Class<?>, BeanInfo> BEAN_INFO_CACHE_IGNORE_HIERARCHY = new ConcurrentHashMap<>(); private static final ConcurrentMap<Class<?>, BeanInfo> BEAN_INFO_CACHE = new ConcurrentHashMap<>(); + /** + * Clears both BeanInfo caches to prevent classloader leaks on hot redeploy. + */ + public static void clearBeanInfoCaches() { + BEAN_INFO_CACHE_IGNORE_HIERARCHY.clear(); + BEAN_INFO_CACHE.clear(); + } + private final StringBuilder buf = new StringBuilder(); private final Deque<Object> stack = new ArrayDeque<>(); private boolean ignoreHierarchy = true; diff --git a/plugins/json/src/main/java/org/apache/struts2/json/JSONCacheDestroyable.java b/plugins/json/src/main/java/org/apache/struts2/json/JSONCacheDestroyable.java new file mode 100644 index 000000000..143d9c02b --- /dev/null +++ b/plugins/json/src/main/java/org/apache/struts2/json/JSONCacheDestroyable.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.struts2.json; + +import org.apache.struts2.dispatcher.InternalDestroyable; + +/** + * WW-5537: Clears JSON plugin's static BeanInfo caches when the Dispatcher is + * destroyed, preventing classloader leaks during hot redeployment. + * + * @since 7.1.0 + */ +public class JSONCacheDestroyable implements InternalDestroyable { + + @Override + public void destroy() { + DefaultJSONWriter.clearBeanInfoCaches(); + } +} diff --git a/plugins/json/src/main/resources/struts-plugin.xml b/plugins/json/src/main/resources/struts-plugin.xml index 1291246a7..1f73b9289 100644 --- a/plugins/json/src/main/resources/struts-plugin.xml +++ b/plugins/json/src/main/resources/struts-plugin.xml @@ -29,6 +29,8 @@ <constant name="struts.json.writer" value="struts"/> <!-- TODO: Make DefaultJSONWriter thread-safe to remove "prototype"s --> <bean class="org.apache.struts2.json.JSONUtil" scope="prototype"/> + <bean type="org.apache.struts2.dispatcher.InternalDestroyable" name="jsonCache" + class="org.apache.struts2.json.JSONCacheDestroyable"/> <package name="json-default" extends="struts-default">
