diff --git a/koupleless-base-plugin/pom.xml b/koupleless-base-plugin/pom.xml index eaace79f4..bc2768a75 100644 --- a/koupleless-base-plugin/pom.xml +++ b/koupleless-base-plugin/pom.xml @@ -63,6 +63,11 @@ ${sofa.ark.version} test + + org.springframework + spring-web + provided + diff --git a/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/BizAnnotationConfigReactiveWebServerApplicationContext.java b/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/BizAnnotationConfigReactiveWebServerApplicationContext.java new file mode 100644 index 000000000..7782f1730 --- /dev/null +++ b/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/BizAnnotationConfigReactiveWebServerApplicationContext.java @@ -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; + } + } + +} \ No newline at end of file diff --git a/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/BizAnnotationConfigServletWebServerApplicationContext.java b/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/BizAnnotationConfigServletWebServerApplicationContext.java new file mode 100644 index 000000000..8c3c20ab3 --- /dev/null +++ b/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/BizAnnotationConfigServletWebServerApplicationContext.java @@ -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; + } + } + +} \ No newline at end of file diff --git a/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/BizDefaultListableBeanFactory.java b/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/BizDefaultListableBeanFactory.java new file mode 100644 index 000000000..04c652aab --- /dev/null +++ b/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/BizDefaultListableBeanFactory.java @@ -0,0 +1,144 @@ +/* + * 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 BASE_FACTORY_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 the required type of the bean to retrieve + * @return bean + * @throws BeansException 异常 + */ + @Override + protected T doGetBean(String name, Class 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_FACTORY_REUSE_BEAN_SET.add(bean); + } + + return bean; + } + + @Override + public void destroySingletons() { + super.destroySingletons(); + //复用bean在 基座销毁时清空 + if (isBaseBeanFactory) { + BASE_FACTORY_REUSE_BEAN_SET.clear(); + } + } + + /** + * 判断是否需要销毁 + * 扩展如果是模块上下文且是基座复用的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_FACTORY_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()); + } + + /** + * Getter method for property isBase. + * + * @return property value of isBase + */ + public boolean isBaseBeanFactory() { + return isBaseBeanFactory; + } + +} \ No newline at end of file diff --git a/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/DefaultApplicationContextFactory.java b/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/DefaultApplicationContextFactory.java new file mode 100644 index 000000000..aeac4f68a --- /dev/null +++ b/koupleless-base-plugin/src/main/java/com/alipay/sofa/koupleless/plugin/context/DefaultApplicationContextFactory.java @@ -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} 来自定义子模块的销毁行为 + *

+ * 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; + } +} \ No newline at end of file diff --git a/koupleless-base-plugin/src/main/resources/META-INF/spring.factories b/koupleless-base-plugin/src/main/resources/META-INF/spring.factories index 1cda1a4c4..11a982c2b 100644 --- a/koupleless-base-plugin/src/main/resources/META-INF/spring.factories +++ b/koupleless-base-plugin/src/main/resources/META-INF/spring.factories @@ -12,4 +12,10 @@ org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ com.alipay.sofa.koupleless.plugin.spring.SkipAutoConfigurationImportFilter org.springframework.context.ApplicationContextInitializer=\ - com.alipay.sofa.koupleless.plugin.spring.BizApplicationContextInitializer \ No newline at end of file + com.alipay.sofa.koupleless.plugin.spring.BizApplicationContextInitializer + + +org.springframework.boot.ApplicationContextFactory=\ +com.alipay.sofa.koupleless.plugin.context.BizAnnotationConfigReactiveWebServerApplicationContext.Factory,\ +com.alipay.sofa.koupleless.plugin.context.BizAnnotationConfigServletWebServerApplicationContext.Factory, \ +com.alipay.sofa.koupleless.plugin.context.DefaultApplicationContextFactory \ No newline at end of file