sofastack / sofa-ark

SOFAArk is a light-weight,java based classloader isolation framework.
https://www.sofastack.tech/projects/sofa-boot/sofa-ark-readme/
Apache License 2.0
1.57k stars 500 forks source link

【Bug】静态变量ApplicationContext指向同一个对象 #672

Closed zhengchangqing closed 1 year ago

zhengchangqing commented 1 year ago

Describe the bug

一个通用工具类,定义了静态变量ApplicationContext,通过静态方法获取Spring中的实例对象。 多个应用都依赖这个工具类时,ApplicationContext是最后一个启动的应用的Spring上下文,导致获取Bean实例失败。

Expected behavior

上述场景下,ApplicationContext能够根据类加载器进行隔离。

Actual behavior

多个应用出现覆盖的情况。

Steps to reproduce

① SpringBeanUtil:

public class SpringBeanUtil implements ApplicationContextAware {
    public SpringBeanUtil() {
    }

    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringBeanUtil.SpringApplicationContextHolder.setApplicationContext(applicationContext);
    }

    public static ApplicationContext getApplicationContext() {
        return SpringBeanUtil.SpringApplicationContextHolder.applicationContext;
    }

    public static final Object getBean(String beanName) {
        return getApplicationContext().getBean(beanName);
    }

    public static final <T> T getBean(Class<T> beanType) {
        return getApplicationContext().getBean(beanType);
    }

    public static final <T> Collection<T> getBeansOfType(Class<T> beanType) {
        Map<String, T> beans = getApplicationContext().getBeansOfType(beanType);
        return (Collection)(beans != null ? beans.values() : Collections.emptyList());
    }

    private static class SpringApplicationContextHolder {
        private static ApplicationContext applicationContext = null;

        private SpringApplicationContextHolder() {
        }

        private static void setApplicationContext(ApplicationContext applicationContext) {
            SpringBeanUtil.SpringApplicationContextHolder.applicationContext = applicationContext;
        }
    }
}

② 使用方法 SpringBeanUtil.getBean(SysConfigService.class);

其中SysConfigService也是封装在一个通用JAR包中,在多个应用中都有依赖。

③ 报错信息:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.esunny.ecp.domain.base.repository.SysConfigRepository' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127) ~[spring-context-5.2.12.RELEASE.jar:5.2.12.RELEASE]

Issue Cause

SpringBeanUtil虽然是两个实例,但是ApplicationContext是静态变量,存在了堆中?导致多个应用启动时,出现了覆盖的情况出现,在调用静态方法获取Bean时,ApplicationContext指向的都是最后一个应用的上下文,所以才导致问题的产生。 Snipaste_2023-06-25_18-21-47

Environment

lylingzhen commented 1 year ago

@FlyAbner 可以尝试提一个 PR 修复呢?

lvjing2 commented 1 year ago

这是 static 变量不支持 多 ClassLoader 问题,要么放模块里,要放基座类就得改造成支持多 classLoader。可以考虑通过字节码增强技术来解决。

zhengchangqing commented 1 year ago

这是 static 变量不支持 多 ClassLoader 问题,要么放模块里,要放基座类就得改造成支持多 classLoader。可以考虑通过字节码增强技术来解决。

使用字节码增强技术,是否使用javaagent技术,只能在AppClassLoader#loadClass时进行增强?修改PUTSTATIC和GETSTATIC指令?

zhengchangqing commented 1 year ago

能否考虑类似于Tomcat的加载过程?AppClassLoader仅加载宿主应用以及拉起EmbedWebServer,然后有一个CommonClassLoader负责加载公共资源,BizClassLoader委托于CommonClassLoader进行加载,有static变量的,重新defineClass?

github-actions[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity in the last 30 days. It will be closed in the next 7 days unless it is tagged (pinned, good first issue or help wanted) or other activity occurs. Thank you for your contributions.

github-actions[bot] commented 1 year ago

This issue has been automatically closed because it has not had activity in the last 37 days. If this issue is still valid, please ping a maintainer and ask them to label it as pinned, good first issue or help wanted. Thank you for your contributions.

lvjing2 commented 1 year ago

能否考虑类似于Tomcat的加载过程?AppClassLoader仅加载宿主应用以及拉起EmbedWebServer,然后有一个CommonClassLoader负责加载公共资源,BizClassLoader委托于CommonClassLoader进行加载,有static变量的,重新defineClass?

如果是通用组件,是可以使用类 tomcat 的方式来修复。当然更快的方式是你可以再基座里创建一个相同签名的类,然后覆盖里面的实现,把这里的 static 变量,修改成key 为 classloader 的map

zhengchangqing commented 11 months ago

能否考虑类似于Tomcat的加载过程?AppClassLoader仅加载宿主应用以及拉起EmbedWebServer,然后有一个CommonClassLoader负责加载公共资源,BizClassLoader委托于CommonClassLoader进行加载,有static变量的,重新defineClass?

如果是通用组件,是可以使用类 tomcat 的方式来修复。当然更快的方式是你可以再基座里创建一个相同签名的类,然后覆盖里面的实现,把这里的 static 变量,修改成key 为 classloader 的map

可能不一定行。 因为Class是运行时加载,而且find的顺序也不确定,是否有可能还是会先加载到组件包的?

lvjing2 commented 11 months ago

我们最近正在讨论一种方案允许指定加载覆盖类,可以解决这类问题。 已经实现了一个版本, https://github.com/sofastack/sofa-serverless/issues/229#issue-1969689166