-
Notifications
You must be signed in to change notification settings - Fork 33
feat:模块复用基座单例bean时,在模块销毁时不销毁基座bean #207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| /* | ||
| * 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 com.alipay.sofa.koupleless.plugin.context; | ||
|
|
||
| import org.springframework.beans.factory.support.DefaultListableBeanFactory; | ||
| import org.springframework.boot.ApplicationContextFactory; | ||
| import org.springframework.boot.WebApplicationType; | ||
| import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; | ||
| import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; | ||
| import org.springframework.context.ConfigurableApplicationContext; | ||
| import org.springframework.core.Ordered; | ||
|
|
||
| /** | ||
| * 目的是自定义 beanFactory 的销毁行为 | ||
| * 1、基于Spring Boot ApplicationContextFactory SPI扩展 当WebApplicationType.REACTIVE创建一个上下文 和父类的区别是指定了自定义的BeanFactory | ||
| * 2、仅处理WebApplicationType.REACTIVE 当返回为null时 有其他SPI扩展去处理 即其他ApplicationContextFactory去处理 不同的WebApplicationType | ||
| * 3、指定 BizDefaultListableBeanFactory{@link com.alipay.sofa.koupleless.plugin.context.BizDefaultListableBeanFactory} 来自定义子模块的销毁行为 | ||
| * | ||
| * @author duanzhiqiang | ||
| * @version BizAnnotationConfigReactiveWebServerApplicationContext.java, v 0.1 2024年11月08日 16:23 duanzhiqiang | ||
| */ | ||
| public class BizAnnotationConfigReactiveWebServerApplicationContext extends | ||
| AnnotationConfigReactiveWebServerApplicationContext { | ||
| /** | ||
| * 构造器 | ||
| * | ||
| * @param beanFactory 指定的beanFactory | ||
| */ | ||
| public BizAnnotationConfigReactiveWebServerApplicationContext(DefaultListableBeanFactory beanFactory) { | ||
| super(beanFactory); | ||
| } | ||
|
|
||
| /** | ||
| * 替换上下文创建行为利用 spring boot factories 扩展 并在默认的优先级前 | ||
| * {@link ApplicationContextFactory} registered in {@code spring.factories} to support | ||
| * {@link AnnotationConfigServletWebServerApplicationContext}. | ||
| */ | ||
| static class Factory implements ApplicationContextFactory, Ordered { | ||
|
|
||
| @Override | ||
| public ConfigurableApplicationContext create(WebApplicationType webApplicationType) { | ||
| return (webApplicationType != WebApplicationType.REACTIVE) ? null : createContext(); | ||
| } | ||
|
|
||
| /** | ||
| * 创建上下文 | ||
| * | ||
| * @return 上下文 | ||
| */ | ||
| private ConfigurableApplicationContext createContext() { | ||
| //自定义BeanFactory的销毁 | ||
| BizDefaultListableBeanFactory beanFactory = new BizDefaultListableBeanFactory(); | ||
| return new BizAnnotationConfigReactiveWebServerApplicationContext(beanFactory); | ||
| } | ||
|
|
||
| @Override | ||
| public int getOrder() { | ||
| return Ordered.HIGHEST_PRECEDENCE; | ||
| } | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| /* | ||
| * 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 com.alipay.sofa.koupleless.plugin.context; | ||
|
|
||
| import org.springframework.beans.factory.support.DefaultListableBeanFactory; | ||
| import org.springframework.boot.ApplicationContextFactory; | ||
| import org.springframework.boot.WebApplicationType; | ||
| import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; | ||
| import org.springframework.context.ConfigurableApplicationContext; | ||
| import org.springframework.core.Ordered; | ||
|
|
||
| /** | ||
| * 目的是自定义 beanFactory 的销毁行为 | ||
| * 1、基于Spring Boot ApplicationContextFactory SPI扩展 当WebApplicationType.SERVLET创建一个上下文 和父类的区别是指定了自定义的BeanFactory | ||
| * 2、仅处理WebApplicationType.SERVLET 当返回为null时 有其他SPI扩展去处理 即其他ApplicationContextFactory去处理 不同的WebApplicationType | ||
| * 3、指定 BizDefaultListableBeanFactory{@link com.alipay.sofa.koupleless.plugin.context.BizDefaultListableBeanFactory} 来自定义子模块的销毁行为 | ||
| * | ||
| * @author duanzhiqiang | ||
| * @version BizAnnotationConfigServletWebServerApplicationContext.java, v 0.1 2024年11月08日 16:23 duanzhiqiang | ||
| */ | ||
| public class BizAnnotationConfigServletWebServerApplicationContext extends | ||
| AnnotationConfigServletWebServerApplicationContext { | ||
| /** | ||
| * 构造器 并使用自定义的 beanFactory | ||
| * | ||
| * @param beanFactory 指定的beanFactory | ||
| */ | ||
| public BizAnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) { | ||
| super(beanFactory); | ||
| } | ||
|
|
||
| /** | ||
| * 替换上下文创建行为利用 spring boot factories 扩展 并在默认的优先级前 | ||
| * {@link ApplicationContextFactory} registered in {@code spring.factories} to support | ||
| * {@link AnnotationConfigServletWebServerApplicationContext}. | ||
| */ | ||
| static class Factory implements ApplicationContextFactory, Ordered { | ||
|
|
||
| @Override | ||
| public ConfigurableApplicationContext create(WebApplicationType webApplicationType) { | ||
| return (webApplicationType != WebApplicationType.SERVLET) ? null : createContext(); | ||
| } | ||
|
|
||
| /** | ||
| * 创建上下文 | ||
| * | ||
| * @return 上下文 | ||
| */ | ||
| private ConfigurableApplicationContext createContext() { | ||
| //自定义BeanFactory的销毁 | ||
| BizDefaultListableBeanFactory beanFactory = new BizDefaultListableBeanFactory(); | ||
| return new BizAnnotationConfigServletWebServerApplicationContext(beanFactory); | ||
| } | ||
|
|
||
| @Override | ||
| public int getOrder() { | ||
| return HIGHEST_PRECEDENCE; | ||
| } | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| /* | ||
| * 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 com.alipay.sofa.koupleless.plugin.context; | ||
|
|
||
| import org.springframework.beans.BeansException; | ||
| import org.springframework.beans.factory.support.DefaultListableBeanFactory; | ||
| import org.springframework.beans.factory.support.RootBeanDefinition; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.Set; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| /** | ||
| * 1、当模块获取基座单例时bean 记录引用(由于无法通过名称获取bean 因为在子模块中 beanName是可以重新定义的) | ||
| * 2、在模块创建这个复用的bean时 不注册销毁行为 支持复用bean 注册单例或者是其他scope | ||
| * 3、通过记录的复用的bean 引用来判断是基座复用bean | ||
| * | ||
| * @author duanzhiqiang | ||
| * @version BizDefaultListableBeanFactory.java, v 0.1 2024年11月08日 16:45 duanzhiqiang | ||
| */ | ||
| public class BizDefaultListableBeanFactory extends DefaultListableBeanFactory { | ||
|
|
||
| /** | ||
| * 是否是基座beanFactory | ||
| */ | ||
| private final boolean isBaseBeanFactory; | ||
|
|
||
| /** | ||
| * 在创建时 额外判断是否是基座bean | ||
| */ | ||
| public BizDefaultListableBeanFactory() { | ||
| super(); | ||
| this.isBaseBeanFactory = !isOnBiz(); | ||
| } | ||
|
|
||
| /** | ||
| * 基座bean复用bean集合引用 | ||
| */ | ||
| private static final Set<Object> BASE_FACTORTY_REUSE_BEAN_SET = Collections.newSetFromMap(new ConcurrentHashMap<>()); | ||
|
|
||
| /** | ||
| * 模块的类加载器名 | ||
| */ | ||
| private static final String BIZ_CLASSLOADER = "com.alipay.sofa.ark.container.service.classloader.BizClassLoader"; | ||
|
|
||
| /** | ||
| * 在子模块获取 base 的bean 记录这个bean 引用 | ||
| * | ||
| * @param name the name of the bean to retrieve | ||
| * @param requiredType the required type of the bean to retrieve | ||
| * @param args arguments to use when creating a bean instance using explicit arguments | ||
| * (only applied when creating a new instance as opposed to retrieving an existing one) | ||
| * @param typeCheckOnly whether the instance is obtained for a type check, | ||
| * not for actual use | ||
| * @param <T> the required type of the bean to retrieve | ||
| * @return bean | ||
| * @throws BeansException 异常 | ||
| */ | ||
| @Override | ||
| protected <T> T doGetBean(String name, Class<T> requiredType, Object[] args, | ||
| boolean typeCheckOnly) throws BeansException { | ||
|
|
||
| T bean = super.doGetBean(name, requiredType, args, typeCheckOnly); | ||
|
|
||
| // 只有是基座isBaseBeanFactory 但获取bean时是模块发起调用(即复用基座的bean时) 记录下复用的基座bean | ||
| if (isBaseBeanFactory && isOnBiz() && isSingleton(name)) { | ||
| BASE_FACTORTY_REUSE_BEAN_SET.add(bean); | ||
| } | ||
|
|
||
| return bean; | ||
| } | ||
|
Comment on lines
+74
to
+85
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential memory leak due to static The static Consider implementing a mechanism to remove bean references from Apply this diff to illustrate the addition of a removal mechanism: // In your `requiresDestruction` method or appropriate lifecycle method
if (!isBaseBeanFactory && isBaseReuseBean(bean)) {
// Do not register DisposableBean
return false;
+ // Remove the bean from the reuse set to prevent memory leaks
+ BASE_FACTORY_REUSE_BEAN_SET.remove(bean);
}Ensure that this removal aligns with the desired bean lifecycle management.
|
||
|
|
||
| /** | ||
| * 判断是否需要销毁 | ||
| * 扩展如果是模块上下文且是基座复用的bean 则不需要进行销毁 | ||
| * | ||
| * @param bean the bean instance to check | ||
| * @param mbd the corresponding bean definition | ||
| * @return false 不需要销毁 true 需要销毁 | ||
| */ | ||
| @Override | ||
| protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) { | ||
| //如果是模块上下文且是基座复用的bean 则不需要进行销毁 | ||
| if (!isBaseBeanFactory && isBaseReuseBean(bean)) { | ||
| //不注册DisposableBean | ||
| return false; | ||
| } | ||
| return super.requiresDestruction(bean, mbd); | ||
| } | ||
|
|
||
| /** | ||
| * 是否是基座复用的bean | ||
| * | ||
| * @param bean bean 实例 | ||
| * @return true 是 false 不是 | ||
| */ | ||
| private boolean isBaseReuseBean(Object bean) { | ||
| return BASE_FACTORTY_REUSE_BEAN_SET.contains(bean); | ||
| } | ||
|
|
||
| /** | ||
| * 判断是否在biz中 而不是基座中 | ||
| * | ||
| * @return true 在biz中 | ||
| */ | ||
| private boolean isOnBiz() { | ||
| ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); | ||
| return contextClassLoader == null | ||
| || BIZ_CLASSLOADER.equals(contextClassLoader.getClass().getName()); | ||
| } | ||
|
Comment on lines
+150
to
+133
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential logical error in The Consider revising the condition: return contextClassLoader != null
&& BIZ_CLASSLOADER.equals(contextClassLoader.getClass().getName());This change ensures that |
||
|
|
||
| /** | ||
| * Getter method for property <tt>isBase</tt>. | ||
| * | ||
| * @return property value of isBase | ||
| */ | ||
| public boolean isBaseBeanFactory() { | ||
| return isBaseBeanFactory; | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /* | ||
| * 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 com.alipay.sofa.koupleless.plugin.context; | ||
|
|
||
| import org.springframework.boot.ApplicationContextFactory; | ||
| import org.springframework.boot.WebApplicationType; | ||
| import org.springframework.context.ConfigurableApplicationContext; | ||
| import org.springframework.context.annotation.AnnotationConfigApplicationContext; | ||
| import org.springframework.core.Ordered; | ||
|
|
||
| /** | ||
| * WebApplicationType.NONE 自定义上下文 | ||
| * 1、基于Spring Boot ApplicationContextFactory SPI扩展 当WebApplicationType.NONE创建一个上下文 | ||
| * 2、仅处理WebApplicationType.NONE 当返回为null时 有其他SPI扩展去处理 即其他ApplicationContextFactory去处理 不同的WebApplicationType | ||
| * 3、指定 BizDefaultListableBeanFactory{@link com.alipay.sofa.koupleless.plugin.context.BizDefaultListableBeanFactory} 来自定义子模块的销毁行为 | ||
| * <p> | ||
| * Custom ApplicationContextFactory for WebApplicationType.NONE that uses BizDefaultListableBeanFactory | ||
| * to prevent destruction of base singleton beans when a module is destroyed. | ||
| * This factory has the highest precedence and only handles non-web application contexts. | ||
| * | ||
| * @author duanzhiqiang | ||
| * @version DefaultApplicationContextFactory.java, v 0.1 2024年11月12日 17:55 duanzhiqiang | ||
| */ | ||
| public class DefaultApplicationContextFactory implements ApplicationContextFactory, Ordered { | ||
| @Override | ||
| public ConfigurableApplicationContext create(WebApplicationType webApplicationType) { | ||
| return (webApplicationType != WebApplicationType.NONE) ? null | ||
| : new AnnotationConfigApplicationContext(new BizDefaultListableBeanFactory()); | ||
| } | ||
|
|
||
| @Override | ||
| public int getOrder() { | ||
| return Ordered.HIGHEST_PRECEDENCE; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in variable name
BASE_FACTORTY_REUSE_BEAN_SETThe variable name
BASE_FACTORTY_REUSE_BEAN_SETappears to have a typo. It should likely beBASE_FACTORY_REUSE_BEAN_SETto accurately represent its purpose.Apply this diff to correct the typo:
Ensure that all references to this variable are updated accordingly.
📝 Committable suggestion