diff --git a/.github/workflows/module_controller_ci_build_batch_deploy_to_aliyun.yml b/.github/workflows/module_controller_ci_build_batch_deploy_to_aliyun.yml index 2fb63e0ae..74f6e681b 100644 --- a/.github/workflows/module_controller_ci_build_batch_deploy_to_aliyun.yml +++ b/.github/workflows/module_controller_ci_build_batch_deploy_to_aliyun.yml @@ -185,7 +185,7 @@ jobs: # 使用 kubectl get 命令获取资源对象的详细信息,并提取自定义字段的值 field_value=$(kubectl get $moduledeploymentname -o custom-columns=PAUSE:.spec.pause --no-headers) - if [ "$field_value" == "$desired_field_value" ]; then + if [ "$field_value" == "0" ]; then echo "字段值已满足条件,执行分组确认" kubectl patch $moduledeploymentname -p '{"spec":{"pause":false}}' --type=merge exit 0 @@ -371,7 +371,7 @@ jobs: # 使用 kubectl get 命令获取资源对象的详细信息,并提取自定义字段的值 field_value=$(kubectl get $moduledeploymentname -o custom-columns=PAUSE:.spec.pause --no-headers) - if [ "$field_value" == "$desired_field_value" ]; then + if [ "$field_value" == "0" ]; then echo "字段值已满足条件,执行分组确认" kubectl patch $moduledeploymentname -p '{"spec":{"pause":false}}' --type=merge exit 0 @@ -442,7 +442,7 @@ jobs: echo "缩容失败" exit 1 fi - + - name: batch scaleup run: | kubectl apply -f config/samples/ci/module-deployment_v1alpha1_moduledeployment_batch_provider.yaml @@ -514,7 +514,7 @@ jobs: # 使用 kubectl get 命令获取资源对象的详细信息,并提取自定义字段的值 field_value=$(kubectl get $moduledeploymentname -o custom-columns=PAUSE:.spec.pause --no-headers) - if [ "$field_value" == "$desired_field_value" ]; then + if [ "$field_value" == "0" ]; then echo "字段值已满足条件,执行分组确认" kubectl patch $moduledeploymentname -p '{"spec":{"pause":false}}' --type=merge exit 0 @@ -699,4 +699,4 @@ jobs: - name: delete deployment run: | - kubectl delete -n default deployment dynamic-stock-deployment \ No newline at end of file + kubectl delete -n default deployment dynamic-stock-deployment diff --git a/.github/workflows/module_controller_ci_build_batch_scaleup_then_scaledown_deploy_to_aliyun.yml b/.github/workflows/module_controller_ci_build_batch_scaleup_then_scaledown_deploy_to_aliyun.yml index c8cc1b711..de6ecd9fa 100644 --- a/.github/workflows/module_controller_ci_build_batch_scaleup_then_scaledown_deploy_to_aliyun.yml +++ b/.github/workflows/module_controller_ci_build_batch_scaleup_then_scaledown_deploy_to_aliyun.yml @@ -185,7 +185,7 @@ jobs: # 使用 kubectl get 命令获取资源对象的详细信息,并提取自定义字段的值 field_value=$(kubectl get $moduledeploymentname -o custom-columns=PAUSE:.spec.pause --no-headers) - if [ "$field_value" == "$desired_field_value" ]; then + if [ "$field_value" == "0" ]; then echo "字段值已满足条件,执行分组确认" kubectl patch $moduledeploymentname -p '{"spec":{"pause":false}}' --type=merge exit 0 @@ -597,7 +597,7 @@ jobs: # 使用 kubectl get 命令获取资源对象的详细信息,并提取自定义字段的值 field_value=$(kubectl get $moduledeploymentname -o custom-columns=PAUSE:.spec.pause --no-headers) - if [ "$field_value" == "$desired_field_value" ]; then + if [ "$field_value" == "0" ]; then echo "字段值已满足条件,执行分组确认" kubectl patch $moduledeploymentname -p '{"spec":{"pause":false}}' --type=merge exit 0 @@ -756,7 +756,7 @@ jobs: # 使用 kubectl get 命令获取资源对象的详细信息,并提取自定义字段的值 field_value=$(kubectl get $moduledeploymentname -o custom-columns=PAUSE:.spec.pause --no-headers) - if [ "$field_value" == "$desired_field_value" ]; then + if [ "$field_value" == "0" ]; then echo "字段值已满足条件,执行分组确认" kubectl patch $moduledeploymentname -p '{"spec":{"pause":false}}' --type=merge exit 0 diff --git a/.github/workflows/module_controller_ci_build_batch_symmetric_deploy_to_aliyun.yml b/.github/workflows/module_controller_ci_build_batch_symmetric_deploy_to_aliyun.yml index 3a831315f..af3f184f3 100644 --- a/.github/workflows/module_controller_ci_build_batch_symmetric_deploy_to_aliyun.yml +++ b/.github/workflows/module_controller_ci_build_batch_symmetric_deploy_to_aliyun.yml @@ -167,7 +167,6 @@ jobs: run: | # 定义要等待的资源名称和字段值 moduledeploymentname=$(kubectl get moduledeployment -o name) - desired_field_value=true # 定义等待的超时时间(以秒为单位) timeout_seconds=300 @@ -183,11 +182,11 @@ jobs: fi # 使用 kubectl get 命令获取资源对象的详细信息,并提取自定义字段的值 - field_value=$(kubectl get $moduledeploymentname -o custom-columns=PAUSE:.spec.pause --no-headers) + field_value=$(kubectl get $moduledeploymentname -o custom-columns=CONFIRMBATCHNUM:.spec.confirmBatchNum --no-headers) - if [ "$field_value" == "$desired_field_value" ]; then + if [ "$field_value" == "0" ]; then echo "字段值已满足条件,执行分组确认" - kubectl patch $moduledeploymentname -p '{"spec":{"pause":false}}' --type=merge + kubectl patch $moduledeploymentname -p '{"spec":{"confirmBatchNum":1}}' --type=merge exit 0 else echo "等待字段值满足条件..." @@ -213,7 +212,6 @@ jobs: run: | # 定义要等待的资源名称和字段值 moduledeploymentname=$(kubectl get moduledeployment -o name) - desired_field_value=true # 定义等待的超时时间(以秒为单位) timeout_seconds=300 @@ -229,11 +227,11 @@ jobs: fi # 使用 kubectl get 命令获取资源对象的详细信息,并提取自定义字段的值 - field_value=$(kubectl get $moduledeploymentname -o custom-columns=PAUSE:.spec.pause --no-headers) + field_value=$(kubectl get $moduledeploymentname -o custom-columns=CONFIRMBATCHNUM:.spec.confirmBatchNum --no-headers) - if [ "$field_value" == "$desired_field_value" ]; then + if [ "$field_value" == "1" ]; then echo "字段值已满足条件,执行分组确认" - kubectl patch $moduledeploymentname -p '{"spec":{"pause":false}}' --type=merge + kubectl patch $moduledeploymentname -p '{"spec":{"confirmBatchNum":2}}' --type=merge exit 0 else echo "等待字段值满足条件..." diff --git a/.github/workflows/serverless_runtime_snapshot.yml b/.github/workflows/serverless_runtime_snapshot.yml new file mode 100644 index 000000000..fa17baba3 --- /dev/null +++ b/.github/workflows/serverless_runtime_snapshot.yml @@ -0,0 +1,81 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: SOFA Serverless Runtime Snapshot + +## https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release +## trigger manually +on: + workflow_dispatch: + +jobs: + build_and_test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker + uses: docker/metadata-action@v3 + with: + images: | + node:14 + + - name: Test Env prepare + run: | + sudo apt-get update >>/tmp/envprepare.out + sudo apt-get install -y expect >>/tmp/envprepare.out + docker pull mongo:7.0.2-jammy + docker run --name mongodb -d -p 27017:27017 -v /home/runner/work/data:/data/db mongo:7.0.2-jammy + docker pull zookeeper:3.9.0 + docker run -p 2181:2181 -it --name zookeeper --restart always -d zookeeper:3.9.0 + sudo apt-get install redis-server -y + sudo systemctl start redis-server + sudo cp .github/workflows/ccbin/arkctl /usr/bin/arkctl + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + + - name: Build with Maven + working-directory: sofa-serverless-runtime + run: mvn clean install -DskipTests -B -U -e + + - name: get sofa-runtime version & update sofa-runtime version for all test sample app + run: | + serverless_runtime_version=$(find sofa-serverless-runtime/sofa-serverless-common -name "*.jar" |grep -v source |sed "s/.*sofa-serverless-common-\(.*\).jar/\1/") + echo "升级sofa-runtime version to $serverless_runtime_version" + for testsample in `find samples -name "*[3]-samples"`;do sed -i "s/.*<\/sofa.serverless.runtime.version>/$serverless_runtime_version<\/sofa.serverless.runtime.version>/g" $testsample/pom.xml ;done + for testsample in `find samples -name "*[3]-samples"`;do if cat $testsample/pom.xml |grep ""|grep $serverless_runtime_version;then echo "版本校验通过";else echo "版本校验失败";exit 1;fi;done + + - name: Run jdk17 serverless runtime test + run: | + set -e + bash .github/workflows/ccbin/start.sh jdk17 + + snapshot_for_jdk17: + needs: build_and_test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import + gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase + - name: Build with Maven + run: mvn --batch-mode deploy -DskipTests -Psnapshot + working-directory: sofa-serverless-runtime + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} diff --git a/README.md b/README.md index f52c822de..9cc22cb73 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ ![license](https://img.shields.io/badge/license-Apache--2.0-green.svg) ![Maven Central](https://img.shields.io/maven-central/v/com.alipay.sofa.serverless/sofa-serverless-runtime) -完整产品介绍欢迎查阅 [SOFAServerless 官网](https://sofaserverless.gitee.io/home/) +SOFAServerless 品牌全新升级为 Koupleless,欢迎访问全新 GitHub 地址:https://github.com/koupleless/koupleless +完整产品介绍欢迎查阅 [Koupleless 全新官网](https://koupleless.gitee.io/home/)
diff --git a/arkctl/common/fileutil/file_util.go b/arkctl/common/fileutil/file_util.go index 953ba9a2b..5ac69c1ff 100644 --- a/arkctl/common/fileutil/file_util.go +++ b/arkctl/common/fileutil/file_util.go @@ -17,6 +17,7 @@ package fileutil import ( "context" "fmt" + "serverless.alipay.com/sofa-serverless/arkctl/common/osutil" "strings" ) @@ -25,8 +26,7 @@ type FileUrl string func (url FileUrl) GetFileUrlType() FileUrlType { switch { - // start with file:// then it's a local file - case strings.HasPrefix(string(url), "file://"): + case strings.HasPrefix(string(url), osutil.GetLocalFileProtocol()): return FileUrlTypeLocal default: diff --git a/arkctl/common/osutil/os_util.go b/arkctl/common/osutil/os_util.go new file mode 100644 index 000000000..4574866de --- /dev/null +++ b/arkctl/common/osutil/os_util.go @@ -0,0 +1,32 @@ +/** + * 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 osutil + +import ( + "runtime" + "strings" +) + +func IsWindows() bool { + return strings.HasPrefix(strings.ToLower(runtime.GOOS), "win") +} + +func GetLocalFileProtocol() string { + if IsWindows() { + return "file:///" + } else { + return "file://" + } +} diff --git a/arkctl/v1/cmd/deploy/deploy.go b/arkctl/v1/cmd/deploy/deploy.go index 8640fba38..5d0c020fb 100644 --- a/arkctl/v1/cmd/deploy/deploy.go +++ b/arkctl/v1/cmd/deploy/deploy.go @@ -23,6 +23,7 @@ import ( "fmt" "os" "path/filepath" + "serverless.alipay.com/sofa-serverless/arkctl/common/osutil" "strings" "serverless.alipay.com/sofa-serverless/arkctl/common/cmdutil" @@ -146,7 +147,7 @@ func execMavenBuild(ctx *contextutil.Context) bool { func execParseBizModel(ctx *contextutil.Context) bool { style.InfoPrefix("Stage").Println("ParseBizModel") - bundlePath := "file://" + defaultArg + bundlePath := osutil.GetLocalFileProtocol() + defaultArg if doBuild { searchdir := defaultArg if subBundlePath != "" { @@ -164,7 +165,7 @@ func execParseBizModel(ctx *contextutil.Context) bool { pterm.Error.Println("can not find pre built biz bundle in build dir!") return false } - bundlePath = "file://" + bundlePath + bundlePath = osutil.GetLocalFileProtocol() + bundlePath } bizModel, err := ark.ParseBizModel(ctx, fileutil.FileUrl(bundlePath)) @@ -289,7 +290,7 @@ func execInstallInKubePod(ctx *contextutil.Context) bool { string(runtime.Must(json.Marshal(ark.BizModel{ BizName: bizModel.BizName, BizVersion: bizModel.BizVersion, - BizUrl: fileutil.FileUrl("file://" + ctx.Value(ctxKeyArkBizBundlePathInSidePod).(string)), + BizUrl: fileutil.FileUrl(osutil.GetLocalFileProtocol() + ctx.Value(ctxKeyArkBizBundlePathInSidePod).(string)), }))), fmt.Sprintf("http://127.0.0.1:%v/installBiz", portFlag), ) diff --git a/arkctl/v1/service/ark/biz_util.go b/arkctl/v1/service/ark/biz_util.go index 60d727d4d..b2c3c2537 100644 --- a/arkctl/v1/service/ark/biz_util.go +++ b/arkctl/v1/service/ark/biz_util.go @@ -18,6 +18,7 @@ import ( "archive/zip" "context" "fmt" + "serverless.alipay.com/sofa-serverless/arkctl/common/osutil" "strings" "serverless.alipay.com/sofa-serverless/arkctl/common/fileutil" @@ -37,8 +38,7 @@ func parseJarBizModel(ctx context.Context, bizUrl fileutil.FileUrl) (*BizModel, return nil, err } - // remove file:// prefix - zipReader, err := zip.OpenReader(localPath[7:]) + zipReader, err := zip.OpenReader(localPath[len(osutil.GetLocalFileProtocol()):]) if err != nil { return nil, err } diff --git a/arkctl/v1/service/ark/biz_util_test.go b/arkctl/v1/service/ark/biz_util_test.go index a93187bd2..a3c3e05d4 100644 --- a/arkctl/v1/service/ark/biz_util_test.go +++ b/arkctl/v1/service/ark/biz_util_test.go @@ -21,6 +21,7 @@ import ( "io" "os" "path/filepath" + "serverless.alipay.com/sofa-serverless/arkctl/common/osutil" "strings" "testing" @@ -74,5 +75,5 @@ func TestParseBizModel_LocalJar(t *testing.T) { assert.Equal(t, model.BizName, "testName") assert.Equal(t, model.BizVersion, "version") - assert.Equal(t, model.BizUrl, fileutil.FileUrl("file://"+zipFilePath)) + assert.Equal(t, model.BizUrl, fileutil.FileUrl(osutil.GetLocalFileProtocol()+zipFilePath)) } diff --git a/arkctl/v1/service/ark/service_test.go b/arkctl/v1/service/ark/service_test.go index 041242fae..09e4b0c93 100644 --- a/arkctl/v1/service/ark/service_test.go +++ b/arkctl/v1/service/ark/service_test.go @@ -19,6 +19,8 @@ import ( "encoding/json" "net" "net/http" + "serverless.alipay.com/sofa-serverless/arkctl/common/fileutil" + "serverless.alipay.com/sofa-serverless/arkctl/common/osutil" "testing" "github.com/sirupsen/logrus" @@ -123,7 +125,7 @@ func TestInstallBiz_NoServer(t *testing.T) { BizModel: BizModel{ BizName: "biz", BizVersion: "0.0.1-SNAPSHOT", - BizUrl: "file:///foobar", + BizUrl: fileutil.FileUrl(osutil.GetLocalFileProtocol() + "/foobar"), }, TargetContainer: ArkContainerRuntimeInfo{ RunType: ArkContainerRunTypeLocal, diff --git a/docs/content/zh-cn/docs/contribution-guidelines/runtime/dubbo2.7.md b/docs/content/zh-cn/docs/contribution-guidelines/runtime/dubbo2.7.md new file mode 100644 index 000000000..f9cd83238 --- /dev/null +++ b/docs/content/zh-cn/docs/contribution-guidelines/runtime/dubbo2.7.md @@ -0,0 +1,194 @@ +--- +title: dubbo2.7 的多模块化适配 +date: 2024-1-19T19:55:35+08:00 +weight: 1 +--- + +## 为什么需要做适配 +原生 dubbo2.7 在多模块场景下,无法支持模块发布自己的dubbo服务,调用时存在序列化、类加载异常等一系列问题。 + +## 多模块适配方案 + +dubbo2.7多模块适配SDK +```xml + + com.alipay.sofa.serverless + sofa-serverless-adapter-dubbo2.7 + 0.5.7-SNAPSHOT + +``` + +主要从类加载、服务发布、服务卸载、服务隔离、模块维度服务管理、配置管理、序列化等方面进行适配。 + +### 1. AnnotatedBeanDefinitionRegistryUtils使用基座classloader无法加载模块类 +com.alibaba.spring.util.AnnotatedBeanDefinitionRegistryUtils#isPresentBean + +```java +public static boolean isPresentBean(BeanDefinitionRegistry registry, Class annotatedClass) { + ... + + // ClassLoader classLoader = annotatedClass.getClassLoader(); // 原生逻辑 + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // 改为使用tccl加载类 + + for (String beanName : beanNames) { + BeanDefinition beanDefinition = registry.getBeanDefinition(beanName); + if (beanDefinition instanceof AnnotatedBeanDefinition) { + ... + String className = annotationMetadata.getClassName(); + Class targetClass = resolveClassName(className, classLoader); + ... + } + } + + return present; +} +``` + +### 2. 模块维度的服务、配置资源管理 +1. com.alipay.sofa.serverless.support.dubbo.ServerlessServiceRepository 替代原生 org.apache.dubbo.rpc.model.ServiceRepository + +原生service采用interfaceName作为缓存,在基座、模块发布同样interface,不同group服务时,无法区分,替代原生service缓存模型,采用Interface Class类型作为key,同时采用包含有group的path作为key,支持基座、模块发布同interface不同group的场景 +```java +private static ConcurrentMap, ServiceDescriptor> globalClassServices = new ConcurrentHashMap<>(); + +private static ConcurrentMap globalPathServices = new ConcurrentHashMap<>(); +``` + +2. com.alipay.sofa.serverless.support.dubbo.ServerlessConfigManager 替代原生 org.apache.dubbo.config.context.ConfigManager + + 为原生config添加classloader维度的key,不同模块根据classloader隔离不同的配置 + +```java +final Map>> globalConfigsCache = new HashMap<>(); + +public void addConfig(AbstractConfig config, boolean unique) { + ... + write(() -> { + Map configsMap = getCurrentConfigsCache().computeIfAbsent(getTagName(config.getClass()), type -> newMap()); + addIfAbsent(config, configsMap, unique); + }); +} +private Map> getCurrentConfigsCache() { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); // 根据当前线程classloader隔离不同配置缓存 + globalConfigsCache.computeIfAbsent(contextClassLoader, k -> new HashMap<>()); + return globalConfigsCache.get(contextClassLoader); +} +``` + +ServerlessServiceRepository 和 ServerlessConfigManager 都依赖 dubbo ExtensionLoader 的扩展机制,从而替代原生逻辑,具体原理可参考 org.apache.dubbo.common.extension.ExtensionLoader.createExtension + +### 3. 模块维度服务发布、服务卸载 +override DubboBootstrapApplicationListener 禁止原生dubbo模块启动、卸载时发布、卸载服务 + +- com.alipay.sofa.serverless.support.dubbo.BizDubboBootstrapListener + +原生dubbo2.7只在基座启动完成后发布dubbo服务,在多模块时,无法支持模块的服务发布,Ark采用监听器监听模块启动事件,并手动调用dubbo进行模块维度的服务发布 + +```java +private void onContextRefreshedEvent(ContextRefreshedEvent event) { + try { + ReflectionUtils.getMethod(DubboBootstrap.class, "exportServices") + .invoke(dubboBootstrap); + ReflectionUtils.getMethod(DubboBootstrap.class, "referServices").invoke(dubboBootstrap); + } catch (Exception e) { + + } +} +``` + +原生dubbo2.7在模块卸载时会调用DubboShutdownHook,将JVM中所有dubbo service unexport,导致模块卸载后基座、其余模块服务均被卸载,Ark采用监听器监听模块spring上下文关闭事件,手动卸载当前模块的dubbo服务,保留基座、其余模块的dubbo服务 + +```java +private void onContextClosedEvent(ContextClosedEvent event) { + // DubboBootstrap.unexportServices 会 unexport 所有服务,只需要 unexport 当前 biz 的服务即可 + Map> exportedServices = ReflectionUtils.getField(dubboBootstrap, DubboBootstrap.class, "exportedServices"); + + Set bizUnexportServices = new HashSet<>(); + for (Map.Entry> entry : exportedServices.entrySet()) { + String serviceKey = entry.getKey(); + ServiceConfigBase sc = entry.getValue(); + if (sc.getRef().getClass().getClassLoader() == Thread.currentThread().getContextClassLoader()) { // 根据ref服务实现的类加载器区分模块服务 + bizUnexportServices.add(serviceKey); + configManager.removeConfig(sc); // 从configManager配置管理中移除服务配置 + sc.unexport(); // 进行服务unexport + serviceRepository.unregisterService(sc.getUniqueServiceName()); // 从serviceRepository服务管理中移除配置 + } + } + for (String service : bizUnexportServices) { + exportedServices.remove(service); // 从DubboBootstrap中移除该service + } + } +``` + +### 4. 服务路由 +- com.alipay.sofa.serverless.support.dubbo.ConsumerRedefinePathFilter + +dubbo服务调用时通过path从ServiceRepository中获取正确的服务端服务模型(包括interface、param、return类型等)进行服务调用、参数、返回值的序列化,原生dubbo2.7采用interfaceName作为path查找service model,无法支持多模块下基座模块发布同interface的场景,Ark自定义consumer端filter添加group信息到path中,以便provider端进行正确的服务路由 + +```java +public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + if (invocation instanceof RpcInvocation) { + RpcInvocation rpcInvocation = (RpcInvocation) invocation; + // 原生path为interfaceName,如com.alipay.sofa.rpc.dubbo27.model.DemoService + // 修改后path为serviceUniqueName,如masterBiz/com.alipay.sofa.rpc.dubbo27.model.DemoService + rpcInvocation.setAttachment("interface", rpcInvocation.getTargetServiceUniqueName()); // 原生path为interfaceName,如 + } + return invoker.invoke(invocation); +} +``` + +### 5. 序列化 +- org.apache.dubbo.common.serialize.java.JavaSerialization +- org.apache.dubbo.common.serialize.java.ClassLoaderJavaObjectInput +- org.apache.dubbo.common.serialize.java.ClassLoaderObjectInputStream + +在获取序列化工具JavaSerialization时,使用ClassLoaderJavaObjectInput替代原生JavaObjectInput,传递provider端service classloader信息 + +```java +// org.apache.dubbo.common.serialize.java.JavaSerialization +public ObjectInput deserialize(URL url, InputStream is) throws IOException { + return new ClassLoaderJavaObjectInput(new ClassLoaderObjectInputStream(null, is)); // 使用ClassLoaderJavaObjectInput替代原生JavaObjectInput,传递provider端service classloader信息 +} + +// org.apache.dubbo.common.serialize.java.ClassLoaderObjectInputStream +private ClassLoader classLoader; + +public ClassLoaderObjectInputStream(final ClassLoader classLoader, final InputStream inputStream) { + super(inputStream); + this.classLoader = classLoader; +} +``` + +- org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation 服务端反序列化参数 + +```java +// patch begin +if (in instanceof ClassLoaderJavaObjectInput) { + InputStream is = ((ClassLoaderJavaObjectInput) in).getInputStream(); + if (is instanceof ClassLoaderObjectInputStream) { + ClassLoader cl = serviceDescriptor.getServiceInterfaceClass().getClassLoader(); // 设置provider端service classloader信息到ClassLoaderObjectInputStream中 + ((ClassLoaderObjectInputStream) is).setClassLoader(cl); + } +} +// patch end +``` +- org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult 客户端反序列化返回值 + +```java +// patch begin +if (in instanceof ClassLoaderJavaObjectInput) { + InputStream is = ((ClassLoaderJavaObjectInput) in).getInputStream(); + if (is instanceof ClassLoaderObjectInputStream) { + ClassLoader cl = invocation.getInvoker().getInterface().getClassLoader(); // 设置consumer端service classloader信息到ClassLoaderObjectInputStream中 + ((ClassLoaderObjectInputStream) is).setClassLoader(cl); + } +} +// patch end +``` + +## 多模块 dubbo2.7 使用样例 + +[多模块 dubbo2.7 使用样例](https://github.com/sofastack/sofa-serverless/tree/master/samples/dubbo-samples/rpc/dubbo27/README.md) + +[dubbo2.7多模块适配sdk源码](https://github.com/sofastack/sofa-serverless/tree/master/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.7) + diff --git a/docs/content/zh-cn/docs/contribution-guidelines/runtime/logback.md b/docs/content/zh-cn/docs/contribution-guidelines/runtime/logback.md new file mode 100644 index 000000000..389390e90 --- /dev/null +++ b/docs/content/zh-cn/docs/contribution-guidelines/runtime/logback.md @@ -0,0 +1,114 @@ +--- +title: logback 的多模块化适配 +date: 2024-1-18T15:32:35+08:00 +weight: 1 +--- + +## 为什么需要做适配 +原生 logback 只有默认日志上下文,各个模块间日志配置无法隔离,无法支持独立的模块日志配置,最终导致在合并部署多模块场景下,模块只能使用基座的日志配置,对模块日志打印带来不便。 + +## 多模块适配方案 +Logback 支持原生扩展 ch.qos.logback.classic.selector.ContextSelector,该接口支持自定义上下文选择器,Ark 默认实现了 ContextSelector 对多个模块的 LoggerContext 进行隔离 (参考 com.alipay.sofa.ark.common.adapter.ArkLogbackContextSelector),不同模块使用各自独立的 LoggerContext,确保日志配置隔离 + +启动期,经由 spring 日志系统 LogbackLoggingSystem 对模块日志配置以及日志上下文进行初始化 + +指定上下文选择器为 com.alipay.sofa.ark.common.adapter.ArkLogbackContextSelector,添加JVM启动参数 +> -Dlogback.ContextSelector=com.alipay.sofa.ark.common.adapter.ArkLogbackContextSelector + +当使用 slf4j 作为日志门面,logback 作为日志实现框架时,在基座启动时,首次进行 slf4j 静态绑定时,将初始化具体的 ContextSelector,当没有自定义上下文选择器时,将使用 DefaultContextSelector, 当我们指定上下文选择器时,将会初始化 ArkLogbackContextSelector 作为上下文选择器 + +ch.qos.logback.classic.util.ContextSelectorStaticBinder.init + +```java +public void init(LoggerContext defaultLoggerContext, Object key) { + ... + + String contextSelectorStr = OptionHelper.getSystemProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR); + if (contextSelectorStr == null) { + contextSelector = new DefaultContextSelector(defaultLoggerContext); + } else if (contextSelectorStr.equals("JNDI")) { + // if jndi is specified, let's use the appropriate class + contextSelector = new ContextJNDISelector(defaultLoggerContext); + } else { + contextSelector = dynamicalContextSelector(defaultLoggerContext, contextSelectorStr); + } +} + +static ContextSelector dynamicalContextSelector(LoggerContext defaultLoggerContext, String contextSelectorStr) { + Class contextSelectorClass = Loader.loadClass(contextSelectorStr); + Constructor cons = contextSelectorClass.getConstructor(new Class[] { LoggerContext.class }); + return (ContextSelector) cons.newInstance(defaultLoggerContext); +} +``` + +在 ArkLogbackContextSelector 中,我们使用 ClassLoader 区分不同模块,将模块 LoggerContext 根据 ClassLoader 缓存 + +根据 classloader 获取不同的 LoggerContext,在 Spring 环境启动时,根据 spring 日志系统初始化日志上下文,通过 org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext 获取日志上下文,此时将会使用 Ark 实现的自定义上下文选择器 com.alipay.sofa.ark.common.adapter.ArkLogbackContextSelector.getLoggerContext() 返回不同模块各自的 LoggerContext + +```java +public LoggerContext getLoggerContext() { + ClassLoader classLoader = this.findClassLoader(); + if (classLoader == null) { + return defaultLoggerContext; + } + return getContext(classLoader); +} +``` + +获取 classloader 时,首先获取线程上下文 classloader,当发现是模块的classloader时,直接返回,若tccl不是模块classloader,则从ClassContext中获取调用Class堆栈,遍历堆栈,当发现模块classloader时直接返回,这样做的目的是为了兼容tccl没有保证为模块classloader时的场景, +比如在模块代码中使用logger打印日志时,当前类由模块classloader自己加载,通过ClassContext遍历可以最终获得当前类,获取到模块classloader,以便确保使用模块对应的 LoggerContext + +```java +private ClassLoader findClassLoader() { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader != null && CONTAINER_CLASS_LOADER.equals(classLoader.getClass().getName())) { + return null; + } + if (classLoader != null && BIZ_CLASS_LOADER.equals(classLoader.getClass().getName())) { + return classLoader; + } + + Class[] context = new SecurityManager() { + @Override + public Class[] getClassContext() { + return super.getClassContext(); + } + }.getClassContext(); + if (context == null || context.length == 0) { + return null; + } + for (Class cls : context) { + if (cls.getClassLoader() != null + && BIZ_CLASS_LOADER.equals(cls.getClassLoader().getClass().getName())) { + return cls.getClassLoader(); + } + } + + return null; +} +``` + +获取到合适 classloader 后,为不同 classloader选择不同的 LoggerContext 实例,所有模块上下文缓存在 com.alipay.sofa.ark.common.adapter.ArkLogbackContextSelector.CLASS_LOADER_LOGGER_CONTEXT 中,以 classloader 为 key + +```java +private LoggerContext getContext(ClassLoader cls) { + LoggerContext loggerContext = CLASS_LOADER_LOGGER_CONTEXT.get(cls); + if (null == loggerContext) { + synchronized (ArkLogbackContextSelector.class) { + loggerContext = CLASS_LOADER_LOGGER_CONTEXT.get(cls); + if (null == loggerContext) { + loggerContext = new LoggerContext(); + loggerContext.setName(Integer.toHexString(System.identityHashCode(cls))); + CLASS_LOADER_LOGGER_CONTEXT.put(cls, loggerContext); + } + } + } + return loggerContext; +} +``` + +## 多模块 logback 使用样例 +[多模块 logback 使用样例](https://github.com/sofastack/sofa-serverless/tree/master/samples/springboot-samples/logging/logback/README.md) + +[详细查看ArkLogbackContextSelector源码](https://github.com/sofastack/sofa-ark/blob/master/sofa-ark-parent/core/common/src/main/java/com/alipay/sofa/ark/common/adapter/ArkLogbackContextSelector.java) + diff --git a/docs/content/zh-cn/docs/introduction/architecture/arch-principle.md b/docs/content/zh-cn/docs/introduction/architecture/arch-principle.md index 4159564f9..fbcb3f03d 100644 --- a/docs/content/zh-cn/docs/introduction/architecture/arch-principle.md +++ b/docs/content/zh-cn/docs/introduction/architecture/arch-principle.md @@ -49,7 +49,8 @@ SOFAArk 的隔离方式和 OSGI 是一致的,但是在共享方面 OSGI 和 JP ![image.png](https://intranetproxy.alipay.com/skylark/lark/0/2023/png/149473/1695175141130-d3b55e17-70c3-4e7c-aeef-2e071f89ada8.png#clientId=uaaa65411-0843-4&from=paste&height=316&id=u589ef06e&originHeight=632&originWidth=3642&originalType=binary&ratio=2&rotation=0&showTitle=false&size=139102&status=done&style=none&taskId=uf9f96d68-7456-4af5-951e-d9351092988&title=&width=1821)
图中的箭头是双向的,如果当前微服务拆分过多,也可以将多个微服务低成本改造成模块合并部署在一个 JVM 内。所以这里的本质是通过在单体架构和微服务架构之间增加一个可以双向过渡的模块化架构,降低改造成本的同时,也让开发者可以根据业务发展按需演进或回退。这样可以把微服务的这几个问题解决掉 ### 模块化架构的优势 -模块化架构的优势主要集中在这四点:快、省、灵活部署、可演进,
![image.png](https://intranetproxy.alipay.com/skylark/lark/0/2023/png/149473/1695180240688-82520d0c-2304-47dc-a9f3-22af08424100.png#clientId=ueb39d37f-ca7b-4&from=paste&height=237&id=u4c60feb3&originHeight=688&originWidth=1504&originalType=binary&ratio=2&rotation=0&showTitle=false&size=437668&status=done&style=none&taskId=uf04ead3d-7cf7-41e7-bfff-81857bf5918&title=&width=518) +模块化架构的优势主要集中在这四点:快、省、灵活部署、可演进,
![image.png](https://github.com/sofastack/sofa-serverless/assets/3754074/11d1d662-d33b-482b-946b-bf600aeb34da) + 与传统应用对比数据如下,可以看到在研发阶段、部署阶段、运行阶段都得到了10倍以上的提升效果。
![image.png](https://intranetproxy.alipay.com/skylark/lark/0/2023/png/149473/1695180250909-f5eca1b3-c416-4bac-9732-549a9bed8b87.png#clientId=ueb39d37f-ca7b-4&from=paste&height=261&id=u8907b613&originHeight=522&originWidth=2838&originalType=binary&ratio=2&rotation=0&showTitle=false&size=219589&status=done&style=none&taskId=ua4b2bd1b-a75f-4945-abce-68826a43377&title=&width=1419) diff --git a/module-controller/api/v1alpha1/moduledeployment_types.go b/module-controller/api/v1alpha1/moduledeployment_types.go index f47b0237f..afe5e91e4 100644 --- a/module-controller/api/v1alpha1/moduledeployment_types.go +++ b/module-controller/api/v1alpha1/moduledeployment_types.go @@ -163,10 +163,9 @@ type ModuleDeploymentSpec struct { ProgressDeadlineSeconds int32 `json:"progressDeadlineSeconds,omitempty"` - // Indicates that the moduleDeployment is paused and will not be processed by the - // moduleDeployment controller. - // +optional - Pause bool `json:"pause,omitempty"` + // +kubebuilder:default:=0 + // +kubebuilder:validation:Minimum=0 + ConfirmBatchNum int32 `json:"confirmBatchNum,omitempty"` OperationStrategy ModuleOperationStrategy `json:"operationStrategy,omitempty"` diff --git a/module-controller/config/crd/bases/serverless.alipay.com_moduledeployments.yaml b/module-controller/config/crd/bases/serverless.alipay.com_moduledeployments.yaml index 1fe788c00..c73635969 100644 --- a/module-controller/config/crd/bases/serverless.alipay.com_moduledeployments.yaml +++ b/module-controller/config/crd/bases/serverless.alipay.com_moduledeployments.yaml @@ -41,6 +41,11 @@ spec: Important: Run "make" to regenerate code after modifying this file' minLength: 1 type: string + confirmBatchNum: + default: 0 + format: int32 + minimum: 0 + type: integer minReadySeconds: format: int32 type: integer diff --git a/module-controller/internal/controller/moduledeployment_controller_operation_strategy_suit_test.go b/module-controller/internal/controller/moduledeployment_controller_operation_strategy_suit_test.go index 4933e6532..e6b82a830 100644 --- a/module-controller/internal/controller/moduledeployment_controller_operation_strategy_suit_test.go +++ b/module-controller/internal/controller/moduledeployment_controller_operation_strategy_suit_test.go @@ -570,7 +570,7 @@ var _ = Describe("ModuleDeployment Controller OperationStrategy Test", func() { return err } - if !moduleDeployment.Spec.Pause { + if moduleDeployment.Spec.ConfirmBatchNum > 0 { return fmt.Errorf("the deployment is not paused") } @@ -585,18 +585,22 @@ var _ = Describe("ModuleDeployment Controller OperationStrategy Test", func() { Eventually(func() bool { Expect(k8sClient.Get(context.TODO(), nn, &moduleDeployment)).Should(Succeed()) - moduleDeployment.Spec.Pause = false + moduleDeployment.Spec.ConfirmBatchNum = 1 return Expect(k8sClient.Update(context.TODO(), &moduleDeployment)).Should(Succeed()) }, timeout, interval).Should(BeTrue()) }) + It("wait moduleDeployment Completed", func() { + waitModuleDeploymentCompleted(moduleDeploymentName, namespace) + }) + It("4. check if the moduleDeployment status is completed", func() { Eventually(func() bool { if k8sClient.Get(context.TODO(), nn, &moduleDeployment) != nil { return false } - if moduleDeployment.Spec.Pause != false { + if moduleDeployment.Spec.ConfirmBatchNum != 0 { return false } @@ -619,7 +623,7 @@ var _ = Describe("ModuleDeployment Controller OperationStrategy Test", func() { return err } - if !moduleDeployment.Spec.Pause { + if moduleDeployment.Spec.ConfirmBatchNum > 0 { return fmt.Errorf("the deployment is not paused") } @@ -634,7 +638,7 @@ var _ = Describe("ModuleDeployment Controller OperationStrategy Test", func() { Eventually(func() bool { Expect(k8sClient.Get(context.TODO(), nn, &moduleDeployment)).Should(Succeed()) - moduleDeployment.Spec.Pause = false + moduleDeployment.Spec.ConfirmBatchNum = 1 return Expect(k8sClient.Update(context.TODO(), &moduleDeployment)).Should(Succeed()) }, timeout, interval).Should(BeTrue()) }) @@ -645,7 +649,7 @@ var _ = Describe("ModuleDeployment Controller OperationStrategy Test", func() { return err } - if moduleDeployment.Spec.Pause != false { + if moduleDeployment.Spec.ConfirmBatchNum != 1 { return fmt.Errorf("the module-deployment is paused") } diff --git a/samples/dubbo-samples/rpc/dubbo26/dubbo26biz/src/main/resources/application.properties b/samples/dubbo-samples/rpc/dubbo26/dubbo26biz/src/main/resources/application.properties index c27bad5d7..6e066c863 100644 --- a/samples/dubbo-samples/rpc/dubbo26/dubbo26biz/src/main/resources/application.properties +++ b/samples/dubbo-samples/rpc/dubbo26/dubbo26biz/src/main/resources/application.properties @@ -6,6 +6,8 @@ logging.level.com.alipay.sofa.arklet=INFO #logging.config=classpath:log4j2-spring.xml spring.main.allow-bean-definition-overriding=true +spring.main.allow-bean-definition-overriding=true + dubbo.application.logger=slf4j dubbo.application.name=biz dubbo.consumer.serialization=java diff --git a/samples/dubbo-samples/rpc/dubbo26/dubbo26biz2/src/main/resources/application.properties b/samples/dubbo-samples/rpc/dubbo26/dubbo26biz2/src/main/resources/application.properties index 7dccd63b9..bbf01fa41 100644 --- a/samples/dubbo-samples/rpc/dubbo26/dubbo26biz2/src/main/resources/application.properties +++ b/samples/dubbo-samples/rpc/dubbo26/dubbo26biz2/src/main/resources/application.properties @@ -6,6 +6,8 @@ logging.level.com.alipay.sofa.arklet=INFO #logging.config=classpath:log4j2-spring.xml spring.main.allow-bean-definition-overriding=true +spring.main.allow-bean-definition-overriding=true + dubbo.application.logger=slf4j dubbo.application.name=biz2 dubbo.consumer.serialization=java diff --git a/samples/dubbo-samples/rpc/dubbo27/dubbo27base/pom.xml b/samples/dubbo-samples/rpc/dubbo27/dubbo27base/pom.xml index 296289c66..4bacdad7e 100644 --- a/samples/dubbo-samples/rpc/dubbo27/dubbo27base/pom.xml +++ b/samples/dubbo-samples/rpc/dubbo27/dubbo27base/pom.xml @@ -29,8 +29,8 @@ com.alipay.sofa.serverless - sofa-serverless-adapter-dubbo27 - ${sofa.serverless.runtime.version} + sofa-serverless-adapter-dubbo2.7 + 0.5.7-SNAPSHOT com.alipay.sofa @@ -57,6 +57,11 @@ spring-boot-starter-log4j2 + + + + + com.lmax disruptor diff --git a/samples/dubbo-samples/rpc/dubbo27/dubbo27biz/pom.xml b/samples/dubbo-samples/rpc/dubbo27/dubbo27biz/pom.xml index 10b11e181..07bd502c5 100644 --- a/samples/dubbo-samples/rpc/dubbo27/dubbo27biz/pom.xml +++ b/samples/dubbo-samples/rpc/dubbo27/dubbo27biz/pom.xml @@ -34,6 +34,18 @@ spring-boot-starter-test test + + com.alipay.sofa.serverless + sofa-serverless-adapter-log4j2 + ${sofa.serverless.runtime.version} + provided + + + com.alipay.sofa.serverless + sofa-serverless-adapter-dubbo2.7 + ${sofa.serverless.runtime.version} + provided + org.projectlombok lombok diff --git a/samples/dubbo-samples/rpc/dubbo27/dubbo27biz2/pom.xml b/samples/dubbo-samples/rpc/dubbo27/dubbo27biz2/pom.xml index 952532c4a..ac3192c37 100644 --- a/samples/dubbo-samples/rpc/dubbo27/dubbo27biz2/pom.xml +++ b/samples/dubbo-samples/rpc/dubbo27/dubbo27biz2/pom.xml @@ -34,6 +34,18 @@ spring-boot-starter-test test + + com.alipay.sofa.serverless + sofa-serverless-adapter-log4j2 + ${sofa.serverless.runtime.version} + provided + + + com.alipay.sofa.serverless + sofa-serverless-adapter-dubbo2.7 + ${sofa.serverless.runtime.version} + provided + org.projectlombok lombok diff --git a/samples/dubbo-samples/rpc/dubbo27/dubbo27biz2/src/main/java/com/alipay/sofa/rpc/dubbo27/biz2/service/BizDemoServiceImpl.java b/samples/dubbo-samples/rpc/dubbo27/dubbo27biz2/src/main/java/com/alipay/sofa/rpc/dubbo27/biz2/service/BizDemoServiceImpl.java index d68be17c8..030c4766b 100644 --- a/samples/dubbo-samples/rpc/dubbo27/dubbo27biz2/src/main/java/com/alipay/sofa/rpc/dubbo27/biz2/service/BizDemoServiceImpl.java +++ b/samples/dubbo-samples/rpc/dubbo27/dubbo27biz2/src/main/java/com/alipay/sofa/rpc/dubbo27/biz2/service/BizDemoServiceImpl.java @@ -23,7 +23,7 @@ public class BizDemoServiceImpl implements DemoService { @Override public DemoResponse handle(DemoRequest demoRequest) { DemoResponse response = new DemoResponse(); - response.setResult(demoRequest.getBiz() + getClass().getName()); + response.setResult(demoRequest.getBiz() + "->" + getClass().getName()); return response; } } \ No newline at end of file diff --git a/samples/dubbo-samples/rpc/dubbo3/dubbo3base/src/main/resources/application.yml b/samples/dubbo-samples/rpc/dubbo3/dubbo3base/src/main/resources/application.yml index 3d9e3f67a..cd69197c3 100644 --- a/samples/dubbo-samples/rpc/dubbo3/dubbo3base/src/main/resources/application.yml +++ b/samples/dubbo-samples/rpc/dubbo3/dubbo3base/src/main/resources/application.yml @@ -8,6 +8,8 @@ dubbo: name: tri port: 50051 +server: + port: 8089 spring: application: diff --git a/samples/springboot-samples/service/biz2/biz2-bootstrap/src/main/java/com/alipay/sofa/biz2/rest/SampleController.java b/samples/springboot-samples/service/biz2/biz2-bootstrap/src/main/java/com/alipay/sofa/biz2/rest/SampleController.java index 06bf396d8..3201f3cc1 100644 --- a/samples/springboot-samples/service/biz2/biz2-bootstrap/src/main/java/com/alipay/sofa/biz2/rest/SampleController.java +++ b/samples/springboot-samples/service/biz2/biz2-bootstrap/src/main/java/com/alipay/sofa/biz2/rest/SampleController.java @@ -77,10 +77,10 @@ public String hello() { Result provide2 = provider.provide(new Param()); } - Provider teacherProvider1 = SpringServiceFinder.getModuleService("biz", "0.0.1-SNAPSHOT", "teacherProvider", Provider.class); + Provider teacherProvider1 = SpringServiceFinder.getModuleService("biz1", "0.0.1-SNAPSHOT", "teacherProvider", Provider.class); Result result1 = teacherProvider1.provide(new Param()); - Map providerMap = SpringServiceFinder.listModuleServices("biz", "0.0.1-SNAPSHOT", Provider.class); + Map providerMap = SpringServiceFinder.listModuleServices("biz1", "0.0.1-SNAPSHOT", Provider.class); for (String beanName : providerMap.keySet()) { Result result2 = providerMap.get(beanName).provide(new Param()); } diff --git a/samples/springboot3-samples/db/mybatis/base/pom.xml b/samples/springboot3-samples/db/mybatis/base/pom.xml index 747817265..c13a01383 100644 --- a/samples/springboot3-samples/db/mybatis/base/pom.xml +++ b/samples/springboot3-samples/db/mybatis/base/pom.xml @@ -99,19 +99,6 @@ sofa-common-tools 2.0.3 - - - com.alipay.sofa.serverless - sofa-serverless-adapter-log4j2 - ${sofa.serverless.runtime.version} - - - - com.alipay.sofa - sofa-boot-alipay-autoconfigure - - - diff --git a/samples/springboot3-samples/db/mybatis/biz1/pom.xml b/samples/springboot3-samples/db/mybatis/biz1/pom.xml index 4d6181379..e1ec97354 100644 --- a/samples/springboot3-samples/db/mybatis/biz1/pom.xml +++ b/samples/springboot3-samples/db/mybatis/biz1/pom.xml @@ -48,6 +48,12 @@ provided + + com.alipay.sofa.serverless + sofa-serverless-app-starter + provided + + org.springframework.boot spring-boot-starter-test @@ -65,13 +71,6 @@ - - com.alipay.sofa.serverless - sofa-serverless-adapter-log4j2 - ${sofa.serverless.runtime.version} - provided - - diff --git a/samples/springboot3-samples/logging/log4j2/base/pom.xml b/samples/springboot3-samples/logging/log4j2/base/pom.xml index 5430bc80c..5eb5138be 100644 --- a/samples/springboot3-samples/logging/log4j2/base/pom.xml +++ b/samples/springboot3-samples/logging/log4j2/base/pom.xml @@ -72,18 +72,6 @@ disruptor ${disruptor.version} - - com.alipay.sofa.serverless - sofa-serverless-adapter-log4j2 - ${sofa.serverless.runtime.version} - - - - com.alipay.sofa - sofa-boot-alipay-autoconfigure - - - diff --git a/samples/springboot3-samples/logging/log4j2/biz1/pom.xml b/samples/springboot3-samples/logging/log4j2/biz1/pom.xml index 5a324192f..e32a43f6e 100644 --- a/samples/springboot3-samples/logging/log4j2/biz1/pom.xml +++ b/samples/springboot3-samples/logging/log4j2/biz1/pom.xml @@ -28,16 +28,15 @@ - org.springframework.boot - spring-boot-starter-test - test + com.alipay.sofa.serverless + sofa-serverless-app-starter + provided - com.alipay.sofa.serverless - sofa-serverless-adapter-log4j2 - ${sofa.serverless.runtime.version} - provided + org.springframework.boot + spring-boot-starter-test + test diff --git a/samples/springboot3-samples/logging/log4j2/biz2/pom.xml b/samples/springboot3-samples/logging/log4j2/biz2/pom.xml index eb7ba2d66..8351e72c8 100644 --- a/samples/springboot3-samples/logging/log4j2/biz2/pom.xml +++ b/samples/springboot3-samples/logging/log4j2/biz2/pom.xml @@ -27,6 +27,12 @@ provided + + com.alipay.sofa.serverless + sofa-serverless-app-starter + provided + + org.springframework.boot spring-boot-starter-test @@ -39,12 +45,6 @@ - - com.alipay.sofa.serverless - sofa-serverless-adapter-log4j2 - ${sofa.serverless.runtime.version} - provided - diff --git a/samples/springboot3-samples/logging/logback/README.md b/samples/springboot3-samples/logging/logback/README.md new file mode 100644 index 000000000..1fe9ed5da --- /dev/null +++ b/samples/springboot3-samples/logging/logback/README.md @@ -0,0 +1,202 @@ +# 支持基座、模块使用采用独立日志配置打印logback日志 +原理详看[这里](https://github.com/sofastack/sofa-serverless/blob/master/docs/content/zh-cn/docs/contribution-guidelines/runtime/logj42.md) + +# 实验内容 +## 实验应用 +### base +base 为普通 springboot 改造成的基座,改造内容为在 pom 里增加如下依赖 +```xml + + + + com.alipay.sofa.serverless + sofa-serverless-base-starter + ${sofa.serverless.runtime.version} + pom + + + com.alipay.sofa + web-ark-plugin + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-logging + +``` + +注意⚠️:需要基座、模块采用独立日志配置特性,要求,sofa-ark-common 包版本不低于 2.2.6 + +基座自定义日志配置参考 logback-spring.xml,其中为控制台输出自定义pattern,日志前方添加 ${appname} 000,并且定义appender将日志输出到基座名目录下 ${logging.file.path}/${appname}/app-default.log +```xml + + + + + + + + + ${appname} 000 %date %5level %6relative --- [%15thread] [%-40logger{40}] [%C:%L] : [%X{traceId:-0}] %msg%n + + + + true + + ${level} + + ${logging.file.path}/${appname}/app-default.log + + ${logging.file.path}/${appname}/app-default.log.%d{yyyy-MM-dd} + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + + + + + + + +``` +注意⚠️:基座、模块日志隔离能力,依赖 logback 原生 context selector 特性,需要在jvm启动参数或系统属性中指定 contextSelector + +方法一:添加jvm启动参数 -Dlogback.ContextSelector=com.alipay.sofa.ark.common.adapter.ArkLogbackContextSelector + +方法二:添加系统属性,需要保证在首次获取 logger 前设置 + +```java +@ImportResource({ "classpath*:META-INF/spring/service.xml"}) +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class}) +public class BaseApplication { + + static { + ArkConfigs.setEmbedEnable(true); + // 建议加到jvm 参数中 + // 需要保证在 slf4j static bind 之前,(如,首次 getLogger、类加载 SpringApplication 之前) + System.setProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR, + "com.alipay.sofa.ark.common.adapter.ArkLogbackContextSelector"); + } + + private static Logger LOGGER = LoggerFactory.getLogger(BaseApplication.class); + + public static void main(String[] args) { + ConfigurableApplicationContext context = SpringApplication.run( + BaseApplication.class, args); + } +} +``` + +### biz +biz 原来是普通 springboot,修改打包插件方式为 sofaArk biz 模块打包方式,打包为 ark biz jar 包,打包插件配置如下: +```xml + + com.alipay.sofa + sofa-ark-maven-plugin + ${sofa.ark.version} + + + default-cli + + repackage + + + + + true + ./target + biz1-logback + biz1 + true + + +``` +注意这里将不同 biz 的 web context path 修改成不同的值,以此才能成功在一个 tomcat host 里安装多个 web 应用。 + +模块自定义日志配置见模块项目资源目录中的 logback-spring.xml,其中为控制台输出自定义pattern,日志前方添加 ${appname} 111,并且定义appender将日志输出到模块名目录下 ${logging.file.path}/${appname}/app-default.log + +```xml + + + + + + + + + + ${appname} 111 %date %5level %6relative --- [%15thread] [%-40logger{40}] [%C:%L] : [%X{traceId:-0}] %msg%n + + + + true + + ${level} + + ${logging.file.path}/${appname}/app-default.log + + ${logging.file.path}/${appname}/app-default.log.%d{yyyy-MM-dd} + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + + + + + + + +``` + +## 实验任务 +### 执行 mvn clean package -DskipTests +可在各 bundle 的 target 目录里查看到打包生成的 ark-biz jar 包 +### 启动基座应用 base,确保基座启动成功 +### 执行 curl 命令安装 biz +```shell +curl --location --request POST 'localhost:1238/installBiz' \ +--header 'Content-Type: application/json' \ +--data '{ + "bizName": "biz1-logback", + "bizVersion": "0.0.1-SNAPSHOT", + // local path should start with file://, alse support remote url which can be downloaded + "bizUrl": "file:///xxxx/samples/springboot-samples/logging/logback/biz1/target/biz1-logback-0.0.1-SNAPSHOT-ark-biz.jar" +}' +``` + +### 验证 + +1. 先查看基座启动日志,可以见到日志中有"base 000" 字样,满足我们日志配置中的pattern,同时在 logging.file.path=./logging/logback/logs/ 目录下存在基座日志文件 + ![img.png](img.png) +2. 再启动模块后,查看模块启动日志,可以见到日志中有"biz1 111" 字样,满足我们日志配置中的pattern,同时在 logging.file.path=./logging/logback/logs/ 目录下存在模块日志文件 + ![img_1.png](img_1.png) +3. 发起请求验证模块web服务 + +```shell +curl http://localhost:8080/biz2 +``` +返回 `hello to /biz1 deploy`,同时查看控制台日志输出,满足我们日志配置中的pattern +```log +biz1 111 2023-12-27 20:05:55,543 INFO 25790 --- [http-nio-8080-exec-1] [c.a.sofa.web.biz1.rest.SampleController ] [com.alipay.sofa.web.biz1.rest.SampleController:21] : [0] /biz1 web test: into sample controller +``` + +## 注意事项 +这里主要使用简单应用做验证,如果复杂应用,需要注意模块做好瘦身,基座有的依赖,模块尽可能设置成 provided,尽可能使用基座的依赖。 diff --git a/samples/springboot3-samples/logging/logback/base/Dockerfile b/samples/springboot3-samples/logging/logback/base/Dockerfile new file mode 100644 index 000000000..a21cfafd1 --- /dev/null +++ b/samples/springboot3-samples/logging/logback/base/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:8-jdk-alpine +ARG JAR_FILE=target/*.jar +ADD ${JAR_FILE} app.jar +EXPOSE 8080 +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/samples/springboot3-samples/logging/logback/base/pom.xml b/samples/springboot3-samples/logging/logback/base/pom.xml new file mode 100644 index 000000000..cac4f4878 --- /dev/null +++ b/samples/springboot3-samples/logging/logback/base/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + com.alipay.sofa + springboot-samples + 0.0.1-SNAPSHOT + ../../../pom.xml + + com.alipay.sofa.logging.logback + base-logback + 0.0.1-SNAPSHOT + base-logback + logging base for logback + + + + + + com.alipay.sofa.serverless + sofa-serverless-adapter-logback + ${sofa.serverless.runtime.version} + + + com.alipay.sofa.serverless + sofa-serverless-base-starter + ${sofa.serverless.runtime.version} + pom + + + + com.alipay.sofa + web-ark-plugin + + + + + com.alipay.sofa + log-sofa-boot-starter + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/BaseApplication.java b/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/BaseApplication.java new file mode 100644 index 000000000..965e8c020 --- /dev/null +++ b/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/BaseApplication.java @@ -0,0 +1,33 @@ +package com.alipay.sofa.web.base; + +import ch.qos.logback.classic.ClassicConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ImportResource; + +@ImportResource({ "classpath*:META-INF/spring/service.xml"}) +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class}) +public class BaseApplication { + + static { + // 建议加到jvm 参数中 + // 需要保证在 slf4j static bind 之前,(如,首次 getLogger、类加载 SpringApplication 之前) + System.setProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR, + "com.alipay.sofa.serverless.adapter.ArkLogbackContextSelector"); + } + + private static Logger LOGGER = LoggerFactory.getLogger(BaseApplication.class); + + public static void main(String[] args) { + ConfigurableApplicationContext context = SpringApplication.run( + BaseApplication.class, args); + context.getBean("sampleService"); + LOGGER.info("BaseApplication start!"); + LOGGER.info("Spring Boot Version: " + SpringApplication.class.getPackage().getImplementationVersion()); + LOGGER.info("BaseApplication classLoader: " + BaseApplication.class.getClassLoader()); + } +} diff --git a/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/facade/SampleService.java b/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/facade/SampleService.java new file mode 100644 index 000000000..b0cf219e3 --- /dev/null +++ b/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/facade/SampleService.java @@ -0,0 +1,10 @@ +package com.alipay.sofa.web.base.facade; + +public interface SampleService { + + /** + * a simple facade + * @return + */ + String service(); +} diff --git a/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/impl/SampleServiceImpl.java b/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/impl/SampleServiceImpl.java new file mode 100644 index 000000000..0ed2c273b --- /dev/null +++ b/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/impl/SampleServiceImpl.java @@ -0,0 +1,24 @@ +package com.alipay.sofa.web.base.impl; + +import com.alipay.sofa.web.base.facade.SampleService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +@Service +public class SampleServiceImpl implements SampleService { + private static Logger LOGGER = LoggerFactory.getLogger(SampleServiceImpl.class); + + @Autowired + private ApplicationContext applicationContext; + + @Override + public String service() { + String appName = applicationContext.getId(); + + LOGGER.info("{} web test: into a service", appName); + return "A Sample Service"; + } +} diff --git a/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/rest/SampleController.java b/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/rest/SampleController.java new file mode 100644 index 000000000..5e5e29ee0 --- /dev/null +++ b/samples/springboot3-samples/logging/logback/base/src/main/java/com/alipay/sofa/web/base/rest/SampleController.java @@ -0,0 +1,31 @@ +package com.alipay.sofa.web.base.rest; + +import com.alipay.sofa.web.base.facade.SampleService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SampleController { + private static Logger LOGGER = LoggerFactory.getLogger(SampleController.class); + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private SampleService sampleService; + + @RequestMapping(value = "/", method = RequestMethod.GET) + public String hello() { + String appName = applicationContext.getId(); + LOGGER.info("{} web test: into sample controller", appName); + + sampleService.service(); + + return String.format("hello to %s deploy", appName); + } +} diff --git a/samples/springboot3-samples/logging/logback/base/src/main/resources/META-INF/spring/service.xml b/samples/springboot3-samples/logging/logback/base/src/main/resources/META-INF/spring/service.xml new file mode 100644 index 000000000..0ce6e397c --- /dev/null +++ b/samples/springboot3-samples/logging/logback/base/src/main/resources/META-INF/spring/service.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/samples/springboot3-samples/logging/logback/base/src/main/resources/application.properties b/samples/springboot3-samples/logging/logback/base/src/main/resources/application.properties new file mode 100644 index 000000000..e56c67b30 --- /dev/null +++ b/samples/springboot3-samples/logging/logback/base/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.application.name=base +logging.file.path=./logging/logback/logs/ diff --git a/samples/springboot3-samples/logging/logback/base/src/main/resources/logback-spring.xml b/samples/springboot3-samples/logging/logback/base/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..bd7018eac --- /dev/null +++ b/samples/springboot3-samples/logging/logback/base/src/main/resources/logback-spring.xml @@ -0,0 +1,35 @@ + + + + + + + + + ${appname} 000 %date %5level %6relative --- [%15thread] [%-40logger{40}] [%C:%L] : [%X{traceId:-0}] %msg%n + + + + true + + ${level} + + ${logging.file.path}/${appname}/app-default.log + + ${logging.file.path}/${appname}/app-default.log.%d{yyyy-MM-dd} + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + + + + + + + \ No newline at end of file diff --git a/samples/springboot3-samples/logging/logback/base/src/test/java/com/alipay/sofa/web/base/BaseApplicationTests.java b/samples/springboot3-samples/logging/logback/base/src/test/java/com/alipay/sofa/web/base/BaseApplicationTests.java new file mode 100644 index 000000000..e53b9df4c --- /dev/null +++ b/samples/springboot3-samples/logging/logback/base/src/test/java/com/alipay/sofa/web/base/BaseApplicationTests.java @@ -0,0 +1,13 @@ +package com.alipay.sofa.web.base; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class BaseApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/samples/springboot3-samples/logging/logback/biz1/pom.xml b/samples/springboot3-samples/logging/logback/biz1/pom.xml new file mode 100644 index 000000000..d7b4635da --- /dev/null +++ b/samples/springboot3-samples/logging/logback/biz1/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + com.alipay.sofa + springboot-samples + 0.0.1-SNAPSHOT + ../../../pom.xml + + com.alipay.sofa.logging.logback + biz1-logback + 0.0.1-SNAPSHOT + biz1-logback + biz1 + + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework.boot + spring-boot-starter-logging + provided + + + com.alipay.sofa.serverless + sofa-serverless-app-starter + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + + + + + + + com.alipay.sofa + sofa-ark-maven-plugin + ${sofa.ark.version} + + + default-cli + + repackage + + + + + true + ./target + biz1-logback + biz1 + true + + + + + + + + + diff --git a/samples/springboot3-samples/logging/logback/biz1/src/main/java/com/alipay/sofa/web/biz1/Biz1Application.java b/samples/springboot3-samples/logging/logback/biz1/src/main/java/com/alipay/sofa/web/biz1/Biz1Application.java new file mode 100644 index 000000000..5d8408107 --- /dev/null +++ b/samples/springboot3-samples/logging/logback/biz1/src/main/java/com/alipay/sofa/web/biz1/Biz1Application.java @@ -0,0 +1,20 @@ +package com.alipay.sofa.web.biz1; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Biz1Application { + private static Logger LOGGER = LoggerFactory.getLogger(Biz1Application.class); + + public static void main(String[] args) { + SpringApplication.run(Biz1Application.class, args); + + LOGGER.info("BaseApplication start!"); + LOGGER.info("Spring Boot Version: " + SpringApplication.class.getPackage().getImplementationVersion()); + LOGGER.info("BaseApplication classLoader: " + Biz1Application.class.getClassLoader()); + } + +} diff --git a/samples/springboot3-samples/logging/logback/biz1/src/main/java/com/alipay/sofa/web/biz1/rest/SampleController.java b/samples/springboot3-samples/logging/logback/biz1/src/main/java/com/alipay/sofa/web/biz1/rest/SampleController.java new file mode 100644 index 000000000..604da157f --- /dev/null +++ b/samples/springboot3-samples/logging/logback/biz1/src/main/java/com/alipay/sofa/web/biz1/rest/SampleController.java @@ -0,0 +1,24 @@ +package com.alipay.sofa.web.biz1.rest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SampleController { + private static final Logger LOGGER = LoggerFactory.getLogger(SampleController.class); + + @Autowired + private ApplicationContext applicationContext; + + @RequestMapping(value = "/", method = RequestMethod.GET) + public String hello() { + String appName = applicationContext.getApplicationName(); + LOGGER.info("{} web test: into sample controller", appName); + return String.format("hello to %s deploy", appName); + } +} diff --git a/samples/springboot3-samples/logging/logback/biz1/src/main/resources/application.properties b/samples/springboot3-samples/logging/logback/biz1/src/main/resources/application.properties new file mode 100644 index 000000000..e1f613136 --- /dev/null +++ b/samples/springboot3-samples/logging/logback/biz1/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.application.name=biz1 +logging.file.path=./logging/logback/logs/ diff --git a/samples/springboot3-samples/logging/logback/biz1/src/main/resources/logback-spring.xml b/samples/springboot3-samples/logging/logback/biz1/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..dbee21580 --- /dev/null +++ b/samples/springboot3-samples/logging/logback/biz1/src/main/resources/logback-spring.xml @@ -0,0 +1,36 @@ + + + + + + + + + + ${appname} 111 %date %5level %6relative --- [%15thread] [%-40logger{40}] [%C:%L] : [%X{traceId:-0}] %msg%n + + + + true + + ${level} + + ${logging.file.path}/${appname}/app-default.log + + ${logging.file.path}/${appname}/app-default.log.%d{yyyy-MM-dd} + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n + UTF-8 + + + + + + + + + + + \ No newline at end of file diff --git a/samples/springboot3-samples/logging/logback/biz1/src/test/java/com/alipay/sofa/web/biz1/Biz1ApplicationTests.java b/samples/springboot3-samples/logging/logback/biz1/src/test/java/com/alipay/sofa/web/biz1/Biz1ApplicationTests.java new file mode 100644 index 000000000..8fe97cb5d --- /dev/null +++ b/samples/springboot3-samples/logging/logback/biz1/src/test/java/com/alipay/sofa/web/biz1/Biz1ApplicationTests.java @@ -0,0 +1,13 @@ +package com.alipay.sofa.web.biz1; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Biz1ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/samples/springboot3-samples/logging/logback/img.png b/samples/springboot3-samples/logging/logback/img.png new file mode 100644 index 000000000..082a742e0 Binary files /dev/null and b/samples/springboot3-samples/logging/logback/img.png differ diff --git a/samples/springboot3-samples/logging/logback/img_1.png b/samples/springboot3-samples/logging/logback/img_1.png new file mode 100644 index 000000000..c103364f6 Binary files /dev/null and b/samples/springboot3-samples/logging/logback/img_1.png differ diff --git a/samples/springboot3-samples/logging/logback/pom.xml b/samples/springboot3-samples/logging/logback/pom.xml new file mode 100644 index 000000000..c9ce32eb3 --- /dev/null +++ b/samples/springboot3-samples/logging/logback/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + com.alipay.sofa + springboot-samples + 0.0.1-SNAPSHOT + ../../pom.xml + + com.alipay.sofa.web + logback + 0.0.1-SNAPSHOT + pom + + + base + biz1 + + diff --git a/samples/springboot3-samples/msg/kafka/base/pom.xml b/samples/springboot3-samples/msg/kafka/base/pom.xml index c517d5598..b8c03c0e7 100644 --- a/samples/springboot3-samples/msg/kafka/base/pom.xml +++ b/samples/springboot3-samples/msg/kafka/base/pom.xml @@ -78,19 +78,6 @@ sofa-common-tools 2.0.3 - - - com.alipay.sofa.serverless - sofa-serverless-adapter-log4j2 - ${sofa.serverless.runtime.version} - - - - com.alipay.sofa - sofa-boot-alipay-autoconfigure - - - diff --git a/samples/springboot3-samples/msg/kafka/biz1/pom.xml b/samples/springboot3-samples/msg/kafka/biz1/pom.xml index 0f1f3655b..58085494d 100644 --- a/samples/springboot3-samples/msg/kafka/biz1/pom.xml +++ b/samples/springboot3-samples/msg/kafka/biz1/pom.xml @@ -27,6 +27,12 @@ provided + + com.alipay.sofa.serverless + sofa-serverless-app-starter + provided + + org.springframework.boot spring-boot-starter-test @@ -44,12 +50,6 @@ spring-kafka provided - - com.alipay.sofa.serverless - sofa-serverless-adapter-log4j2 - ${sofa.serverless.runtime.version} - provided - diff --git a/samples/springboot3-samples/msg/kafka/biz2/pom.xml b/samples/springboot3-samples/msg/kafka/biz2/pom.xml index a00a8a88e..c76a12933 100644 --- a/samples/springboot3-samples/msg/kafka/biz2/pom.xml +++ b/samples/springboot3-samples/msg/kafka/biz2/pom.xml @@ -27,6 +27,12 @@ provided + + com.alipay.sofa.serverless + sofa-serverless-app-starter + provided + + org.springframework.boot spring-boot-starter-test @@ -44,12 +50,6 @@ - - com.alipay.sofa.serverless - sofa-serverless-adapter-log4j2 - ${sofa.serverless.runtime.version} - provided - diff --git a/samples/springboot3-samples/pom.xml b/samples/springboot3-samples/pom.xml index bc78956a1..8d27c9e6f 100644 --- a/samples/springboot3-samples/pom.xml +++ b/samples/springboot3-samples/pom.xml @@ -18,9 +18,9 @@ pom - 3.2.1 + 3.0.9 17 - 3.0.1 + 3.0.2-SNAPSHOT 0.5.6-jdk17-SNAPSHOT 3.4.2 1.7.1 @@ -32,6 +32,7 @@ logging/log4j2/base logging/log4j2/biz1 logging/log4j2/biz2 + logging/logback web/tomcat/base web/tomcat/biz1 web/tomcat/biz2 diff --git a/samples/springboot3-samples/web/tomcat/biz1/pom.xml b/samples/springboot3-samples/web/tomcat/biz1/pom.xml index 38f22b52d..b87f7f13a 100644 --- a/samples/springboot3-samples/web/tomcat/biz1/pom.xml +++ b/samples/springboot3-samples/web/tomcat/biz1/pom.xml @@ -21,6 +21,11 @@ spring-boot-starter-web provided + + com.alipay.sofa.serverless + sofa-serverless-app-starter + provided + org.springframework.boot diff --git a/samples/springboot3-samples/web/tomcat/biz2/pom.xml b/samples/springboot3-samples/web/tomcat/biz2/pom.xml index a83bd5d4b..6386a87e0 100644 --- a/samples/springboot3-samples/web/tomcat/biz2/pom.xml +++ b/samples/springboot3-samples/web/tomcat/biz2/pom.xml @@ -21,6 +21,11 @@ spring-boot-starter-web provided + + com.alipay.sofa.serverless + sofa-serverless-app-starter + provided + org.springframework.boot diff --git a/sofa-serverless-runtime/arklet-core/pom.xml b/sofa-serverless-runtime/arklet-core/pom.xml index d921e0fb6..13394d064 100644 --- a/sofa-serverless-runtime/arklet-core/pom.xml +++ b/sofa-serverless-runtime/arklet-core/pom.xml @@ -17,6 +17,11 @@ com.alipay.sofa sofa-ark-api + + com.alipay.sofa.serverless + sofa-serverless-common + ${revision} + com.github.oshi @@ -69,13 +74,6 @@ mockito-core test - - - org.mockito - mockito-inline - test - - diff --git a/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/model/BatchInstallRequest.java b/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/model/BatchInstallRequest.java index 91bca4153..4406a9f31 100644 --- a/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/model/BatchInstallRequest.java +++ b/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/model/BatchInstallRequest.java @@ -23,8 +23,8 @@ /** * 合并部署请求。 - * @author gouzhendong.gzd - * @version $Id: BatchInstallRequest, v 0.1 2023-11-20 15:21 gouzhendong.gzd Exp $ + * @author CodeNoobKingKc2 + * @version $Id: BatchInstallRequest, v 0.1 2023-11-20 15:21 CodeNoobKingKc2 Exp $ */ @NoArgsConstructor @AllArgsConstructor diff --git a/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/model/BatchInstallResponse.java b/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/model/BatchInstallResponse.java index 0fb723fe1..3b6c57267 100644 --- a/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/model/BatchInstallResponse.java +++ b/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/common/model/BatchInstallResponse.java @@ -27,8 +27,8 @@ /** * 合并部署响应。 - * @author gouzhendong.gzd - * @version $Id: BatchInstallResponse, v 0.1 2023-11-20 15:19 gouzhendong.gzd Exp $ + * @author CodeNoobKingKc2 + * @version $Id: BatchInstallResponse, v 0.1 2023-11-20 15:19 CodeNoobKingKc2 Exp $ */ @NoArgsConstructor @AllArgsConstructor diff --git a/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/BatchInstallHelper.java b/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/BatchInstallHelper.java index 2874ff0b8..509b4c5b8 100644 --- a/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/BatchInstallHelper.java +++ b/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/BatchInstallHelper.java @@ -32,8 +32,8 @@ /** * 合并部署帮助类。 - * @author gouzhendong.gzd - * @version $Id: BatchInstallService, v 0.1 2023-11-20 15:35 gouzhendong.gzd Exp $ + * @author CodeNoobKingKc2 + * @version $Id: BatchInstallService, v 0.1 2023-11-20 15:35 CodeNoobKingKc2 Exp $ */ public class BatchInstallHelper { diff --git a/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationServiceImpl.java b/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationServiceImpl.java index b9f1b8b74..3f508b321 100644 --- a/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationServiceImpl.java +++ b/sofa-serverless-runtime/arklet-core/src/main/java/com/alipay/sofa/serverless/arklet/core/ops/UnifiedOperationServiceImpl.java @@ -33,6 +33,7 @@ import com.alipay.sofa.serverless.arklet.core.common.log.ArkletLoggerFactory; import com.alipay.sofa.serverless.arklet.core.common.model.BatchInstallRequest; import com.alipay.sofa.serverless.arklet.core.common.model.BatchInstallResponse; +import com.alipay.sofa.serverless.common.util.OSUtils; import com.google.inject.Singleton; /** @@ -67,7 +68,8 @@ public ClientResponse safeBatchInstall(String bizUrl) { BizOperation bizOperation = new BizOperation() .setOperationType(BizOperation.OperationType.INSTALL); - bizOperation.putParameter(Constants.CONFIG_BIZ_URL, "file://" + bizUrl); + bizOperation.putParameter(Constants.CONFIG_BIZ_URL, + OSUtils.getLocalFileProtocolPrefix() + bizUrl); Map mainAttributes = batchInstallHelper.getMainAttributes(bizUrl); bizOperation.setBizName((String) mainAttributes.get(Constants.ARK_BIZ_NAME)); bizOperation.setBizVersion((String) mainAttributes.get(Constants.ARK_BIZ_VERSION)); diff --git a/sofa-serverless-runtime/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/component/UnifiedOperationServiceImplTests.java b/sofa-serverless-runtime/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/component/UnifiedOperationServiceImplTests.java index 0b74e4127..6067107f2 100644 --- a/sofa-serverless-runtime/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/component/UnifiedOperationServiceImplTests.java +++ b/sofa-serverless-runtime/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/component/UnifiedOperationServiceImplTests.java @@ -133,12 +133,9 @@ public void testBatchInstall() { BatchInstallResponse response = unifiedOperationService.batchInstall(BatchInstallRequest .builder().bizDirAbsolutePath("/path/to/biz").build()); - Assert.assertTrue(response.getBizUrlToResponse(). + Assert.assertTrue(response.getBizUrlToResponse().containsKey("/file/a-biz.jar")); - containsKey("/file/a-biz.jar")); - Assert.assertTrue(response.getBizUrlToResponse(). - - containsKey("/file/b-biz.jar")); + Assert.assertTrue(response.getBizUrlToResponse().containsKey("/file/b-biz.jar")); } } diff --git a/sofa-serverless-runtime/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/ops/BatchInstallHelperTest.java b/sofa-serverless-runtime/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/ops/BatchInstallHelperTest.java index 952354c2f..97b7af09e 100644 --- a/sofa-serverless-runtime/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/ops/BatchInstallHelperTest.java +++ b/sofa-serverless-runtime/arklet-core/src/test/java/com/alipay/sofa/serverless/arklet/core/ops/BatchInstallHelperTest.java @@ -43,8 +43,8 @@ /** * - * @author gouzhendong.gzd - * @version $Id: BatchInstallHelper, v 0.1 2023-11-22 10:59 gouzhendong.gzd Exp $ + * @author CodeNoobKingKc2 + * @version $Id: BatchInstallHelper, v 0.1 2023-11-22 10:59 CodeNoobKingKc2 Exp $ */ @RunWith(MockitoJUnitRunner.class) public class BatchInstallHelperTest { diff --git a/sofa-serverless-runtime/arklet-springboot-starter/pom.xml b/sofa-serverless-runtime/arklet-springboot-starter/pom.xml index 3b1f26714..88d0bfd69 100644 --- a/sofa-serverless-runtime/arklet-springboot-starter/pom.xml +++ b/sofa-serverless-runtime/arklet-springboot-starter/pom.xml @@ -98,12 +98,6 @@ test - - org.mockito - mockito-inline - test - - org.mockito mockito-core diff --git a/sofa-serverless-runtime/pom.xml b/sofa-serverless-runtime/pom.xml index c0fac8085..cbffccf01 100644 --- a/sofa-serverless-runtime/pom.xml +++ b/sofa-serverless-runtime/pom.xml @@ -9,10 +9,9 @@ pom - 3.0.1 + 3.0.2-SNAPSHOT 3.0.9 - 1.3.5 - 0.5.5-jdk17 + 0.5.6-jdk17-SNAPSHOT UTF-8 UTF-8 1.8 @@ -29,6 +28,7 @@ 8 1.5.0 6.4.5 + 3.12.0 6.0.0 1.5.0 6.4.5 @@ -77,7 +77,6 @@ arklet-core arklet-springboot-starter sofa-serverless-adapter-ext - sofa-serverless-base-loader diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/pom.xml b/sofa-serverless-runtime/sofa-serverless-adapter-ext/pom.xml index 545ba925d..d37361e22 100644 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/pom.xml +++ b/sofa-serverless-runtime/sofa-serverless-adapter-ext/pom.xml @@ -13,9 +13,9 @@ pom + sofa-serverless-adapter-logback sofa-serverless-adapter-log4j2 sofa-serverless-adapter-apollo - sofa-serverless-adapter-dubbo2.6 diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/README.md b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/README.md deleted file mode 100644 index 1caa6c511..000000000 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/README.md +++ /dev/null @@ -1,1446 +0,0 @@ -# dubbo 2.6.x support -# 实际使用 -由于目前将Dubbo放入基座 而基座的class目前无法直接增强 -针对当前2.6版本支持需要覆盖对应的dubbo class -因此需要将对应的class复制到classpath中 一般都是按照原封不动的包名放入base模块 确保类加载器优先加载到我们的class从而增强dubbo2.6支持多classLoader - -# 背景 -目前我们将Dubbo放入到base基座之后 我们与此同时将biz中的dubbo组件去除 -但是我们发现Dubbo的ExtensionLoader目前是静态类 -其初次加载就已经稳定 这样无法根据classloader不同去不同的biz模块进行加载 - -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1698218715966-4e510ce8-4031-4b0e-b5c6-293c4dcfe140.png#averageHue=%230f0f0f&clientId=u8bee2931-a7cd-4&from=paste&height=684&id=uc16f2576&originHeight=684&originWidth=1495&originalType=binary&ratio=1&rotation=0&showTitle=false&size=218206&status=done&style=none&taskId=u0b300a31-daab-4212-8ea9-85f4e5d34f0&title=&width=1495) - -从而导致启动新biz的时候出现报错 - -# 思路 -考虑扩充ExtensionLoader当前版本支持不同classloader -这样可以根据ExtensionClassLoader使用不同的BizClassLoader而去重新加载对应资源 -# 改造 - -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1698218819626-c5a93c93-3b11-4e7f-a311-87655e085757.png#averageHue=%23646438&clientId=u8bee2931-a7cd-4&from=paste&height=506&id=uf0cd09e8&originHeight=506&originWidth=1422&originalType=binary&ratio=1&rotation=0&showTitle=false&size=171265&status=done&style=none&taskId=u935dc20e-8e5a-4de6-9c8e-dd6aec82457&title=&width=1422) -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1698218841564-f45a7168-ad17-4f19-b765-7b1b67cf93ee.png#averageHue=%2373633d&clientId=u8bee2931-a7cd-4&from=paste&height=551&id=u9b971db3&originHeight=551&originWidth=1651&originalType=binary&ratio=1&rotation=0&showTitle=false&size=226591&status=done&style=none&taskId=u3dfd5b70-fe18-4dfd-bed8-0f1115c2937&title=&width=1651) - -此时由于biz加载的时候会设置对应的Thread的ContextClassLoader -继而可以触发对应的SPI加载 - -``` -package com.alibaba.dubbo.common.extension; - -import com.alibaba.dubbo.common.Constants; -import com.alibaba.dubbo.common.URL; -import com.alibaba.dubbo.common.extension.support.ActivateComparator; -import com.alibaba.dubbo.common.logger.Logger; -import com.alibaba.dubbo.common.logger.LoggerFactory; -import com.alibaba.dubbo.common.utils.ConcurrentHashSet; -import com.alibaba.dubbo.common.utils.ConfigUtils; -import com.alibaba.dubbo.common.utils.Holder; -import com.alibaba.dubbo.common.utils.StringUtils; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.regex.Pattern; - -/** - * Load dubbo extensions - *
    - *
  • auto inject dependency extension
  • - *
  • auto wrap extension in wrapper
  • - *
  • default extension is an adaptive instance
  • - *
- * - * @see Service Provider in Java 5 - * @see com.alibaba.dubbo.common.extension.SPI - * @see com.alibaba.dubbo.common.extension.Adaptive - * @see com.alibaba.dubbo.common.extension.Activate - */ -public class ExtensionLoader { - - private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class); - - private static final String SERVICES_DIRECTORY = "META-INF/services/"; - - private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; - - private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/"; - - private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*"); - - private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap<>(); - //add by qixiaobo start - private static final ConcurrentMap, ExtensionLoader>> EXTENSION_LOADERS_SUPPORT_CLASSLOADER = new ConcurrentHashMap<>(); - static{ - EXTENSION_LOADERS_SUPPORT_CLASSLOADER.put(findClassLoader(),EXTENSION_LOADERS); - } - //add by qixiaobo end - private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap, Object>(); - - // ============================== - - private final Class type; - - private final ExtensionFactory objectFactory; - - private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap, String>(); - - private final Holder>> cachedClasses = new Holder>>(); - - private final Map cachedActivates = new ConcurrentHashMap(); - private final ConcurrentMap> cachedInstances = new ConcurrentHashMap>(); - private final Holder cachedAdaptiveInstance = new Holder(); - private volatile Class cachedAdaptiveClass = null; - private String cachedDefaultName; - private volatile Throwable createAdaptiveInstanceError; - - private Set> cachedWrapperClasses; - - private Map exceptions = new ConcurrentHashMap(); - - private ExtensionLoader(Class type) { - this.type = type; - objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); - } - - private static boolean withExtensionAnnotation(Class type) { - return type.isAnnotationPresent(SPI.class); - } - - @SuppressWarnings("unchecked") - public static ExtensionLoader getExtensionLoader(Class type) { - if (type == null) - throw new IllegalArgumentException("Extension type == null"); - if (!type.isInterface()) { - throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); - } - if (!withExtensionAnnotation(type)) { - throw new IllegalArgumentException("Extension type(" + type + - ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); - } - ClassLoader classLoader = findClassLoader(); - ConcurrentMap, ExtensionLoader> classExtensionLoaderConcurrentMap = EXTENSION_LOADERS_SUPPORT_CLASSLOADER.get(classLoader); - if (classExtensionLoaderConcurrentMap == null) { - EXTENSION_LOADERS_SUPPORT_CLASSLOADER.putIfAbsent(classLoader, new ConcurrentHashMap<>()); - classExtensionLoaderConcurrentMap = EXTENSION_LOADERS_SUPPORT_CLASSLOADER.get(classLoader); - } -// - ExtensionLoader loader = (ExtensionLoader) classExtensionLoaderConcurrentMap.get(type); - if (loader == null) { - EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)); - loader = (ExtensionLoader) EXTENSION_LOADERS.get(type); - } - return loader; - } - - private static ClassLoader findClassLoader() { - //add by qixiaobo start support classloader - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - if(classLoader!=null) return classLoader; - //add by qixiaobo end - return ExtensionLoader.class.getClassLoader(); - } - - public String getExtensionName(T extensionInstance) { - return getExtensionName(extensionInstance.getClass()); - } - - public String getExtensionName(Class extensionClass) { - return cachedNames.get(extensionClass); - } - - /** - * This is equivalent to {@code getActivateExtension(url, key, null)} - * - * @param url url - * @param key url parameter key which used to get extension point names - * @return extension list which are activated. - * @see #getActivateExtension(com.alibaba.dubbo.common.URL, String, String) - */ - public List getActivateExtension(URL url, String key) { - return getActivateExtension(url, key, null); - } - - /** - * This is equivalent to {@code getActivateExtension(url, values, null)} - * - * @param url url - * @param values extension point names - * @return extension list which are activated - * @see #getActivateExtension(com.alibaba.dubbo.common.URL, String[], String) - */ - public List getActivateExtension(URL url, String[] values) { - return getActivateExtension(url, values, null); - } - - /** - * This is equivalent to {@code getActivateExtension(url, url.getParameter(key).split(","), null)} - * - * @param url url - * @param key url parameter key which used to get extension point names - * @param group group - * @return extension list which are activated. - * @see #getActivateExtension(com.alibaba.dubbo.common.URL, String[], String) - */ - public List getActivateExtension(URL url, String key, String group) { - String value = url.getParameter(key); - return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group); - } - - /** - * Get activate extensions. - * - * @param url url - * @param values extension point names - * @param group group - * @return extension list which are activated - * @see com.alibaba.dubbo.common.extension.Activate - */ - public List getActivateExtension(URL url, String[] values, String group) { - List exts = new ArrayList(); - List names = values == null ? new ArrayList(0) : Arrays.asList(values); - if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) { - getExtensionClasses(); - for (Map.Entry entry : cachedActivates.entrySet()) { - String name = entry.getKey(); - Activate activate = entry.getValue(); - if (isMatchGroup(group, activate.group())) { - T ext = getExtension(name); - if (!names.contains(name) - && !names.contains(Constants.REMOVE_VALUE_PREFIX + name) - && isActive(activate, url)) { - exts.add(ext); - } - } - } - Collections.sort(exts, ActivateComparator.COMPARATOR); - } - List usrs = new ArrayList(); - for (int i = 0; i < names.size(); i++) { - String name = names.get(i); - if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX) - && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { - if (Constants.DEFAULT_KEY.equals(name)) { - if (!usrs.isEmpty()) { - exts.addAll(0, usrs); - usrs.clear(); - } - } else { - T ext = getExtension(name); - usrs.add(ext); - } - } - } - if (!usrs.isEmpty()) { - exts.addAll(usrs); - } - return exts; - } - - private boolean isMatchGroup(String group, String[] groups) { - if (group == null || group.length() == 0) { - return true; - } - if (groups != null && groups.length > 0) { - for (String g : groups) { - if (group.equals(g)) { - return true; - } - } - } - return false; - } - - private boolean isActive(Activate activate, URL url) { - String[] keys = activate.value(); - if (keys.length == 0) { - return true; - } - for (String key : keys) { - for (Map.Entry entry : url.getParameters().entrySet()) { - String k = entry.getKey(); - String v = entry.getValue(); - if ((k.equals(key) || k.endsWith("." + key)) - && ConfigUtils.isNotEmpty(v)) { - return true; - } - } - } - return false; - } - - /** - * Get extension's instance. Return null if extension is not found or is not initialized. Pls. note - * that this method will not trigger extension load. - *

- * In order to trigger extension load, call {@link #getExtension(String)} instead. - * - * @see #getExtension(String) - */ - @SuppressWarnings("unchecked") - public T getLoadedExtension(String name) { - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Extension name == null"); - Holder holder = cachedInstances.get(name); - if (holder == null) { - cachedInstances.putIfAbsent(name, new Holder()); - holder = cachedInstances.get(name); - } - return (T) holder.get(); - } - - /** - * Return the list of extensions which are already loaded. - *

- * Usually {@link #getSupportedExtensions()} should be called in order to get all extensions. - * - * @see #getSupportedExtensions() - */ - public Set getLoadedExtensions() { - return Collections.unmodifiableSet(new TreeSet(cachedInstances.keySet())); - } - - /** - * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException} - * will be thrown. - */ - @SuppressWarnings("unchecked") - public T getExtension(String name) { - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Extension name == null"); - if ("true".equals(name)) { - return getDefaultExtension(); - } - Holder holder = cachedInstances.get(name); - if (holder == null) { - cachedInstances.putIfAbsent(name, new Holder()); - holder = cachedInstances.get(name); - } - Object instance = holder.get(); - if (instance == null) { - synchronized (holder) { - instance = holder.get(); - if (instance == null) { - instance = createExtension(name); - holder.set(instance); - } - } - } - return (T) instance; - } - - /** - * Return default extension, return null if it's not configured. - */ - public T getDefaultExtension() { - getExtensionClasses(); - if (null == cachedDefaultName || cachedDefaultName.length() == 0 - || "true".equals(cachedDefaultName)) { - return null; - } - return getExtension(cachedDefaultName); - } - - public boolean hasExtension(String name) { - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Extension name == null"); - try { - this.getExtensionClass(name); - return true; - } catch (Throwable t) { - return false; - } - } - - public Set getSupportedExtensions() { - Map> clazzes = getExtensionClasses(); - return Collections.unmodifiableSet(new TreeSet(clazzes.keySet())); - } - - /** - * Return default extension name, return null if not configured. - */ - public String getDefaultExtensionName() { - getExtensionClasses(); - return cachedDefaultName; - } - - /** - * Register new extension via API - * - * @param name extension name - * @param clazz extension class - * @throws IllegalStateException when extension with the same name has already been registered. - */ - public void addExtension(String name, Class clazz) { - getExtensionClasses(); // load classes - - if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Input type " + - clazz + "not implement Extension " + type); - } - if (clazz.isInterface()) { - throw new IllegalStateException("Input type " + - clazz + "can not be interface!"); - } - - if (!clazz.isAnnotationPresent(Adaptive.class)) { - if (StringUtils.isBlank(name)) { - throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); - } - if (cachedClasses.get().containsKey(name)) { - throw new IllegalStateException("Extension name " + - name + " already existed(Extension " + type + ")!"); - } - - cachedNames.put(clazz, name); - cachedClasses.get().put(name, clazz); - } else { - if (cachedAdaptiveClass != null) { - throw new IllegalStateException("Adaptive Extension already existed(Extension " + type + ")!"); - } - - cachedAdaptiveClass = clazz; - } - } - - /** - * Replace the existing extension via API - * - * @param name extension name - * @param clazz extension class - * @throws IllegalStateException when extension to be placed doesn't exist - * @deprecated not recommended any longer, and use only when test - */ - @Deprecated - public void replaceExtension(String name, Class clazz) { - getExtensionClasses(); // load classes - - if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Input type " + - clazz + "not implement Extension " + type); - } - if (clazz.isInterface()) { - throw new IllegalStateException("Input type " + - clazz + "can not be interface!"); - } - - if (!clazz.isAnnotationPresent(Adaptive.class)) { - if (StringUtils.isBlank(name)) { - throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); - } - if (!cachedClasses.get().containsKey(name)) { - throw new IllegalStateException("Extension name " + - name + " not existed(Extension " + type + ")!"); - } - - cachedNames.put(clazz, name); - cachedClasses.get().put(name, clazz); - cachedInstances.remove(name); - } else { - if (cachedAdaptiveClass == null) { - throw new IllegalStateException("Adaptive Extension not existed(Extension " + type + ")!"); - } - - cachedAdaptiveClass = clazz; - cachedAdaptiveInstance.set(null); - } - } - - @SuppressWarnings("unchecked") - public T getAdaptiveExtension() { - Object instance = cachedAdaptiveInstance.get(); - if (instance == null) { - if (createAdaptiveInstanceError == null) { - synchronized (cachedAdaptiveInstance) { - instance = cachedAdaptiveInstance.get(); - if (instance == null) { - try { - instance = createAdaptiveExtension(); - cachedAdaptiveInstance.set(instance); - } catch (Throwable t) { - createAdaptiveInstanceError = t; - throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); - } - } - } - } else { - throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); - } - } - - return (T) instance; - } - - private IllegalStateException findException(String name) { - for (Map.Entry entry : exceptions.entrySet()) { - if (entry.getKey().toLowerCase().contains(name.toLowerCase())) { - return entry.getValue(); - } - } - StringBuilder buf = new StringBuilder("No such extension " + type.getName() + " by name " + name); - - - int i = 1; - for (Map.Entry entry : exceptions.entrySet()) { - if (i == 1) { - buf.append(", possible causes: "); - } - - buf.append("\r\n("); - buf.append(i++); - buf.append(") "); - buf.append(entry.getKey()); - buf.append(":\r\n"); - buf.append(StringUtils.toString(entry.getValue())); - } - return new IllegalStateException(buf.toString()); - } - - @SuppressWarnings("unchecked") - private T createExtension(String name) { - Class clazz = getExtensionClasses().get(name); - if (clazz == null) { - throw findException(name); - } - try { - T instance = (T) EXTENSION_INSTANCES.get(clazz); - if (instance == null) { - EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); - instance = (T) EXTENSION_INSTANCES.get(clazz); - } - injectExtension(instance); - Set> wrapperClasses = cachedWrapperClasses; - if (wrapperClasses != null && !wrapperClasses.isEmpty()) { - for (Class wrapperClass : wrapperClasses) { - instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); - } - } - return instance; - } catch (Throwable t) { - throw new IllegalStateException("Extension instance(name: " + name + ", class: " + - type + ") could not be instantiated: " + t.getMessage(), t); - } - } - - private T injectExtension(T instance) { - try { - if (objectFactory != null) { - for (Method method : instance.getClass().getMethods()) { - if (method.getName().startsWith("set") - && method.getParameterTypes().length == 1 - && Modifier.isPublic(method.getModifiers())) { - /** - * Check {@link DisableInject} to see if we need auto injection for this property - */ - if (method.getAnnotation(DisableInject.class) != null) { - continue; - } - Class pt = method.getParameterTypes()[0]; - try { - String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; - Object object = objectFactory.getExtension(pt, property); - if (object != null) { - method.invoke(instance, object); - } - } catch (Exception e) { - logger.error("fail to inject via method " + method.getName() - + " of interface " + type.getName() + ": " + e.getMessage(), e); - } - } - } - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - return instance; - } - - private Class getExtensionClass(String name) { - if (type == null) - throw new IllegalArgumentException("Extension type == null"); - if (name == null) - throw new IllegalArgumentException("Extension name == null"); - Class clazz = getExtensionClasses().get(name); - if (clazz == null) - throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!"); - return clazz; - } - - private Map> getExtensionClasses() { - Map> classes = cachedClasses.get(); - if (classes == null) { - synchronized (cachedClasses) { - classes = cachedClasses.get(); - if (classes == null) { - classes = loadExtensionClasses(); - cachedClasses.set(classes); - } - } - } - return classes; - } - - // synchronized in getExtensionClasses - private Map> loadExtensionClasses() { - final SPI defaultAnnotation = type.getAnnotation(SPI.class); - if (defaultAnnotation != null) { - String value = defaultAnnotation.value(); - if ((value = value.trim()).length() > 0) { - String[] names = NAME_SEPARATOR.split(value); - if (names.length > 1) { - throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() - + ": " + Arrays.toString(names)); - } - if (names.length == 1) cachedDefaultName = names[0]; - } - } - - Map> extensionClasses = new HashMap>(); - loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); - loadDirectory(extensionClasses, DUBBO_DIRECTORY); - loadDirectory(extensionClasses, SERVICES_DIRECTORY); - return extensionClasses; - } - - private void loadDirectory(Map> extensionClasses, String dir) { - String fileName = dir + type.getName(); - try { - Enumeration urls; - ClassLoader classLoader = findClassLoader(); - if (classLoader != null) { - urls = classLoader.getResources(fileName); - } else { - urls = ClassLoader.getSystemResources(fileName); - } - if (urls != null) { - while (urls.hasMoreElements()) { - java.net.URL resourceURL = urls.nextElement(); - loadResource(extensionClasses, classLoader, resourceURL); - } - } - } catch (Throwable t) { - logger.error("Exception when load extension class(interface: " + - type + ", description file: " + fileName + ").", t); - } - } - - private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8")); - try { - String line; - while ((line = reader.readLine()) != null) { - final int ci = line.indexOf('#'); - if (ci >= 0) line = line.substring(0, ci); - line = line.trim(); - if (line.length() > 0) { - try { - String name = null; - int i = line.indexOf('='); - if (i > 0) { - name = line.substring(0, i).trim(); - line = line.substring(i + 1).trim(); - } - if (line.length() > 0) { - loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); - } - } catch (Throwable t) { - IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t); - exceptions.put(line, e); - } - } - } - } finally { - reader.close(); - } - } catch (Throwable t) { - logger.error("Exception when load extension class(interface: " + - type + ", class file: " + resourceURL + ") in " + resourceURL, t); - } - } - - private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class clazz, String name) throws NoSuchMethodException { - if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Error when load extension class(interface: " + - type + ", class line: " + clazz.getName() + "), class " - + clazz.getName() + "is not subtype of interface."); - } - if (clazz.isAnnotationPresent(Adaptive.class)) { - if (cachedAdaptiveClass == null) { - cachedAdaptiveClass = clazz; - } else if (!cachedAdaptiveClass.equals(clazz)) { - throw new IllegalStateException("More than 1 adaptive class found: " - + cachedAdaptiveClass.getClass().getName() - + ", " + clazz.getClass().getName()); - } - } else if (isWrapperClass(clazz)) { - Set> wrappers = cachedWrapperClasses; - if (wrappers == null) { - cachedWrapperClasses = new ConcurrentHashSet>(); - wrappers = cachedWrapperClasses; - } - wrappers.add(clazz); - } else { - clazz.getConstructor(); - if (name == null || name.length() == 0) { - name = findAnnotationName(clazz); - if (name.length() == 0) { - throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); - } - } - String[] names = NAME_SEPARATOR.split(name); - if (names != null && names.length > 0) { - Activate activate = clazz.getAnnotation(Activate.class); - if (activate != null) { - cachedActivates.put(names[0], activate); - } - for (String n : names) { - if (!cachedNames.containsKey(clazz)) { - cachedNames.put(clazz, n); - } - Class c = extensionClasses.get(n); - if (c == null) { - extensionClasses.put(n, clazz); - } else if (c != clazz) { - throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); - } - } - } - } - } - - private boolean isWrapperClass(Class clazz) { - try { - clazz.getConstructor(type); - return true; - } catch (NoSuchMethodException e) { - return false; - } - } - - @SuppressWarnings("deprecation") - private String findAnnotationName(Class clazz) { - com.alibaba.dubbo.common.Extension extension = clazz.getAnnotation(com.alibaba.dubbo.common.Extension.class); - if (extension == null) { - String name = clazz.getSimpleName(); - if (name.endsWith(type.getSimpleName())) { - name = name.substring(0, name.length() - type.getSimpleName().length()); - } - return name.toLowerCase(); - } - return extension.value(); - } - - @SuppressWarnings("unchecked") - private T createAdaptiveExtension() { - try { - return injectExtension((T) getAdaptiveExtensionClass().newInstance()); - } catch (Exception e) { - throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); - } - } - - private Class getAdaptiveExtensionClass() { - getExtensionClasses(); - if (cachedAdaptiveClass != null) { - return cachedAdaptiveClass; - } - return cachedAdaptiveClass = createAdaptiveExtensionClass(); - } - - private Class createAdaptiveExtensionClass() { - String code = createAdaptiveExtensionClassCode(); - ClassLoader classLoader = findClassLoader(); - com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); - return compiler.compile(code, classLoader); - } - - private String createAdaptiveExtensionClassCode() { - StringBuilder codeBuilder = new StringBuilder(); - Method[] methods = type.getMethods(); - boolean hasAdaptiveAnnotation = false; - for (Method m : methods) { - if (m.isAnnotationPresent(Adaptive.class)) { - hasAdaptiveAnnotation = true; - break; - } - } - // no need to generate adaptive class since there's no adaptive method found. - if (!hasAdaptiveAnnotation) - throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!"); - - codeBuilder.append("package ").append(type.getPackage().getName()).append(";"); - codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";"); - codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {"); - - for (Method method : methods) { - Class rt = method.getReturnType(); - Class[] pts = method.getParameterTypes(); - Class[] ets = method.getExceptionTypes(); - - Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); - StringBuilder code = new StringBuilder(512); - if (adaptiveAnnotation == null) { - code.append("throw new UnsupportedOperationException(\"method ") - .append(method.toString()).append(" of interface ") - .append(type.getName()).append(" is not adaptive method!\");"); - } else { - int urlTypeIndex = -1; - for (int i = 0; i < pts.length; ++i) { - if (pts[i].equals(URL.class)) { - urlTypeIndex = i; - break; - } - } - // found parameter in URL type - if (urlTypeIndex != -1) { - // Null Point check - String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");", - urlTypeIndex); - code.append(s); - - s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); - code.append(s); - } - // did not find parameter in URL type - else { - String attribMethod = null; - - // find URL getter method - LBL_PTS: - for (int i = 0; i < pts.length; ++i) { - Method[] ms = pts[i].getMethods(); - for (Method m : ms) { - String name = m.getName(); - if ((name.startsWith("get") || name.length() > 3) - && Modifier.isPublic(m.getModifiers()) - && !Modifier.isStatic(m.getModifiers()) - && m.getParameterTypes().length == 0 - && m.getReturnType() == URL.class) { - urlTypeIndex = i; - attribMethod = name; - break LBL_PTS; - } - } - } - if (attribMethod == null) { - throw new IllegalStateException("fail to create adaptive class for interface " + type.getName() - + ": not found url parameter or url attribute in parameters of method " + method.getName()); - } - - // Null point check - String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");", - urlTypeIndex, pts[urlTypeIndex].getName()); - code.append(s); - s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");", - urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod); - code.append(s); - - s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod); - code.append(s); - } - - String[] value = adaptiveAnnotation.value(); - // value is not set, use the value generated from class name as the key - if (value.length == 0) { - char[] charArray = type.getSimpleName().toCharArray(); - StringBuilder sb = new StringBuilder(128); - for (int i = 0; i < charArray.length; i++) { - if (Character.isUpperCase(charArray[i])) { - if (i != 0) { - sb.append("."); - } - sb.append(Character.toLowerCase(charArray[i])); - } else { - sb.append(charArray[i]); - } - } - value = new String[]{sb.toString()}; - } - - boolean hasInvocation = false; - for (int i = 0; i < pts.length; ++i) { - if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) { - // Null Point check - String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i); - code.append(s); - s = String.format("\nString methodName = arg%d.getMethodName();", i); - code.append(s); - hasInvocation = true; - break; - } - } - - String defaultExtName = cachedDefaultName; - String getNameCode = null; - for (int i = value.length - 1; i >= 0; --i) { - if (i == value.length - 1) { - if (null != defaultExtName) { - if (!"protocol".equals(value[i])) - if (hasInvocation) - getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); - else - getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName); - else - getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName); - } else { - if (!"protocol".equals(value[i])) - if (hasInvocation) - getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); - else - getNameCode = String.format("url.getParameter(\"%s\")", value[i]); - else - getNameCode = "url.getProtocol()"; - } - } else { - if (!"protocol".equals(value[i])) - if (hasInvocation) - getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); - else - getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode); - else - getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode); - } - } - code.append("\nString extName = ").append(getNameCode).append(";"); - // check extName == null? - String s = String.format("\nif(extName == null) " + - "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");", - type.getName(), Arrays.toString(value)); - code.append(s); - - s = String.format("\n%s extension = (% 0) { - codeBuilder.append(", "); - } - codeBuilder.append(pts[i].getCanonicalName()); - codeBuilder.append(" "); - codeBuilder.append("arg").append(i); - } - codeBuilder.append(")"); - if (ets.length > 0) { - codeBuilder.append(" throws "); - for (int i = 0; i < ets.length; i++) { - if (i > 0) { - codeBuilder.append(", "); - } - codeBuilder.append(ets[i].getCanonicalName()); - } - } - codeBuilder.append(" {"); - codeBuilder.append(code.toString()); - codeBuilder.append("\n}"); - } - codeBuilder.append("\n}"); - if (logger.isDebugEnabled()) { - logger.debug(codeBuilder.toString()); - } - return codeBuilder.toString(); - } - - @Override - public String toString() { - return this.getClass().getName() + "[" + type.getName() + "]"; - } - -} - -``` -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1698218917861-37b9d42d-b9f8-47df-9046-7edb34c62bc8.png#averageHue=%233b3f42&clientId=u8bee2931-a7cd-4&from=paste&height=425&id=ud6a171c1&originHeight=425&originWidth=508&originalType=binary&ratio=1&rotation=0&showTitle=false&size=42315&status=done&style=none&taskId=u3a538066-041e-4225-b8f3-c4f9002f3b3&title=&width=508) - -dubbo版本2.6.4 -dubbo多biz加载后启动出现一些classloader导致的ClassNotFoundException - -``` -2023-10-24 13:38:34,627 [WARN] [NettyServerWorker-9-9] c.a.d.r.p.d.DecodeableRpcInvocation:? [] [DUBBO] Decode argument failed: com.f6car.merchant.so.org.TgOrgGroupMemberSo, dubbo version: 2.6.12, current host: 172.27.121.46 -java.lang.ClassNotFoundException: com.f6car.merchant.so.org.TgOrgGroupMemberSo - at java.net.URLClassLoader.findClass(URLClassLoader.java:387) - at java.lang.ClassLoader.loadClass(ClassLoader.java:418) - at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352) - at java.lang.ClassLoader.loadClass(ClassLoader.java:351) - at java.lang.Class.forName0(Native Method) - at java.lang.Class.forName(Class.java:348) - at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:758) - at com.alibaba.dubbo.common.utils.SerialDetector.resolveClass(SerialDetector.java:67) - at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1986) - at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1850) - at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2160) - at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1667) - at java.io.ObjectInputStream.readObject(ObjectInputStream.java:503) - at java.io.ObjectInputStream.readObject(ObjectInputStream.java:461) - at com.alibaba.dubbo.common.serialize.java.JavaObjectInput.readObject(JavaObjectInput.java:70) - at com.alibaba.dubbo.common.serialize.java.JavaObjectInput.readObject(JavaObjectInput.java:77) - at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:122) - at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:72) - at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.decodeBody(DubboCodec.java:138) - at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:126) - at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:86) - at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.decode(DubboCountCodec.java:46) - at com.alibaba.dubbo.remoting.transport.netty4.NettyCodecAdapter$InternalDecoder.decode(NettyCodecAdapter.java:95) - at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:529) - at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:468) -``` - -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1698398961430-bb632e4e-d008-4cc4-a4dc-95a634a8ab9b.png#averageHue=%23e6e6e6&clientId=u9afc399f-ea80-4&from=paste&height=417&id=u6286cb3e&originHeight=417&originWidth=866&originalType=binary&ratio=1&rotation=0&showTitle=false&size=118101&status=done&style=none&taskId=u07169dd2-776b-43fb-b490-8098407824c&title=&width=866) - -可以看到这个明显是base的classLoader -那么为何dubbo暴露了端口之后进行正反序列化的时候直接使用当前的classLoader呢? -针对正常非biz隔离的场景是OK的 -针对biz存在隔离的case 除非反序列化场景时使用了独立的bizClassLoader -但是问题是我如何知道当前场景下是应该调用哪一个biz呢??? - -```java -@Override -public Object decode(Channel channel, InputStream input) throws IOException { - ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType) - .deserialize(channel.getUrl(), input); - this.put(SERIALIZATION_ID_KEY, serializationType); - - String dubboVersion = in.readUTF(); - request.setVersion(dubboVersion); - setAttachment(Constants.DUBBO_VERSION_KEY, dubboVersion); - - String path = in.readUTF(); - setAttachment(Constants.PATH_KEY, path); - String version = in.readUTF(); - setAttachment(Constants.VERSION_KEY, version); - - setMethodName(in.readUTF()); - try { - if (Boolean.parseBoolean(System.getProperty(SERIALIZATION_SECURITY_CHECK_KEY, "false"))) { - CodecSupport.checkSerialization(path, version, serializationType); - } - - Object[] args; - Class[] pts; - String desc = in.readUTF(); - if (desc.length() == 0) { - pts = DubboCodec.EMPTY_CLASS_ARRAY; - args = DubboCodec.EMPTY_OBJECT_ARRAY; - } else { - pts = ReflectUtils.desc2classArray(desc); - args = new Object[pts.length]; - for (int i = 0; i < args.length; i++) { - try { - args[i] = in.readObject(pts[i]); - } catch (Exception e) { - if (log.isWarnEnabled()) { - log.warn("Decode argument failed: " + e.getMessage(), e); - } - } - } - } - setParameterTypes(pts); - - Map map = (Map) in.readObject(Map.class); - if (map != null && map.size() > 0) { - Map attachment = getAttachments(); - if (attachment == null) { - attachment = new HashMap(); - } - attachment.putAll(map); - setAttachments(attachment); - } - //decode argument ,may be callback - for (int i = 0; i < args.length; i++) { - args[i] = decodeInvocationArgument(channel, this, pts, i, args[i]); - } - - setArguments(args); - - } catch (ClassNotFoundException e) { - throw new IOException(StringUtils.toString("Read invocation data failed.", e)); - } finally { - if (in instanceof Cleanable) { - ((Cleanable) in).cleanup(); - } - } - return this; -} -``` - -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1698399359953-e9745f87-c4cb-4e46-aa79-8ba355aec69f.png#averageHue=%232f2f2e&clientId=u9afc399f-ea80-4&from=paste&height=324&id=uc443f7ac&originHeight=324&originWidth=1226&originalType=binary&ratio=1&rotation=0&showTitle=false&size=64184&status=done&style=none&taskId=u958d86b2-04e1-4409-a75c-9b2158d31e6&title=&width=1226) - -也就是在这块需要根据相关的Path自动要解析出对应的Biz 然后根据不同的biz的classLoader进行解析【因此最好在注册rpc的时候记录相关bizClassLoader的关系 后续可以反向使用】 -为了规避此问题目前将dubbo放入biz来进行规避 -不同dubbo的biz会出现端口冲突问题 指定dubbo端口为-1即可 - -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1698399510036-576be94b-6931-4abb-bfcc-f366931b458e.png#averageHue=%23302f2f&clientId=ua7a818cb-964d-4&from=paste&height=373&id=u6ec02f46&originHeight=373&originWidth=1304&originalType=binary&ratio=1&rotation=0&showTitle=false&size=98371&status=done&style=none&taskId=uce2b5b3e-742a-4538-8d96-fbb20342baa&title=&width=1304) - - - -多biz支持后 由于Spring的上下文会绑定到支持Dubbo的SPI的注入 -因此不同biz的容器务必隔离 避免出现bean泄露 - -```java -/* - * 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.alibaba.dubbo.config.spring.extension; - -import com.alibaba.dubbo.common.extension.ExtensionFactory; -import com.alibaba.dubbo.common.logger.Logger; -import com.alibaba.dubbo.common.logger.LoggerFactory; -import com.alibaba.dubbo.common.utils.ConcurrentHashSet; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.context.ApplicationContext; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * SpringExtensionFactory - */ -public class SpringExtensionFactory implements ExtensionFactory { - private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class); - - private static final Map> contextsWithClassLoader = new ConcurrentHashMap<>(); - - public static void addApplicationContext(ApplicationContext context) { - getContexts().add(context); - } - - public static void removeApplicationContext(ApplicationContext context) { - getContexts().remove(context); - } - - // currently for test purpose - public static void clearContexts() { - - getContexts().clear(); - } - - @Override - @SuppressWarnings("unchecked") - public T getExtension(Class type, String name) { - for (ApplicationContext context : getContexts()) { - if (context.containsBean(name)) { - Object bean = context.getBean(name); - if (type.isInstance(bean)) { - return (T) bean; - } - } - } - - logger.warn("No spring extension(bean) named:" + name + ", try to find an extension(bean) of type " + type.getName()); - - for (ApplicationContext context : getContexts()) { - try { - return context.getBean(type); - } catch (NoUniqueBeanDefinitionException multiBeanExe) { - throw multiBeanExe; - } catch (NoSuchBeanDefinitionException noBeanExe) { - if (logger.isDebugEnabled()) { - logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe); - } - } - } - - logger.warn("No spring extension(bean) named:" + name + ", type:" + type.getName() + " found, stop get bean."); - - return null; - } - - private static ClassLoader findClassLoader() { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - if (classLoader != null) return classLoader; - return SpringExtensionFactory.class.getClassLoader(); - } - private static Set getContexts(){ - ClassLoader classLoader = findClassLoader(); - Set contexts = null; - if ((contexts = contextsWithClassLoader.get(classLoader)) == null) { - contextsWithClassLoader.put(classLoader, new ConcurrentHashSet<>()); - contexts = contextsWithClassLoader.get(classLoader); - }; - return contexts; - } - -} - -``` - - -dubbo放入base之后dubbo 由于一部分代码是static执行的 导致部分场景下不同biz的初始化的生命周期不一致 -导致我们部分场景下出现异常 -举例如下 我们的ReferenceConfig在初次加载时会触发 - -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1699266065661-2f31de12-b218-44da-97d9-d6477eaf1f87.png#averageHue=%231f2022&clientId=u898e26e6-1421-4&from=paste&height=498&id=u09ba332b&originHeight=498&originWidth=1359&originalType=binary&ratio=1&rotation=0&showTitle=false&size=115077&status=done&style=none&taskId=u7438f359-df95-43d0-aeb1-4b6064d16d6&title=&width=1359) - -这样在不同的biz时由于已经加载完毕后因此无法自动触发导致缺少部分初始化场景 需要补齐 - -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1699266024533-6613a574-3a17-48c1-8009-065ae4520587.png#averageHue=%23232427&clientId=u898e26e6-1421-4&from=paste&height=575&id=udffabb6f&originHeight=575&originWidth=1476&originalType=binary&ratio=1&rotation=0&showTitle=false&size=182369&status=done&style=none&taskId=udd60c607-7e0a-4119-a116-ef52159ab3d&title=&width=1476) - - -目前在同一个端口下 dubbo接收到请求无法判断到当前模块 -已知我们使用的是原生的java序列化方式 -这样自然需要想办法来找到对应的模块 从而取到对应的classLoader -经过sofa社区的尚之同学的建议 给到了如下方案 - - -```java -/* - * Ant Group - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package com.alibaba.dubbo.demo.provider; - -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectStreamClass; -import java.io.OutputStream; -import java.io.StreamCorruptedException; -import java.lang.reflect.Field; -import java.lang.reflect.Proxy; - -import com.alibaba.dubbo.common.URL; -import com.alibaba.dubbo.common.serialize.ObjectInput; -import com.alibaba.dubbo.common.serialize.ObjectOutput; -import com.alibaba.dubbo.common.serialize.java.JavaObjectInput; -import com.alibaba.dubbo.common.serialize.java.JavaObjectOutput; -import com.alibaba.dubbo.common.serialize.java.JavaSerialization; -import com.alibaba.dubbo.config.model.ApplicationModel; -import com.alibaba.dubbo.config.spring.ServiceBean; - -import org.springframework.context.ApplicationContext; - -/** - * - * @author syd - * @version ClassLoaderJavaSerialization.java, v 0.1 2023年10月28日 19:18 syd - */ -public class ClassLoaderJavaSerialization extends JavaSerialization { - - @Override - public byte getContentTypeId() { - return 3; - } - - @Override - public String getContentType() { - return "x-application/java"; - } - - @Override - public ObjectOutput serialize(URL url, OutputStream out) throws IOException { - return new JavaObjectOutput(out); - } - - @Override - public ObjectInput deserialize(URL url, InputStream is) throws IOException { - ClassLoader classLoader = getClassLoaderByUrl(url); - return new JavaObjectInput(new ClassLoaderObjectInputStream(classLoader, is)); - } - - private ClassLoader getClassLoaderByUrl(URL url) { - ServiceBean serviceBean = (ServiceBean) ApplicationModel.getProviderModel(url.getServiceKey()).getMetadata(); - try { - Field field = ServiceBean.class.getField("applicationContext"); - ApplicationContext applicationContext = (ApplicationContext) field.get(serviceBean); - return applicationContext.getClassLoader(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static class ClassLoaderObjectInputStream extends ObjectInputStream { - /** The class loader to use. */ - private final ClassLoader classLoader; - - /** - * Constructs a new ClassLoaderObjectInputStream. - * - * @param classLoader the ClassLoader from which classes should be loaded - * @param inputStream the InputStream to work on - * @throws IOException in case of an I/O error - * @throws StreamCorruptedException if the stream is corrupted - */ - public ClassLoaderObjectInputStream( - ClassLoader classLoader, InputStream inputStream) - throws IOException, StreamCorruptedException { - super(inputStream); - this.classLoader = classLoader; - } - - /** - * Resolve a class specified by the descriptor using the - * specified ClassLoader or the super ClassLoader. - * - * @param objectStreamClass descriptor of the class - * @return the Class object described by the ObjectStreamClass - * @throws IOException in case of an I/O error - * @throws ClassNotFoundException if the Class cannot be found - */ - @Override - protected Class resolveClass(ObjectStreamClass objectStreamClass) - throws IOException, ClassNotFoundException { - - Class clazz = Class.forName(objectStreamClass.getName(), false, classLoader); - - if (clazz != null) { - // the classloader knows of the class - return clazz; - } else { - // classloader knows not of class, let the super classloader do it - return super.resolveClass(objectStreamClass); - } - } - - /** - * Create a proxy class that implements the specified interfaces using - * the specified ClassLoader or the super ClassLoader. - * - * @param interfaces the interfaces to implement - * @return a proxy class implementing the interfaces - * @throws IOException in case of an I/O error - * @throws ClassNotFoundException if the Class cannot be found - * @see java.io.ObjectInputStream#resolveProxyClass(java.lang.String[]) - * @since Commons IO 2.1 - */ - @Override - protected Class resolveProxyClass(String[] interfaces) throws IOException, - ClassNotFoundException { - Class[] interfaceClasses = new Class[interfaces.length]; - for (int i = 0; i < interfaces.length; i++) { - interfaceClasses[i] = Class.forName(interfaces[i], false, classLoader); - } - try { - return Proxy.getProxyClass(classLoader, interfaceClasses); - } catch (IllegalArgumentException e) { - return super.resolveProxyClass(interfaces); - } - } - } -} -``` - -我们发现为了使该方案生效 我们必须修改Java序列化 -同时其他不支持多模块的dubbo仍然保持Java的名称 而不是新建新的SPI扩展 -经过研究验证确认我们发现 - -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1699262773133-6db151d9-abc0-4c61-b4fb-7d1b48a70722.png#averageHue=%23636a61&clientId=u9cade2e4-accb-4&from=paste&height=674&id=u0859502a&originHeight=674&originWidth=1120&originalType=binary&ratio=1&rotation=0&showTitle=false&size=320040&status=done&style=none&taskId=u7354dcef-1b23-4559-83f6-ce08a2c6330&title=&width=1120) - -原来url中携带的信息和具体的invocation可能是不匹配的 -究其原因是dubbo会作成dubbo的链接有2类,第一类是共享连接。consumer&每一个provider实例有一个多服务共享的连接。第二类是独享连接,consumer&每一个provider实例的每一个暴露的服务有独立的链接。 -因此我们需要的是要在invocation中获取到对应的path - -因此我们需要复写DecodeableRpcInvocation - -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1699263362261-24276699-018e-4fe8-8dae-473064b1ef8c.png#averageHue=%2326272a&clientId=u9cade2e4-accb-4&from=paste&height=597&id=ueea820e0&originHeight=597&originWidth=1315&originalType=binary&ratio=1&rotation=0&showTitle=false&size=191220&status=done&style=none&taskId=u9e8da21b-5ced-4187-9dde-5e2ab6113eb&title=&width=1315) - -这里有个额外需要注意的点是 - -```java -package com.alibaba.dubbo.common.serialize.java; - -import com.alibaba.dubbo.common.serialize.nativejava.NativeJavaObjectInput; - -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.lang.reflect.Type; - -public class ClassLoaderJavaObjectInput extends NativeJavaObjectInput { - public final static int MAX_BYTE_ARRAY_LENGTH = 8 * 1024 * 1024; - - public ClassLoaderJavaObjectInput(InputStream is) throws IOException { - super((ObjectInputStream)(is instanceof ObjectInputStream ? is : new ObjectInputStream(is))); - } - - @Override - public byte[] readBytes() throws IOException { - int len = getObjectInputStream().readInt(); - if (len < 0) - return null; - if (len == 0) - return new byte[0]; - if (len > MAX_BYTE_ARRAY_LENGTH) - throw new IOException("Byte array length too large. " + len); - - byte[] b = new byte[len]; - getObjectInputStream().readFully(b); - return b; - } - - @Override - public String readUTF() throws IOException { - int len = getObjectInputStream().readInt(); - if (len < 0) - return null; - - return getObjectInputStream().readUTF(); - } - - @Override - public Object readObject() throws IOException, ClassNotFoundException { - byte b = getObjectInputStream().readByte(); - if (b == 0) - return null; - - return getObjectInputStream().readObject(); - } - - @Override - @SuppressWarnings("unchecked") - public T readObject(Class cls) throws IOException, - ClassNotFoundException { - return (T) readObject(); - } - - @Override - @SuppressWarnings("unchecked") - public T readObject(Class cls, Type type) throws IOException, ClassNotFoundException { - return (T) readObject(); - } - - public InputStream getInputStream(){ - return getObjectInputStream(); - } - -} - -``` - -![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1699263409159-4bc365de-877f-429c-8a81-123d23328a36.png#averageHue=%23202124&clientId=u9cade2e4-accb-4&from=paste&height=161&id=u2ddc141f&originHeight=161&originWidth=1051&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30124&status=done&style=none&taskId=ud5663a6e-def8-4ac0-9d9d-0e3bc8d2f63&title=&width=1051)![image.png](https://cdn.nlark.com/yuque/0/2023/png/145710/1699263420045-b82eded9-7d1a-4757-a6f3-ffb48ed282e9.png#averageHue=%231f2124&clientId=u9cade2e4-accb-4&from=paste&height=296&id=ud1663c18&originHeight=296&originWidth=817&originalType=binary&ratio=1&rotation=0&showTitle=false&size=53230&status=done&style=none&taskId=u6b26ebe9-3404-491f-b7b8-8d2b93e8845&title=&width=817) - -注意基类同时支持inputStream和ObjectInPutStream 如果不强行转换会走到错误的构造函数 从而导致流被破坏 \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/ClassLoaderUtil.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/ClassLoaderUtil.java deleted file mode 100644 index eadff266c..000000000 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/ClassLoaderUtil.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.alibaba.dubbo.common; - -import java.lang.reflect.Field; - -import org.springframework.context.ApplicationContext; - -import com.alibaba.dubbo.common.logger.Logger; -import com.alibaba.dubbo.common.logger.LoggerFactory; -import com.alibaba.dubbo.config.model.ApplicationModel; -import com.alibaba.dubbo.config.model.ProviderModel; -import com.alibaba.dubbo.config.spring.ReferenceBean; -import com.alibaba.dubbo.config.spring.ServiceBean; - -public class ClassLoaderUtil { - static Field serviceBeanApplicationContextField; - static Field referenceBeanApplicationContextField; - private static final Logger log = LoggerFactory.getLogger(ClassLoaderUtil.class); - - static { - try { - serviceBeanApplicationContextField = ServiceBean.class - .getDeclaredField("applicationContext"); - serviceBeanApplicationContextField.setAccessible(true); - referenceBeanApplicationContextField = ReferenceBean.class - .getDeclaredField("applicationContext"); - referenceBeanApplicationContextField.setAccessible(true); - } catch (NoSuchFieldException e) { - throw new RuntimeException(e); - } - } - - public static ClassLoader getClassLoaderByPath(String path) { - ProviderModel providerModel = null; - for (ProviderModel iter : ApplicationModel.allProviderModels()) { - if (iter.getServiceName().contains(path)) { - providerModel = iter; - break; - } - } - if (providerModel == null) { - log.warn("can not find classloader by path:" + path); - return ClassLoader.getSystemClassLoader(); - } - ServiceBean serviceBean = (ServiceBean) providerModel.getMetadata(); - try { - ApplicationContext applicationContext = (ApplicationContext) serviceBeanApplicationContextField - .get(serviceBean); - return applicationContext.getClassLoader(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - -} diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/extension/ExtensionLoader.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/extension/ExtensionLoader.java deleted file mode 100644 index 2165fa14c..000000000 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/extension/ExtensionLoader.java +++ /dev/null @@ -1,1028 +0,0 @@ -/* - * 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.alibaba.dubbo.common.extension; - -import com.alibaba.dubbo.common.Constants; -import com.alibaba.dubbo.common.URL; -import com.alibaba.dubbo.common.extension.support.ActivateComparator; -import com.alibaba.dubbo.common.logger.Logger; -import com.alibaba.dubbo.common.logger.LoggerFactory; -import com.alibaba.dubbo.common.utils.ConcurrentHashSet; -import com.alibaba.dubbo.common.utils.ConfigUtils; -import com.alibaba.dubbo.common.utils.Holder; -import com.alibaba.dubbo.common.utils.StringUtils; -import com.alibaba.dubbo.rpc.Protocol; -import com.alibaba.dubbo.rpc.ProxyFactory; -import com.alibaba.dubbo.rpc.cluster.Cluster; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.regex.Pattern; - -/** - * Load dubbo extensions - *
    - *
  • auto inject dependency extension
  • - *
  • auto wrap extension in wrapper
  • - *
  • default extension is an adaptive instance
  • - *
- * - * @see Service Provider in Java 5 - * @see SPI - * @see Adaptive - * @see Activate - */ -public class ExtensionLoader { - - private static final Logger logger = LoggerFactory - .getLogger(ExtensionLoader.class); - - private static final String SERVICES_DIRECTORY = "META-INF/services/"; - - private static final String DUBBO_DIRECTORY = "META-INF/dubbo/"; - - private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY - + "internal/"; - - private static final Pattern NAME_SEPARATOR = Pattern - .compile("\\s*[,]+\\s*"); - - private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap<>(); - private static final ConcurrentMap, ExtensionLoader>> EXTENSION_LOADERS_SUPPORT_CLASSLOADER = new ConcurrentHashMap<>(); - - static { - EXTENSION_LOADERS_SUPPORT_CLASSLOADER.put(findClassLoader(), EXTENSION_LOADERS); - } - - private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap, Object>(); - - // ============================== - - private final Class type; - - private final ExtensionFactory objectFactory; - - private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap, String>(); - - private final Holder>> cachedClasses = new Holder>>(); - - private final Map cachedActivates = new ConcurrentHashMap(); - private final ConcurrentMap> cachedInstances = new ConcurrentHashMap>(); - private final Holder cachedAdaptiveInstance = new Holder(); - private volatile Class cachedAdaptiveClass = null; - private String cachedDefaultName; - private volatile Throwable createAdaptiveInstanceError; - - private Set> cachedWrapperClasses; - - private Map exceptions = new ConcurrentHashMap(); - - private ExtensionLoader(Class type) { - this.type = type; - objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader - .getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); - } - - private static boolean withExtensionAnnotation(Class type) { - return type.isAnnotationPresent(SPI.class); - } - - @SuppressWarnings("unchecked") - public static ExtensionLoader getExtensionLoader(Class type) { - if (type == null) - throw new IllegalArgumentException("Extension type == null"); - if (!type.isInterface()) { - throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); - } - if (!withExtensionAnnotation(type)) { - throw new IllegalArgumentException("Extension type(" + type - + ") is not extension, because WITHOUT @" - + SPI.class.getSimpleName() + " Annotation!"); - } - ClassLoader classLoader = findClassLoader(); - ConcurrentMap, ExtensionLoader> classExtensionLoaderConcurrentMap = EXTENSION_LOADERS_SUPPORT_CLASSLOADER - .get(classLoader); - if (classExtensionLoaderConcurrentMap == null) { - EXTENSION_LOADERS_SUPPORT_CLASSLOADER.putIfAbsent(classLoader, - new ConcurrentHashMap<>()); - classExtensionLoaderConcurrentMap = EXTENSION_LOADERS_SUPPORT_CLASSLOADER - .get(classLoader); - //TO avoid static init only once like ReferenceConfig - ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); - - ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension(); - - ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); - } - // - ExtensionLoader loader = (ExtensionLoader) classExtensionLoaderConcurrentMap - .get(type); - if (loader == null) { - classExtensionLoaderConcurrentMap.putIfAbsent(type, new ExtensionLoader(type)); - loader = (ExtensionLoader) classExtensionLoaderConcurrentMap.get(type); - } - return loader; - } - - private static ClassLoader findClassLoader() { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - if (classLoader != null) - return classLoader; - return ExtensionLoader.class.getClassLoader(); - } - - public String getExtensionName(T extensionInstance) { - return getExtensionName(extensionInstance.getClass()); - } - - public String getExtensionName(Class extensionClass) { - return cachedNames.get(extensionClass); - } - - /** - * This is equivalent to {@code getActivateExtension(url, key, null)} - * - * @param url url - * @param key url parameter key which used to get extension point names - * @return extension list which are activated. - * @see #getActivateExtension(URL, String, String) - */ - public List getActivateExtension(URL url, String key) { - return getActivateExtension(url, key, null); - } - - /** - * This is equivalent to {@code getActivateExtension(url, values, null)} - * - * @param url url - * @param values extension point names - * @return extension list which are activated - * @see #getActivateExtension(URL, String[], String) - */ - public List getActivateExtension(URL url, String[] values) { - return getActivateExtension(url, values, null); - } - - /** - * This is equivalent to {@code getActivateExtension(url, url.getParameter(key).split(","), null)} - * - * @param url url - * @param key url parameter key which used to get extension point names - * @param group group - * @return extension list which are activated. - * @see #getActivateExtension(URL, String[], String) - */ - public List getActivateExtension(URL url, String key, String group) { - String value = url.getParameter(key); - return getActivateExtension(url, value == null || value.length() == 0 ? null - : Constants.COMMA_SPLIT_PATTERN.split(value), group); - } - - /** - * Get activate extensions. - * - * @param url url - * @param values extension point names - * @param group group - * @return extension list which are activated - * @see Activate - */ - public List getActivateExtension(URL url, String[] values, String group) { - List exts = new ArrayList(); - List names = values == null ? new ArrayList(0) : Arrays.asList(values); - if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) { - getExtensionClasses(); - for (Map.Entry entry : cachedActivates.entrySet()) { - String name = entry.getKey(); - Activate activate = entry.getValue(); - if (isMatchGroup(group, activate.group())) { - T ext = getExtension(name); - if (!names.contains(name) - && !names.contains(Constants.REMOVE_VALUE_PREFIX + name) - && isActive(activate, url)) { - exts.add(ext); - } - } - } - Collections.sort(exts, ActivateComparator.COMPARATOR); - } - List usrs = new ArrayList(); - for (int i = 0; i < names.size(); i++) { - String name = names.get(i); - if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX) - && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { - if (Constants.DEFAULT_KEY.equals(name)) { - if (!usrs.isEmpty()) { - exts.addAll(0, usrs); - usrs.clear(); - } - } else { - T ext = getExtension(name); - usrs.add(ext); - } - } - } - if (!usrs.isEmpty()) { - exts.addAll(usrs); - } - return exts; - } - - private boolean isMatchGroup(String group, String[] groups) { - if (group == null || group.length() == 0) { - return true; - } - if (groups != null && groups.length > 0) { - for (String g : groups) { - if (group.equals(g)) { - return true; - } - } - } - return false; - } - - private boolean isActive(Activate activate, URL url) { - String[] keys = activate.value(); - if (keys.length == 0) { - return true; - } - for (String key : keys) { - for (Map.Entry entry : url.getParameters().entrySet()) { - String k = entry.getKey(); - String v = entry.getValue(); - if ((k.equals(key) || k.endsWith("." + key)) && ConfigUtils.isNotEmpty(v)) { - return true; - } - } - } - return false; - } - - /** - * Get extension's instance. Return null if extension is not found or is not initialized. Pls. note - * that this method will not trigger extension load. - *

- * In order to trigger extension load, call {@link #getExtension(String)} instead. - * - * @see #getExtension(String) - */ - @SuppressWarnings("unchecked") - public T getLoadedExtension(String name) { - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Extension name == null"); - Holder holder = cachedInstances.get(name); - if (holder == null) { - cachedInstances.putIfAbsent(name, new Holder()); - holder = cachedInstances.get(name); - } - return (T) holder.get(); - } - - /** - * Return the list of extensions which are already loaded. - *

- * Usually {@link #getSupportedExtensions()} should be called in order to get all extensions. - * - * @see #getSupportedExtensions() - */ - public Set getLoadedExtensions() { - return Collections.unmodifiableSet(new TreeSet(cachedInstances.keySet())); - } - - /** - * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException} - * will be thrown. - */ - @SuppressWarnings("unchecked") - public T getExtension(String name) { - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Extension name == null"); - if ("true".equals(name)) { - return getDefaultExtension(); - } - Holder holder = cachedInstances.get(name); - if (holder == null) { - cachedInstances.putIfAbsent(name, new Holder()); - holder = cachedInstances.get(name); - } - Object instance = holder.get(); - if (instance == null) { - synchronized (holder) { - instance = holder.get(); - if (instance == null) { - instance = createExtension(name); - holder.set(instance); - } - } - } - return (T) instance; - } - - /** - * Return default extension, return null if it's not configured. - */ - public T getDefaultExtension() { - getExtensionClasses(); - if (null == cachedDefaultName || cachedDefaultName.length() == 0 - || "true".equals(cachedDefaultName)) { - return null; - } - return getExtension(cachedDefaultName); - } - - public boolean hasExtension(String name) { - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Extension name == null"); - try { - this.getExtensionClass(name); - return true; - } catch (Throwable t) { - return false; - } - } - - public Set getSupportedExtensions() { - Map> clazzes = getExtensionClasses(); - return Collections.unmodifiableSet(new TreeSet(clazzes.keySet())); - } - - /** - * Return default extension name, return null if not configured. - */ - public String getDefaultExtensionName() { - getExtensionClasses(); - return cachedDefaultName; - } - - /** - * Register new extension via API - * - * @param name extension name - * @param clazz extension class - * @throws IllegalStateException when extension with the same name has already been registered. - */ - public void addExtension(String name, Class clazz) { - getExtensionClasses(); // load classes - - if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Input type " + clazz + "not implement Extension " - + type); - } - if (clazz.isInterface()) { - throw new IllegalStateException("Input type " + clazz + "can not be interface!"); - } - - if (!clazz.isAnnotationPresent(Adaptive.class)) { - if (StringUtils.isBlank(name)) { - throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); - } - if (cachedClasses.get().containsKey(name)) { - throw new IllegalStateException("Extension name " + name - + " already existed(Extension " + type + ")!"); - } - - cachedNames.put(clazz, name); - cachedClasses.get().put(name, clazz); - } else { - if (cachedAdaptiveClass != null) { - throw new IllegalStateException("Adaptive Extension already existed(Extension " - + type + ")!"); - } - - cachedAdaptiveClass = clazz; - } - } - - /** - * Replace the existing extension via API - * - * @param name extension name - * @param clazz extension class - * @throws IllegalStateException when extension to be placed doesn't exist - * @deprecated not recommended any longer, and use only when test - */ - @Deprecated - public void replaceExtension(String name, Class clazz) { - getExtensionClasses(); // load classes - - if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Input type " + clazz + "not implement Extension " - + type); - } - if (clazz.isInterface()) { - throw new IllegalStateException("Input type " + clazz + "can not be interface!"); - } - - if (!clazz.isAnnotationPresent(Adaptive.class)) { - if (StringUtils.isBlank(name)) { - throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); - } - if (!cachedClasses.get().containsKey(name)) { - throw new IllegalStateException("Extension name " + name - + " not existed(Extension " + type + ")!"); - } - - cachedNames.put(clazz, name); - cachedClasses.get().put(name, clazz); - cachedInstances.remove(name); - } else { - if (cachedAdaptiveClass == null) { - throw new IllegalStateException("Adaptive Extension not existed(Extension " + type - + ")!"); - } - - cachedAdaptiveClass = clazz; - cachedAdaptiveInstance.set(null); - } - } - - @SuppressWarnings("unchecked") - public T getAdaptiveExtension() { - Object instance = cachedAdaptiveInstance.get(); - if (instance == null) { - if (createAdaptiveInstanceError == null) { - synchronized (cachedAdaptiveInstance) { - instance = cachedAdaptiveInstance.get(); - if (instance == null) { - try { - instance = createAdaptiveExtension(); - cachedAdaptiveInstance.set(instance); - } catch (Throwable t) { - createAdaptiveInstanceError = t; - throw new IllegalStateException("fail to create adaptive instance: " - + t.toString(), t); - } - } - } - } else { - throw new IllegalStateException("fail to create adaptive instance: " - + createAdaptiveInstanceError.toString(), - createAdaptiveInstanceError); - } - } - - return (T) instance; - } - - private IllegalStateException findException(String name) { - for (Map.Entry entry : exceptions.entrySet()) { - if (entry.getKey().toLowerCase().contains(name.toLowerCase())) { - return entry.getValue(); - } - } - StringBuilder buf = new StringBuilder("No such extension " + type.getName() + " by name " - + name); - - int i = 1; - for (Map.Entry entry : exceptions.entrySet()) { - if (i == 1) { - buf.append(", possible causes: "); - } - - buf.append("\r\n("); - buf.append(i++); - buf.append(") "); - buf.append(entry.getKey()); - buf.append(":\r\n"); - buf.append(StringUtils.toString(entry.getValue())); - } - return new IllegalStateException(buf.toString()); - } - - @SuppressWarnings("unchecked") - private T createExtension(String name) { - Class clazz = getExtensionClasses().get(name); - if (clazz == null) { - throw findException(name); - } - try { - T instance = (T) EXTENSION_INSTANCES.get(clazz); - if (instance == null) { - EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); - instance = (T) EXTENSION_INSTANCES.get(clazz); - } - injectExtension(instance); - Set> wrapperClasses = cachedWrapperClasses; - if (wrapperClasses != null && !wrapperClasses.isEmpty()) { - for (Class wrapperClass : wrapperClasses) { - instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance( - instance)); - } - } - return instance; - } catch (Throwable t) { - throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type - + ") could not be instantiated: " + t.getMessage(), t); - } - } - - private T injectExtension(T instance) { - try { - if (objectFactory != null) { - for (Method method : instance.getClass().getMethods()) { - if (method.getName().startsWith("set") - && method.getParameterTypes().length == 1 - && Modifier.isPublic(method.getModifiers())) { - Class pt = method.getParameterTypes()[0]; - try { - String property = method.getName().length() > 3 ? method.getName() - .substring(3, 4).toLowerCase() - + method.getName() - .substring(4) - : ""; - Object object = objectFactory.getExtension(pt, property); - if (object != null) { - method.invoke(instance, object); - } - } catch (Exception e) { - logger.error( - "fail to inject via method " + method.getName() + " of interface " - + type.getName() + ": " + e.getMessage(), e); - } - } - } - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - return instance; - } - - private Class getExtensionClass(String name) { - if (type == null) - throw new IllegalArgumentException("Extension type == null"); - if (name == null) - throw new IllegalArgumentException("Extension name == null"); - Class clazz = getExtensionClasses().get(name); - if (clazz == null) - throw new IllegalStateException("No such extension \"" + name + "\" for " - + type.getName() + "!"); - return clazz; - } - - private Map> getExtensionClasses() { - Map> classes = cachedClasses.get(); - if (classes == null) { - synchronized (cachedClasses) { - classes = cachedClasses.get(); - if (classes == null) { - classes = loadExtensionClasses(); - cachedClasses.set(classes); - } - } - } - return classes; - } - - // synchronized in getExtensionClasses - private Map> loadExtensionClasses() { - final SPI defaultAnnotation = type.getAnnotation(SPI.class); - if (defaultAnnotation != null) { - String value = defaultAnnotation.value(); - if ((value = value.trim()).length() > 0) { - String[] names = NAME_SEPARATOR.split(value); - if (names.length > 1) { - throw new IllegalStateException( - "more than 1 default extension name on extension " + type.getName() + ": " - + Arrays.toString(names)); - } - if (names.length == 1) - cachedDefaultName = names[0]; - } - } - - Map> extensionClasses = new HashMap>(); - loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); - loadDirectory(extensionClasses, DUBBO_DIRECTORY); - loadDirectory(extensionClasses, SERVICES_DIRECTORY); - return extensionClasses; - } - - private void loadDirectory(Map> extensionClasses, String dir) { - String fileName = dir + type.getName(); - try { - Enumeration urls; - ClassLoader classLoader = findClassLoader(); - if (classLoader != null) { - urls = classLoader.getResources(fileName); - } else { - urls = ClassLoader.getSystemResources(fileName); - } - if (urls != null) { - while (urls.hasMoreElements()) { - java.net.URL resourceURL = urls.nextElement(); - loadResource(extensionClasses, classLoader, resourceURL); - } - } - } catch (Throwable t) { - logger.error("Exception when load extension class(interface: " + type - + ", description file: " + fileName + ").", t); - } - } - - private void loadResource(Map> extensionClasses, ClassLoader classLoader, - java.net.URL resourceURL) { - try { - BufferedReader reader = new BufferedReader(new InputStreamReader( - resourceURL.openStream(), "utf-8")); - try { - String line; - while ((line = reader.readLine()) != null) { - final int ci = line.indexOf('#'); - if (ci >= 0) - line = line.substring(0, ci); - line = line.trim(); - if (line.length() > 0) { - try { - String name = null; - int i = line.indexOf('='); - if (i > 0) { - name = line.substring(0, i).trim(); - line = line.substring(i + 1).trim(); - } - if (line.length() > 0) { - loadClass(extensionClasses, resourceURL, - Class.forName(line, true, classLoader), name); - } - } catch (Throwable t) { - IllegalStateException e = new IllegalStateException( - "Failed to load extension class(interface: " + type - + ", class line: " + line + ") in " + resourceURL - + ", cause: " + t.getMessage(), t); - exceptions.put(line, e); - } - } - } - } finally { - reader.close(); - } - } catch (Throwable t) { - logger.error("Exception when load extension class(interface: " + type - + ", class file: " + resourceURL + ") in " + resourceURL, t); - } - } - - private void loadClass(Map> extensionClasses, java.net.URL resourceURL, - Class clazz, String name) throws NoSuchMethodException { - if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Error when load extension class(interface: " + type - + ", class line: " + clazz.getName() + "), class " - + clazz.getName() + "is not subtype of interface."); - } - if (clazz.isAnnotationPresent(Adaptive.class)) { - if (cachedAdaptiveClass == null) { - cachedAdaptiveClass = clazz; - } else if (!cachedAdaptiveClass.equals(clazz)) { - throw new IllegalStateException("More than 1 adaptive class found: " - + cachedAdaptiveClass.getClass().getName() + ", " - + clazz.getClass().getName()); - } - } else if (isWrapperClass(clazz)) { - Set> wrappers = cachedWrapperClasses; - if (wrappers == null) { - cachedWrapperClasses = new ConcurrentHashSet>(); - wrappers = cachedWrapperClasses; - } - wrappers.add(clazz); - } else { - clazz.getConstructor(); - if (name == null || name.length() == 0) { - name = findAnnotationName(clazz); - if (name.length() == 0) { - throw new IllegalStateException("No such extension name for the class " - + clazz.getName() + " in the config " - + resourceURL); - } - } - String[] names = NAME_SEPARATOR.split(name); - if (names != null && names.length > 0) { - Activate activate = clazz.getAnnotation(Activate.class); - if (activate != null) { - cachedActivates.put(names[0], activate); - } - for (String n : names) { - if (!cachedNames.containsKey(clazz)) { - cachedNames.put(clazz, n); - } - Class c = extensionClasses.get(n); - if (c == null) { - extensionClasses.put(n, clazz); - } else if (c != clazz) { - throw new IllegalStateException("Duplicate extension " + type.getName() - + " name " + n + " on " + c.getName() - + " and " + clazz.getName()); - } - } - } - } - } - - private boolean isWrapperClass(Class clazz) { - try { - clazz.getConstructor(type); - return true; - } catch (NoSuchMethodException e) { - return false; - } - } - - @SuppressWarnings("deprecation") - private String findAnnotationName(Class clazz) { - com.alibaba.dubbo.common.Extension extension = clazz - .getAnnotation(com.alibaba.dubbo.common.Extension.class); - if (extension == null) { - String name = clazz.getSimpleName(); - if (name.endsWith(type.getSimpleName())) { - name = name.substring(0, name.length() - type.getSimpleName().length()); - } - return name.toLowerCase(); - } - return extension.value(); - } - - @SuppressWarnings("unchecked") - private T createAdaptiveExtension() { - try { - return injectExtension((T) getAdaptiveExtensionClass().newInstance()); - } catch (Exception e) { - throw new IllegalStateException("Can not create adaptive extension " + type - + ", cause: " + e.getMessage(), e); - } - } - - private Class getAdaptiveExtensionClass() { - getExtensionClasses(); - if (cachedAdaptiveClass != null) { - return cachedAdaptiveClass; - } - return cachedAdaptiveClass = createAdaptiveExtensionClass(); - } - - private Class createAdaptiveExtensionClass() { - String code = createAdaptiveExtensionClassCode(); - ClassLoader classLoader = findClassLoader(); - com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader( - com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); - return compiler.compile(code, classLoader); - } - - private String createAdaptiveExtensionClassCode() { - StringBuilder codeBuilder = new StringBuilder(); - Method[] methods = type.getMethods(); - boolean hasAdaptiveAnnotation = false; - for (Method m : methods) { - if (m.isAnnotationPresent(Adaptive.class)) { - hasAdaptiveAnnotation = true; - break; - } - } - // no need to generate adaptive class since there's no adaptive method found. - if (!hasAdaptiveAnnotation) - throw new IllegalStateException("No adaptive method on extension " + type.getName() - + ", refuse to create the adaptive class!"); - - codeBuilder.append("package ").append(type.getPackage().getName()).append(";"); - codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";"); - codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive") - .append(" implements ").append(type.getCanonicalName()).append(" {"); - - for (Method method : methods) { - Class rt = method.getReturnType(); - Class[] pts = method.getParameterTypes(); - Class[] ets = method.getExceptionTypes(); - - Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); - StringBuilder code = new StringBuilder(512); - if (adaptiveAnnotation == null) { - code.append("throw new UnsupportedOperationException(\"method ") - .append(method.toString()).append(" of interface ").append(type.getName()) - .append(" is not adaptive method!\");"); - } else { - int urlTypeIndex = -1; - for (int i = 0; i < pts.length; ++i) { - if (pts[i].equals(URL.class)) { - urlTypeIndex = i; - break; - } - } - // found parameter in URL type - if (urlTypeIndex != -1) { - // Null Point check - String s = String - .format( - "\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");", - urlTypeIndex); - code.append(s); - - s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); - code.append(s); - } - // did not find parameter in URL type - else { - String attribMethod = null; - - // find URL getter method - LBL_PTS: for (int i = 0; i < pts.length; ++i) { - Method[] ms = pts[i].getMethods(); - for (Method m : ms) { - String name = m.getName(); - if ((name.startsWith("get") || name.length() > 3) - && Modifier.isPublic(m.getModifiers()) - && !Modifier.isStatic(m.getModifiers()) - && m.getParameterTypes().length == 0 - && m.getReturnType() == URL.class) { - urlTypeIndex = i; - attribMethod = name; - break LBL_PTS; - } - } - } - if (attribMethod == null) { - throw new IllegalStateException( - "fail to create adaptive class for interface " - + type.getName() - + ": not found url parameter or url attribute in parameters of method " - + method.getName()); - } - - // Null point check - String s = String - .format( - "\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");", - urlTypeIndex, pts[urlTypeIndex].getName()); - code.append(s); - s = String - .format( - "\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");", - urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod); - code.append(s); - - s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, - attribMethod); - code.append(s); - } - - String[] value = adaptiveAnnotation.value(); - // value is not set, use the value generated from class name as the key - if (value.length == 0) { - char[] charArray = type.getSimpleName().toCharArray(); - StringBuilder sb = new StringBuilder(128); - for (int i = 0; i < charArray.length; i++) { - if (Character.isUpperCase(charArray[i])) { - if (i != 0) { - sb.append("."); - } - sb.append(Character.toLowerCase(charArray[i])); - } else { - sb.append(charArray[i]); - } - } - value = new String[] { sb.toString() }; - } - - boolean hasInvocation = false; - for (int i = 0; i < pts.length; ++i) { - if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) { - // Null Point check - String s = String - .format( - "\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", - i); - code.append(s); - s = String.format("\nString methodName = arg%d.getMethodName();", i); - code.append(s); - hasInvocation = true; - break; - } - } - - String defaultExtName = cachedDefaultName; - String getNameCode = null; - for (int i = value.length - 1; i >= 0; --i) { - if (i == value.length - 1) { - if (null != defaultExtName) { - if (!"protocol".equals(value[i])) - if (hasInvocation) - getNameCode = String.format( - "url.getMethodParameter(methodName, \"%s\", \"%s\")", - value[i], defaultExtName); - else - getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", - value[i], defaultExtName); - else - getNameCode = String.format( - "( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", - defaultExtName); - } else { - if (!"protocol".equals(value[i])) - if (hasInvocation) - getNameCode = String.format( - "url.getMethodParameter(methodName, \"%s\", \"%s\")", - value[i], defaultExtName); - else - getNameCode = String.format("url.getParameter(\"%s\")", - value[i]); - else - getNameCode = "url.getProtocol()"; - } - } else { - if (!"protocol".equals(value[i])) - if (hasInvocation) - getNameCode = String.format( - "url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], - defaultExtName); - else - getNameCode = String.format("url.getParameter(\"%s\", %s)", - value[i], getNameCode); - else - getNameCode = String - .format("url.getProtocol() == null ? (%s) : url.getProtocol()", - getNameCode); - } - } - code.append("\nString extName = ").append(getNameCode).append(";"); - // check extName == null? - String s = String - .format( - "\nif(extName == null) " - + "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");", - type.getName(), Arrays.toString(value)); - code.append(s); - - s = String.format( - "\n%s extension = (% 0) { - codeBuilder.append(", "); - } - codeBuilder.append(pts[i].getCanonicalName()); - codeBuilder.append(" "); - codeBuilder.append("arg").append(i); - } - codeBuilder.append(")"); - if (ets.length > 0) { - codeBuilder.append(" throws "); - for (int i = 0; i < ets.length; i++) { - if (i > 0) { - codeBuilder.append(", "); - } - codeBuilder.append(ets[i].getCanonicalName()); - } - } - codeBuilder.append(" {"); - codeBuilder.append(code.toString()); - codeBuilder.append("\n}"); - } - codeBuilder.append("\n}"); - if (logger.isDebugEnabled()) { - logger.debug(codeBuilder.toString()); - } - return codeBuilder.toString(); - } - - @Override - public String toString() { - return this.getClass().getName() + "[" + type.getName() + "]"; - } - -} \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/serialize/java/ClassLoaderJavaObjectInput.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/serialize/java/ClassLoaderJavaObjectInput.java deleted file mode 100644 index 4cc4671f4..000000000 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/serialize/java/ClassLoaderJavaObjectInput.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.alibaba.dubbo.common.serialize.java; - -import com.alibaba.dubbo.common.serialize.nativejava.NativeJavaObjectInput; - -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.lang.reflect.Type; - -public class ClassLoaderJavaObjectInput extends NativeJavaObjectInput { - public final static int MAX_BYTE_ARRAY_LENGTH = 8 * 1024 * 1024; - - public ClassLoaderJavaObjectInput(InputStream is) throws IOException { - super( - (ObjectInputStream) (is instanceof ObjectInputStream ? is : new ObjectInputStream(is))); - } - - @Override - public byte[] readBytes() throws IOException { - int len = getObjectInputStream().readInt(); - if (len < 0) - return null; - if (len == 0) - return new byte[0]; - if (len > MAX_BYTE_ARRAY_LENGTH) - throw new IOException("Byte array length too large. " + len); - - byte[] b = new byte[len]; - getObjectInputStream().readFully(b); - return b; - } - - @Override - public String readUTF() throws IOException { - int len = getObjectInputStream().readInt(); - if (len < 0) - return null; - - return getObjectInputStream().readUTF(); - } - - @Override - public Object readObject() throws IOException, ClassNotFoundException { - byte b = getObjectInputStream().readByte(); - if (b == 0) - return null; - - return getObjectInputStream().readObject(); - } - - @Override - @SuppressWarnings("unchecked") - public T readObject(Class cls) throws IOException, ClassNotFoundException { - return (T) readObject(); - } - - @Override - @SuppressWarnings("unchecked") - public T readObject(Class cls, Type type) throws IOException, ClassNotFoundException { - return (T) readObject(); - } - - public InputStream getInputStream() { - return getObjectInputStream(); - } - -} diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/serialize/java/ClassLoaderObjectInputStream.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/serialize/java/ClassLoaderObjectInputStream.java deleted file mode 100644 index 2220b97ee..000000000 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/serialize/java/ClassLoaderObjectInputStream.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.alibaba.dubbo.common.serialize.java; - -import java.io.*; -import java.lang.reflect.Proxy; - -public class ClassLoaderObjectInputStream extends ObjectInputStream { - - public ClassLoader getClassLoader() { - return classLoader; - } - - public void setClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - } - - /** - * The class loader to use. - */ - private ClassLoader classLoader; - - /** - * Constructs a new ClassLoaderObjectInputStream. - * - * @param classLoader the ClassLoader from which classes should be loaded - * @param inputStream the InputStream to work on - * @throws IOException in case of an I/O error - * @throws StreamCorruptedException if the stream is corrupted - */ - public ClassLoaderObjectInputStream(final ClassLoader classLoader, final InputStream inputStream) - throws IOException, - StreamCorruptedException { - super(inputStream); - this.classLoader = classLoader; - } - - /** - * Resolve a class specified by the descriptor using the - * specified ClassLoader or the super ClassLoader. - * - * @param objectStreamClass descriptor of the class - * @return the Class object described by the ObjectStreamClass - * @throws IOException in case of an I/O error - * @throws ClassNotFoundException if the Class cannot be found - */ - @Override - protected Class resolveClass(final ObjectStreamClass objectStreamClass) throws IOException, - ClassNotFoundException { - - try { - return Class.forName(objectStreamClass.getName(), false, classLoader); - } catch (final ClassNotFoundException cnfe) { - // delegate to super class loader which can resolve primitives - return super.resolveClass(objectStreamClass); - } - } - - /** - * Create a proxy class that implements the specified interfaces using - * the specified ClassLoader or the super ClassLoader. - * - * @param interfaces the interfaces to implement - * @return a proxy class implementing the interfaces - * @throws IOException in case of an I/O error - * @throws ClassNotFoundException if the Class cannot be found - * @see ObjectInputStream#resolveProxyClass(String[]) - * @since 2.1 - */ - @Override - protected Class resolveProxyClass(final String[] interfaces) throws IOException, - ClassNotFoundException { - final Class[] interfaceClasses = new Class[interfaces.length]; - for (int i = 0; i < interfaces.length; i++) { - interfaceClasses[i] = Class.forName(interfaces[i], false, classLoader); - } - try { - return Proxy.getProxyClass(classLoader, interfaceClasses); - } catch (final IllegalArgumentException e) { - return super.resolveProxyClass(interfaces); - } - } - -} \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/serialize/java/JavaSerialization.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/serialize/java/JavaSerialization.java deleted file mode 100644 index 91581b508..000000000 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/common/serialize/java/JavaSerialization.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.alibaba.dubbo.common.serialize.java; - -import com.alibaba.dubbo.common.URL; -import com.alibaba.dubbo.common.serialize.ObjectInput; -import com.alibaba.dubbo.common.serialize.ObjectOutput; -import com.alibaba.dubbo.common.serialize.Serialization; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * @author syd - * @version ClassLoaderJavaSerialization.java, v 0.1 2023年10月28日 19:18 syd - */ -public class JavaSerialization implements Serialization { - - @Override - public byte getContentTypeId() { - return 3; - } - - @Override - public String getContentType() { - return "x-application/java"; - } - - @Override - public ObjectOutput serialize(URL url, OutputStream output) throws IOException { - return new JavaObjectOutput(output); - } - - @Override - public ObjectInput deserialize(URL url, InputStream is) throws IOException { - return new ClassLoaderJavaObjectInput(new ClassLoaderObjectInputStream(null, is)); - } - -} \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/config/spring/extension/SpringExtensionFactory.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/config/spring/extension/SpringExtensionFactory.java deleted file mode 100644 index cc2d8c5f8..000000000 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/config/spring/extension/SpringExtensionFactory.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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.alibaba.dubbo.config.spring.extension; - -import com.alibaba.dubbo.common.extension.ExtensionFactory; -import com.alibaba.dubbo.common.logger.Logger; -import com.alibaba.dubbo.common.logger.LoggerFactory; -import com.alibaba.dubbo.common.utils.ConcurrentHashSet; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.context.ApplicationContext; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * SpringExtensionFactory - */ -public class SpringExtensionFactory implements ExtensionFactory { - private static final Logger logger = LoggerFactory - .getLogger(SpringExtensionFactory.class); - - private static final Map> contextsWithClassLoader = new ConcurrentHashMap<>(); - - public static void addApplicationContext(ApplicationContext context) { - getContexts().add(context); - } - - public static void removeApplicationContext(ApplicationContext context) { - getContexts().remove(context); - } - - // currently for test purpose - public static void clearContexts() { - - getContexts().clear(); - } - - @Override - @SuppressWarnings("unchecked") - public T getExtension(Class type, String name) { - for (ApplicationContext context : getContexts()) { - if (context.containsBean(name)) { - Object bean = context.getBean(name); - if (type.isInstance(bean)) { - return (T) bean; - } - } - } - - logger.warn("No spring extension(bean) named:" + name - + ", try to find an extension(bean) of type " + type.getName()); - - for (ApplicationContext context : getContexts()) { - try { - return context.getBean(type); - } catch (NoUniqueBeanDefinitionException multiBeanExe) { - throw multiBeanExe; - } catch (NoSuchBeanDefinitionException noBeanExe) { - if (logger.isDebugEnabled()) { - logger.debug( - "Error when get spring extension(bean) for type:" + type.getName(), - noBeanExe); - } - } - } - - logger.warn("No spring extension(bean) named:" + name + ", type:" + type.getName() - + " found, stop get bean."); - - return null; - } - - private static ClassLoader findClassLoader() { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - if (classLoader != null) - return classLoader; - return SpringExtensionFactory.class.getClassLoader(); - } - - private static Set getContexts() { - ClassLoader classLoader = findClassLoader(); - Set contexts = null; - if ((contexts = contextsWithClassLoader.get(classLoader)) == null) { - contextsWithClassLoader.putIfAbsent(classLoader, new ConcurrentHashSet<>()); - contexts = contextsWithClassLoader.get(classLoader); - } - ; - return contexts; - } - -} diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocation.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocation.java deleted file mode 100644 index b8332b511..000000000 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocation.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * 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.alibaba.dubbo.rpc.protocol.dubbo; - -import com.alibaba.dubbo.common.ClassLoaderUtil; -import com.alibaba.dubbo.common.Constants; -import com.alibaba.dubbo.common.logger.Logger; -import com.alibaba.dubbo.common.logger.LoggerFactory; -import com.alibaba.dubbo.common.serialize.Cleanable; -import com.alibaba.dubbo.common.serialize.ObjectInput; -import com.alibaba.dubbo.common.serialize.java.ClassLoaderJavaObjectInput; -import com.alibaba.dubbo.common.serialize.java.ClassLoaderObjectInputStream; -import com.alibaba.dubbo.common.utils.Assert; -import com.alibaba.dubbo.common.utils.ReflectUtils; -import com.alibaba.dubbo.common.utils.StringUtils; -import com.alibaba.dubbo.remoting.Channel; -import com.alibaba.dubbo.remoting.Codec; -import com.alibaba.dubbo.remoting.Decodeable; -import com.alibaba.dubbo.remoting.exchange.Request; -import com.alibaba.dubbo.remoting.transport.CodecSupport; -import com.alibaba.dubbo.rpc.RpcInvocation; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; - -import static com.alibaba.dubbo.rpc.protocol.dubbo.CallbackServiceCodec.decodeInvocationArgument; - -public class DecodeableRpcInvocation extends RpcInvocation implements Codec, Decodeable { - - private static final Logger log = LoggerFactory.getLogger(DecodeableRpcInvocation.class); - - private Channel channel; - - private byte serializationType; - - private InputStream inputStream; - - private Request request; - - private volatile boolean hasDecoded; - - public DecodeableRpcInvocation(Channel channel, Request request, InputStream is, byte id) { - Assert.notNull(channel, "channel == null"); - Assert.notNull(request, "request == null"); - Assert.notNull(is, "inputStream == null"); - this.channel = channel; - this.request = request; - this.inputStream = is; - this.serializationType = id; - } - - @Override - public void decode() throws Exception { - if (!hasDecoded && channel != null && inputStream != null) { - try { - decode(channel, inputStream); - } catch (Throwable e) { - if (log.isWarnEnabled()) { - log.warn("Decode rpc invocation failed: " + e.getMessage(), e); - } - request.setBroken(true); - request.setData(e); - } finally { - hasDecoded = true; - } - } - } - - @Override - public void encode(Channel channel, OutputStream output, Object message) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public Object decode(Channel channel, InputStream input) throws IOException { - //注意 channel的url不具有唯一性 实际上使用以inputstream的path为准 - ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType) - .deserialize(channel.getUrl(), input); - - String dubboVersion = in.readUTF(); - request.setVersion(dubboVersion); - setAttachment(Constants.DUBBO_VERSION_KEY, dubboVersion); - String path = in.readUTF(); - setAttachment(Constants.PATH_KEY, path); - ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); - if (in instanceof ClassLoaderJavaObjectInput) { - InputStream is = ((ClassLoaderJavaObjectInput) in).getInputStream(); - if (is instanceof ClassLoaderObjectInputStream) { - ClassLoader cl = ClassLoaderUtil.getClassLoaderByPath(path); - ((ClassLoaderObjectInputStream) is).setClassLoader(cl); - Thread.currentThread().setContextClassLoader(cl); - } - } - setAttachment(Constants.VERSION_KEY, in.readUTF()); - - setMethodName(in.readUTF()); - try { - Object[] args; - Class[] pts; - String desc = in.readUTF(); - if (desc.length() == 0) { - pts = DubboCodec.EMPTY_CLASS_ARRAY; - args = DubboCodec.EMPTY_OBJECT_ARRAY; - } else { - pts = ReflectUtils.desc2classArray(desc); - args = new Object[pts.length]; - for (int i = 0; i < args.length; i++) { - try { - args[i] = in.readObject(pts[i]); - } catch (Exception e) { - if (log.isWarnEnabled()) { - log.warn("Decode argument failed: " + e.getMessage(), e); - } - } - } - } - setParameterTypes(pts); - - Map map = (Map) in.readObject(Map.class); - if (map != null && map.size() > 0) { - Map attachment = getAttachments(); - if (attachment == null) { - attachment = new HashMap(); - } - attachment.putAll(map); - setAttachments(attachment); - } - //decode argument ,may be callback - for (int i = 0; i < args.length; i++) { - args[i] = decodeInvocationArgument(channel, this, pts, i, args[i]); - } - - setArguments(args); - - } catch (ClassNotFoundException e) { - throw new IOException(StringUtils.toString("Read invocation data failed.", e)); - } finally { - if (in instanceof Cleanable) { - ((Cleanable) in).cleanup(); - } - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - return this; - } - -} diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/rpc/protocol/dubbo/DecodeableRpcResult.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/rpc/protocol/dubbo/DecodeableRpcResult.java deleted file mode 100644 index dd8b69311..000000000 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alibaba/dubbo/rpc/protocol/dubbo/DecodeableRpcResult.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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.alibaba.dubbo.rpc.protocol.dubbo; - -import com.alibaba.dubbo.common.ClassLoaderUtil; -import com.alibaba.dubbo.common.Constants; -import com.alibaba.dubbo.common.logger.Logger; -import com.alibaba.dubbo.common.logger.LoggerFactory; -import com.alibaba.dubbo.common.serialize.Cleanable; -import com.alibaba.dubbo.common.serialize.ObjectInput; -import com.alibaba.dubbo.common.serialize.java.ClassLoaderJavaObjectInput; -import com.alibaba.dubbo.common.serialize.java.ClassLoaderObjectInputStream; -import com.alibaba.dubbo.common.utils.Assert; -import com.alibaba.dubbo.common.utils.StringUtils; -import com.alibaba.dubbo.remoting.Channel; -import com.alibaba.dubbo.remoting.Codec; -import com.alibaba.dubbo.remoting.Decodeable; -import com.alibaba.dubbo.remoting.exchange.Response; -import com.alibaba.dubbo.remoting.transport.CodecSupport; -import com.alibaba.dubbo.rpc.Invocation; -import com.alibaba.dubbo.rpc.RpcResult; -import com.alibaba.dubbo.rpc.support.RpcUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Type; -import java.util.Map; - -public class DecodeableRpcResult extends RpcResult implements Codec, Decodeable { - - private static final Logger log = LoggerFactory.getLogger(DecodeableRpcResult.class); - - private Channel channel; - - private byte serializationType; - - private InputStream inputStream; - - private Response response; - - private Invocation invocation; - - private volatile boolean hasDecoded; - - public DecodeableRpcResult(Channel channel, Response response, InputStream is, - Invocation invocation, byte id) { - Assert.notNull(channel, "channel == null"); - Assert.notNull(response, "response == null"); - Assert.notNull(is, "inputStream == null"); - this.channel = channel; - this.response = response; - this.inputStream = is; - this.invocation = invocation; - this.serializationType = id; - } - - @Override - public void encode(Channel channel, OutputStream output, Object message) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public Object decode(Channel channel, InputStream input) throws IOException { - ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType) - .deserialize(channel.getUrl(), input); - ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); - ClassLoader cl = invocation.getInvoker().getInterface().getClassLoader(); - Thread.currentThread().setContextClassLoader(cl); - if (in instanceof ClassLoaderJavaObjectInput) { - InputStream is = ((ClassLoaderJavaObjectInput) in).getInputStream(); - if (is instanceof ClassLoaderObjectInputStream) { - ((ClassLoaderObjectInputStream) is).setClassLoader(cl); - } - } - byte flag = in.readByte(); - switch (flag) { - case DubboCodec.RESPONSE_NULL_VALUE: - break; - case DubboCodec.RESPONSE_VALUE: - try { - Type[] returnType = RpcUtils.getReturnTypes(invocation); - setValue(returnType == null || returnType.length == 0 ? in.readObject() - : (returnType.length == 1 ? in.readObject((Class) returnType[0]) : in - .readObject((Class) returnType[0], returnType[1]))); - } catch (ClassNotFoundException e) { - throw new IOException(StringUtils.toString("Read response data failed.", e)); - } finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - break; - case DubboCodec.RESPONSE_WITH_EXCEPTION: - try { - Object obj = in.readObject(); - if (obj instanceof Throwable == false) - throw new IOException("Response data error, expect Throwable, but get " - + obj); - setException((Throwable) obj); - } catch (ClassNotFoundException e) { - throw new IOException(StringUtils.toString("Read response data failed.", e)); - } finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - break; - case DubboCodec.RESPONSE_NULL_VALUE_WITH_ATTACHMENTS: - try { - setAttachments((Map) in.readObject(Map.class)); - } catch (ClassNotFoundException e) { - throw new IOException(StringUtils.toString("Read response data failed.", e)); - } finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - break; - case DubboCodec.RESPONSE_VALUE_WITH_ATTACHMENTS: - try { - Type[] returnType = RpcUtils.getReturnTypes(invocation); - setValue(returnType == null || returnType.length == 0 ? in.readObject() - : (returnType.length == 1 ? in.readObject((Class) returnType[0]) : in - .readObject((Class) returnType[0], returnType[1]))); - setAttachments((Map) in.readObject(Map.class)); - } catch (ClassNotFoundException e) { - throw new IOException(StringUtils.toString("Read response data failed.", e)); - } finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - break; - case DubboCodec.RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS: - try { - Object obj = in.readObject(); - if (obj instanceof Throwable == false) - throw new IOException("Response data error, expect Throwable, but get " - + obj); - setException((Throwable) obj); - setAttachments((Map) in.readObject(Map.class)); - } catch (ClassNotFoundException e) { - throw new IOException(StringUtils.toString("Read response data failed.", e)); - } finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - break; - default: - throw new IOException("Unknown result flag, expect '0' '1' '2', get " + flag); - } - if (in instanceof Cleanable) { - ((Cleanable) in).cleanup(); - } - Thread.currentThread().setContextClassLoader(originalClassLoader); - return this; - } - - @Override - public void decode() throws Exception { - if (!hasDecoded && channel != null && inputStream != null) { - try { - decode(channel, inputStream); - } catch (Throwable e) { - if (log.isWarnEnabled()) { - log.warn("Decode rpc result failed: " + e.getMessage(), e); - } - response.setStatus(Response.CLIENT_ERROR); - response.setErrorMessage(StringUtils.toString(e)); - } finally { - hasDecoded = true; - } - } - } - -} diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-log4j2/pom.xml b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-log4j2/pom.xml index ddab63580..860493cb0 100644 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-log4j2/pom.xml +++ b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-log4j2/pom.xml @@ -56,11 +56,6 @@ - - - org.springframework.boot.logging.log4j2.Log4J2LoggingSystem - - *:*:* diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-log4j2/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-log4j2/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java index fcab97631..873e064a5 100644 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-log4j2/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java +++ b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-log4j2/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java @@ -47,14 +47,8 @@ import org.springframework.boot.context.properties.bind.BindResult; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.logging.LogFile; -import org.springframework.boot.logging.LogLevel; -import org.springframework.boot.logging.LoggerConfiguration; +import org.springframework.boot.logging.*; import org.springframework.boot.logging.LoggerConfiguration.LevelConfiguration; -import org.springframework.boot.logging.LoggingInitializationContext; -import org.springframework.boot.logging.LoggingSystem; -import org.springframework.boot.logging.LoggingSystemFactory; -import org.springframework.boot.logging.Slf4JLoggingSystem; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.util.Assert; @@ -72,7 +66,7 @@ * @author Ben Hale * @since 1.2.0 */ -public class Log4J2LoggingSystem extends Slf4JLoggingSystem { +public class Log4J2LoggingSystem extends AbstractLoggingSystem { private static final String FILE_PROTOCOL = "file"; @@ -204,7 +198,9 @@ private List getOverrides(LoggingInitializationContext initializationCon @Override protected void loadConfiguration(LoggingInitializationContext initializationContext, String location, LogFile logFile) { - super.loadConfiguration(initializationContext, location, logFile); + if (initializationContext != null) { + applySystemProperties(initializationContext.getEnvironment(), logFile); + } loadConfiguration(location, logFile, getOverrides(initializationContext)); } diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/pom.xml b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/pom.xml similarity index 65% rename from sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/pom.xml rename to sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/pom.xml index 239d5a13e..d5101f09e 100644 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/pom.xml +++ b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/pom.xml @@ -1,7 +1,6 @@ - + 4.0.0 com.alipay.sofa.serverless @@ -9,14 +8,14 @@ ${revision} ../pom.xml - sofa-serverless-adapter-dubbo2.6 + sofa-serverless-adapter-logback ${revision} - com.alibaba - dubbo - 2.6.12 + org.springframework.boot + spring-boot-starter + ${spring.boot.version} provided @@ -24,6 +23,18 @@ sofa-ark-spi provided + + + + + + + + + + + + @@ -41,19 +52,14 @@ - com.alipay.sofa.serverless.adapter.Dubbo26AdapterActivator + com.alipay.sofa.serverless.adapter.LogbackAdapterActivator - - com.alibaba.dubbo.common - com.alibaba.dubbo.config.spring.extension - com.alibaba.dubbo.rpc.protocol.dubbo - *:*:* - com.alipay.sofa.serverless:sofa-serverless-adapter-dubbo2.6 + com.alipay.sofa.serverless:sofa-serverless-adapter-logback @@ -61,5 +67,4 @@ - diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/src/main/java/ch/qos/logback/classic/spi/LogbackServiceProvider.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/src/main/java/ch/qos/logback/classic/spi/LogbackServiceProvider.java new file mode 100755 index 000000000..80381debe --- /dev/null +++ b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/src/main/java/ch/qos/logback/classic/spi/LogbackServiceProvider.java @@ -0,0 +1,113 @@ +/* + * 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 ch.qos.logback.classic.spi; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.util.ContextInitializer; +import ch.qos.logback.classic.util.ContextSelectorStaticBinder; +import ch.qos.logback.classic.util.LogbackMDCAdapter; +import ch.qos.logback.core.CoreConstants; +import ch.qos.logback.core.joran.spi.JoranException; +import ch.qos.logback.core.status.StatusUtil; +import ch.qos.logback.core.util.StatusPrinter; +import org.slf4j.ILoggerFactory; +import org.slf4j.IMarkerFactory; +import org.slf4j.helpers.BasicMarkerFactory; +import org.slf4j.helpers.Util; +import org.slf4j.spi.MDCAdapter; +import org.slf4j.spi.SLF4JServiceProvider; + +public class LogbackServiceProvider implements SLF4JServiceProvider { + + final static String NULL_CS_URL = CoreConstants.CODES_URL + + "#null_CS"; + + /** + * Declare the version of the SLF4J API this implementation is compiled against. + * The value of this field is modified with each major release. + */ + // to avoid constant folding by the compiler, this field must *not* be final + public static String REQUESTED_API_VERSION = "2.0.99"; // !final + + private LoggerContext defaultLoggerContext; + private IMarkerFactory markerFactory; + private LogbackMDCAdapter mdcAdapter; + private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder + .getSingleton(); + private static Object KEY = new Object(); + private volatile boolean initialized = false; + + @Override + public void initialize() { + defaultLoggerContext = new LoggerContext(); + defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME); + initializeLoggerContext(); + defaultLoggerContext.start(); + markerFactory = new BasicMarkerFactory(); + mdcAdapter = new LogbackMDCAdapter(); + // set the MDCAdapter for the defaultLoggerContext immediately + defaultLoggerContext.setMDCAdapter(mdcAdapter); + System.out.println("init logback in sofa serverless adapter"); + } + + private void initializeLoggerContext() { + try { + try { + new ContextInitializer(defaultLoggerContext).autoConfig(); + } catch (JoranException je) { + Util.report("Failed to auto configure default logger context", je); + } + // LOGBACK-292 + if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) { + StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext); + } + contextSelectorBinder.init(defaultLoggerContext, KEY); + initialized = true; + } catch (Exception t) { // see LOGBACK-1159 + Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t); + } + } + + @Override + public ILoggerFactory getLoggerFactory() { + if (!initialized) { + return defaultLoggerContext; + } + + if (contextSelectorBinder.getContextSelector() == null) { + throw new IllegalStateException("contextSelector cannot be null. See also " + + NULL_CS_URL); + } + return contextSelectorBinder.getContextSelector().getLoggerContext(); + } + + @Override + public IMarkerFactory getMarkerFactory() { + return markerFactory; + } + + @Override + public MDCAdapter getMDCAdapter() { + return mdcAdapter; + } + + @Override + public String getRequestedApiVersion() { + return REQUESTED_API_VERSION; + } + +} diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/src/main/java/com/alipay/sofa/serverless/adapter/ArkLogbackContextSelector.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/src/main/java/com/alipay/sofa/serverless/adapter/ArkLogbackContextSelector.java new file mode 100644 index 000000000..e6481a540 --- /dev/null +++ b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/src/main/java/com/alipay/sofa/serverless/adapter/ArkLogbackContextSelector.java @@ -0,0 +1,143 @@ +/* + * 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.serverless.adapter; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.selector.ContextSelector; +import ch.qos.logback.classic.util.LogbackMDCAdapter; +import ch.qos.logback.core.CoreConstants; +import org.slf4j.helpers.BasicMarkerFactory; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ArkLogbackContextSelector implements ContextSelector { + + private static final Map CLASS_LOADER_LOGGER_CONTEXT = new HashMap<>(); + + private static final String BIZ_CLASS_LOADER = "com.alipay.sofa.ark.container.service.classloader.BizClassLoader"; + private static final String CONTAINER_CLASS_LOADER = "com.alipay.sofa.ark.bootstrap.ContainerClassLoader"; + + private LoggerContext defaultLoggerContext; + + public ArkLogbackContextSelector(LoggerContext loggerContext) { + this.defaultLoggerContext = loggerContext; + } + + @Override + public LoggerContext getLoggerContext() { + ClassLoader classLoader = this.findClassLoader(); + if (classLoader == null) { + return defaultLoggerContext; + } + return getContext(classLoader); + } + + private ClassLoader findClassLoader() { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader != null && CONTAINER_CLASS_LOADER.equals(classLoader.getClass().getName())) { + return null; + } + if (classLoader != null && BIZ_CLASS_LOADER.equals(classLoader.getClass().getName())) { + return classLoader; + } + + Class[] context = new SecurityManager() { + @Override + public Class[] getClassContext() { + return super.getClassContext(); + } + }.getClassContext(); + if (context == null || context.length == 0) { + return null; + } + for (Class cls : context) { + if (cls.getClassLoader() != null + && BIZ_CLASS_LOADER.equals(cls.getClassLoader().getClass().getName())) { + return cls.getClassLoader(); + } + } + + return null; + } + + private LoggerContext getContext(ClassLoader cls) { + LoggerContext loggerContext = CLASS_LOADER_LOGGER_CONTEXT.get(cls); + if (null == loggerContext) { + synchronized (ArkLogbackContextSelector.class) { + loggerContext = CLASS_LOADER_LOGGER_CONTEXT.get(cls); + if (null == loggerContext) { + loggerContext = new LoggerContext(); + loggerContext.setName(Integer.toHexString(System.identityHashCode(cls))); + loggerContext.start(); + // set the MDCAdapter for the defaultLoggerContext immediately + loggerContext.setMDCAdapter(new LogbackMDCAdapter()); + CLASS_LOADER_LOGGER_CONTEXT.put(cls, loggerContext); + } + } + } + return loggerContext; + } + + @Override + public LoggerContext getLoggerContext(String name) { + if (StringUtils.isEmpty(name)) { + return defaultLoggerContext; + } + for (ClassLoader classLoader : CLASS_LOADER_LOGGER_CONTEXT.keySet()) { + LoggerContext loggerContext = CLASS_LOADER_LOGGER_CONTEXT.get(classLoader); + if (name.equals(loggerContext.getName())) { + return loggerContext; + } + } + return defaultLoggerContext; + } + + @Override + public LoggerContext getDefaultLoggerContext() { + return defaultLoggerContext; + } + + @Override + public LoggerContext detachLoggerContext(String loggerContextName) { + if (StringUtils.isEmpty(loggerContextName)) { + return null; + } + for (ClassLoader classLoader : CLASS_LOADER_LOGGER_CONTEXT.keySet()) { + LoggerContext loggerContext = CLASS_LOADER_LOGGER_CONTEXT.get(classLoader); + if (loggerContextName.equals(loggerContext.getName())) { + return removeContext(classLoader); + } + } + return null; + } + + public LoggerContext removeContext(ClassLoader cls) { + if (cls == null) { + return null; + } + return CLASS_LOADER_LOGGER_CONTEXT.remove(cls); + } + + @Override + public List getContextNames() { + return CLASS_LOADER_LOGGER_CONTEXT.values().stream().map(LoggerContext::getName).collect(Collectors.toList()); + } +} diff --git a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alipay/sofa/serverless/adapter/Dubbo26AdapterActivator.java b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/src/main/java/com/alipay/sofa/serverless/adapter/LogbackAdapterActivator.java similarity index 94% rename from sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alipay/sofa/serverless/adapter/Dubbo26AdapterActivator.java rename to sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/src/main/java/com/alipay/sofa/serverless/adapter/LogbackAdapterActivator.java index ad378bc87..f24c380db 100644 --- a/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-dubbo2.6/src/main/java/com/alipay/sofa/serverless/adapter/Dubbo26AdapterActivator.java +++ b/sofa-serverless-runtime/sofa-serverless-adapter-ext/sofa-serverless-adapter-logback/src/main/java/com/alipay/sofa/serverless/adapter/LogbackAdapterActivator.java @@ -23,7 +23,7 @@ * @author lvjing2 * @since 0.5.5 */ -public class Dubbo26AdapterActivator implements PluginActivator { +public class LogbackAdapterActivator implements PluginActivator { @Override public void start(PluginContext context) { } diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/pom.xml b/sofa-serverless-runtime/sofa-serverless-base-loader/pom.xml deleted file mode 100644 index ace06cd58..000000000 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - sofa-serverless-runtime - com.alipay.sofa.serverless - ${revision} - ../pom.xml - - 4.0.0 - ${revision} - pom - sofa-serverless-base-loader - - sofa-serverless-spring-loader - sofa-serverless-spring-loader-tool - - - - 8 - 8 - - - \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/README.md b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/README.md deleted file mode 100644 index 50710c0e0..000000000 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# 使用说明 - -1. 引入打包依赖 -```xml - - org.springframework.boot - spring-boot-maven-plugin - 2.7.15 - - ../../target/boot - executable - - - - package - - repackage - - - - - - com.alipay.sofa.serverless - sofa-serverless-spring-loader-tool - - 0.5.6 - - - -``` -2. fat jar启动方式不变,会默认使用sofa-serverless-spring-loader的JarLauncher启动 -```shell -java -jar xxx-executable.jar -``` -3. 解压启动方式,Launcher需要改成com.alipay.sofa.serverless.spring.loader.JarLauncher -```shell -java -classpath xxx-executable-unpack com.alipay.sofa.serverless.spring.loader.JarLauncher -``` - -# 维护说明 - -如果改了ofa-serverless-spring-loader代码,需要先手动mvn打包,然后将sofa-serverless-spring-loader/target/sofa-serverless-spring-loader-xxx.jar复制到sofa-serverless-spring-loader-tool/src/main/resources/META-INF/loader - diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/pom.xml b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/pom.xml deleted file mode 100644 index c9cd8935f..000000000 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/pom.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - com.alipay.sofa.serverless - sofa-serverless-base-loader - ${revision} - ../pom.xml - - 4.0.0 - - sofa-serverless-spring-loader-tool - ${revision} - - - 8 - 8 - - - - org.springframework.boot - spring-boot-loader - - - org.springframework.boot - spring-boot-loader-tools - ${spring.boot.version} - - - junit - junit - test - - - - org.mockito - mockito-core - test - - - - org.mockito - mockito-inline - test - - - - \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/java/com/alipay/sofa/serverless/spring/loader/tools/CustomLayoutFactory.java b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/java/com/alipay/sofa/serverless/spring/loader/tools/CustomLayoutFactory.java deleted file mode 100644 index d64b3132b..000000000 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/java/com/alipay/sofa/serverless/spring/loader/tools/CustomLayoutFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.serverless.spring.loader.tools; - -import java.io.File; - -import org.springframework.boot.loader.tools.Layout; -import org.springframework.boot.loader.tools.LayoutFactory; - -/** - * CustomLayoutFactory - * @author zjulbj - * @daye 2023/12/26 - * @version CustomLayoutFactory.java, v 0.1 2023年12月26日 14:45 syd - */ -public class CustomLayoutFactory implements LayoutFactory { - @Override - public Layout getLayout(File source) { - return Layouts.forFile(source); - } -} \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/java/com/alipay/sofa/serverless/spring/loader/tools/Layouts.java b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/java/com/alipay/sofa/serverless/spring/loader/tools/Layouts.java deleted file mode 100644 index cf650eb01..000000000 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/java/com/alipay/sofa/serverless/spring/loader/tools/Layouts.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.serverless.spring.loader.tools; - -import java.io.File; -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -import org.springframework.boot.loader.tools.CustomLoaderLayout; -import org.springframework.boot.loader.tools.Layout; -import org.springframework.boot.loader.tools.LibraryScope; -import org.springframework.boot.loader.tools.LoaderClassesWriter; - -/** - * Custom Layouts - * @author zjulbj - * @daye 2023/12/26 - * @version Layouts.java, v 0.1 2023年12月26日 14:45 syd - */ -public class Layouts { - private Layouts() { - } - - /** - * Return a layout for the given source file. - * - * @param file the source file - * @return a {@link Layout} - */ - public static Layout forFile(File file) { - if (file == null) { - throw new IllegalArgumentException("File must not be null"); - } - String lowerCaseFileName = file.getName().toLowerCase(Locale.ENGLISH); - if (lowerCaseFileName.endsWith(".jar")) { - return new Jar(); - } - return org.springframework.boot.loader.tools.Layouts.forFile(file); - } - - /** - * Executable JAR layout. - */ - public static class Jar extends org.springframework.boot.loader.tools.Layouts.Jar implements - CustomLoaderLayout { - @Override - public String getLauncherClassName() { - return "com.alipay.sofa.serverless.spring.loader.JarLauncher"; - } - - @Override - public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException { - writer.writeLoaderClasses("META-INF/loader/spring-boot-loader.jar"); - writer.writeLoaderClasses("META-INF/loader/sofa-serverless-spring-loader.jar"); - } - } -} \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/resources/META-INF/loader/sofa-serverless-spring-loader.jar b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/resources/META-INF/loader/sofa-serverless-spring-loader.jar deleted file mode 100644 index 239e58df0..000000000 Binary files a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/resources/META-INF/loader/sofa-serverless-spring-loader.jar and /dev/null differ diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/resources/META-INF/spring.factories b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/resources/META-INF/spring.factories deleted file mode 100644 index fb31a99b9..000000000 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.boot.loader.tools.LayoutFactory=com.alipay.sofa.serverless.spring.loader.tools.CustomLayoutFactory diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/test/java/com/alipay/sofa/serverless/spring/loader/tools/LayoutsTest.java b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/test/java/com/alipay/sofa/serverless/spring/loader/tools/LayoutsTest.java deleted file mode 100644 index e747dfc0c..000000000 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/test/java/com/alipay/sofa/serverless/spring/loader/tools/LayoutsTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.serverless.spring.loader.tools; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.jar.JarFile; - -import com.alipay.sofa.serverless.spring.loader.tools.Layouts.Jar; -import junit.framework.TestCase; -import org.junit.Assert; - -import org.springframework.boot.loader.tools.JarWriter; -import org.springframework.boot.loader.tools.Layout; - -public class LayoutsTest extends TestCase { - - public void testForNullFile() throws IOException { - IllegalArgumentException exception = null; - try { - Layouts.forFile(null); - } catch (IllegalArgumentException e) { - exception = e; - } - assertNotNull(exception); - } - - public void testForFile() throws IOException { - CustomLayoutFactory customLayoutFactory = new CustomLayoutFactory(); - - File appJar = new File(getClass().getClassLoader().getResource("demo-executable.jar") - .getFile()); - Layout layout = customLayoutFactory.getLayout(appJar); - assertTrue(layout instanceof Jar); - Jar jar = (Jar) layout; - assertEquals("com.alipay.sofa.serverless.spring.loader.JarLauncher", - jar.getLauncherClassName()); - File rewrite = new File(appJar.getParent() + "/demo-executable-rewrite.jar"); - JarWriter writer = new JarWriter(rewrite); - jar.writeLoadedClasses(writer); - writer.close(); - URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { rewrite.toURI().toURL() }, - null); - try { - urlClassLoader.loadClass(jar.getLauncherClassName()); - } catch (ClassNotFoundException e) { - Assert.fail(e.getMessage()); - } - } -} \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/test/resources/demo-executable.jar b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/test/resources/demo-executable.jar deleted file mode 100644 index 93dc8f4b4..000000000 Binary files a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader-tool/src/test/resources/demo-executable.jar and /dev/null differ diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/pom.xml b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/pom.xml deleted file mode 100644 index 6976efe48..000000000 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - com.alipay.sofa.serverless - sofa-serverless-base-loader - ${revision} - ../pom.xml - - 4.0.0 - sofa-serverless-spring-loader - ${revision} - - - 8 - 8 - - - - org.springframework.boot - spring-boot-loader - - - org.springframework.boot - spring-boot-loader-tools - ${spring.boot.version} - - - junit - junit - test - - - - org.mockito - mockito-core - test - - - - org.mockito - mockito-inline - test - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - ${java.version} - ${java.version} - UTF-8 - true - - - - - - \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/main/java/com/alipay/sofa/serverless/spring/loader/CachedLaunchedURLClassLoader.java b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/main/java/com/alipay/sofa/serverless/spring/loader/CachedLaunchedURLClassLoader.java deleted file mode 100644 index 40b422b60..000000000 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/main/java/com/alipay/sofa/serverless/spring/loader/CachedLaunchedURLClassLoader.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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.serverless.spring.loader; - -import java.io.IOException; -import java.net.URL; -import java.util.Collections; -import java.util.Enumeration; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; - -import org.springframework.boot.loader.LaunchedURLClassLoader; -import org.springframework.boot.loader.archive.Archive; - -/** - * A cached LaunchedURLClassLoader to accelerate load classes and resources - * @author zjulbj - * @daye 2023/12/26 - * @version CachedLaunchedURLClassLoader.java, v 0.1 2023年12月26日 14:45 syd - */ -public class CachedLaunchedURLClassLoader extends LaunchedURLClassLoader { - private static final int ENTRY_CACHE_SIZE = Integer.getInteger( - "serverless.class.cache.size", - 6000); - private static final Object NOT_FOUND = new Object(); - protected final Map classCache = Collections - .synchronizedMap(new LinkedHashMap( - ENTRY_CACHE_SIZE, 0.75f, - true) { - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() >= ENTRY_CACHE_SIZE; - } - }); - protected final Map> resourceUrlCache = Collections - .synchronizedMap(new LinkedHashMap>( - ENTRY_CACHE_SIZE, 0.75f, - true) { - @Override - protected boolean removeEldestEntry(Map.Entry> eldest) { - return size() >= ENTRY_CACHE_SIZE; - } - }); - protected final Map resourcesUrlCache = Collections - .synchronizedMap(new LinkedHashMap( - ENTRY_CACHE_SIZE, 0.75f, - true) { - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() >= ENTRY_CACHE_SIZE; - } - }); - - static { - ClassLoader.registerAsParallelCapable(); - } - - public CachedLaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, - ClassLoader parent) { - super(exploded, rootArchive, urls, parent); - } - - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - return loadClassWithCache(name, resolve); - } - - @Override - public URL findResource(String name) { - Optional urlOptional = resourceUrlCache.get(name); - if (urlOptional != null) { - return urlOptional.orElse(null); - } - URL url = super.findResource(name); - resourceUrlCache.put(name, url != null ? Optional.of(url) : Optional.empty()); - return url; - } - - @Override - public Enumeration findResources(String name) throws IOException { - Optional> urlOptional = resourcesUrlCache.get(name); - if (urlOptional != null) { - return urlOptional.orElse(null); - } - Enumeration enumeration = super.findResources(name); - if (enumeration == null || !enumeration.hasMoreElements()) { - resourcesUrlCache.put(name, Optional.empty()); - } - return enumeration; - } - - /** - * NOTE: Only cache ClassNotFoundException when class not found. - * If class found, do not cache, and just use parent class loader cache. - * - * @param name - * @param resolve - * @return - * @throws ClassNotFoundException - */ - protected Class loadClassWithCache(String name, boolean resolve) - throws ClassNotFoundException { - Object resultInCache = classCache.get(name); - if (resultInCache == NOT_FOUND) { - throw new ClassNotFoundException(name); - } - try { - Class clazz = super.findLoadedClass(name); - if (clazz == null) { - clazz = super.loadClass(name, resolve); - } - return clazz; - } catch (ClassNotFoundException exception) { - classCache.put(name, NOT_FOUND); - throw exception; - } - } - - @Override - public void clearCache() { - super.clearCache(); - classCache.clear(); - resourceUrlCache.clear(); - resourcesUrlCache.clear(); - } -} \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/java/com/alipay/sofa/serverless/spring/loader/CachedLaunchedURLClassLoaderTest.java b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/java/com/alipay/sofa/serverless/spring/loader/CachedLaunchedURLClassLoaderTest.java deleted file mode 100644 index db2a2a8dc..000000000 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/java/com/alipay/sofa/serverless/spring/loader/CachedLaunchedURLClassLoaderTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.serverless.spring.loader; - -import java.io.IOException; -import java.net.URL; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import junit.framework.TestCase; -import org.junit.Assert; - -public class CachedLaunchedURLClassLoaderTest extends TestCase { - - public void testLoadClass() throws IOException, ClassNotFoundException, InterruptedException { - - URL appJar = getClass().getClassLoader().getResource("jars/demo.jar"); - - System.setProperty("serverless.class.cache.size", "100"); - CachedLaunchedURLClassLoader loader = new CachedLaunchedURLClassLoader(false, null, - new URL[] { appJar }, null); - URL url = loader.getResource("com/example/demo/DemoApplication.class"); - assertNotNull(url); - assertEquals(loader.getResource("com/example/demo/DemoApplication.class"), url); - assertEquals(loader.loadClass("com.example.demo.DemoApplication"), - loader.loadClass("com.example.demo.DemoApplication")); - assertNull(loader.getResource("demo/ApplicationNotExist.class")); - assertTrue(!loader.getResources("demo/ApplicationNotExist.class").hasMoreElements()); - assertTrue(!loader.getResources("demo/ApplicationNotExist.class").hasMoreElements()); - - ClassNotFoundException ex = null; - ClassNotFoundException ex1 = null; - ClassNotFoundException ex2 = null; - ClassNotFoundException ex3 = null; - try { - loader.loadClass("demo.ApplicationNotExist"); - } catch (ClassNotFoundException exception) { - ex = exception; - } - try { - loader.loadClass("demo.ApplicationNotExist"); - } catch (ClassNotFoundException exception) { - ex1 = exception; - } - try { - loader.loadClass("demo.ApplicationNotExist"); - } catch (ClassNotFoundException exception) { - ex2 = exception; - } - try { - loader.clearCache(); - loader.loadClass("demo.ApplicationNotExist"); - } catch (ClassNotFoundException exception) { - ex3 = exception; - } - assertNotNull(ex); - assertNotNull(ex1); - assertNotNull(ex2); - assertNotNull(ex3); - - assertEquals(ex.getMessage(), ex1.getMessage()); - assertNotSame(ex, ex2); - assertNotSame(ex1, ex2); - assertNotSame(ex, ex3); - assertNotSame(ex1, ex3); - assertNotSame(ex2, ex3); - ExecutorService executor = new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS, - new LinkedBlockingQueue(100)); - CountDownLatch countDownLatch = new CountDownLatch(100); - for (int i = 0; i < 100; i++) { - final String className = "notExits" + i; - executor.submit(() -> { - try { - loader.loadClass(className); - } catch (ClassNotFoundException e) { - } - countDownLatch.countDown(); - }); - } - countDownLatch.await(); - Assert.assertEquals(99, loader.classCache.size()); - } -} \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/resources/jars/demo-executable.jar b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/resources/jars/demo-executable.jar deleted file mode 100644 index 93dc8f4b4..000000000 Binary files a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/resources/jars/demo-executable.jar and /dev/null differ diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/resources/jars/demo.jar b/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/resources/jars/demo.jar deleted file mode 100644 index fcb018d45..000000000 Binary files a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/resources/jars/demo.jar and /dev/null differ diff --git a/sofa-serverless-runtime/sofa-serverless-base-plugin/pom.xml b/sofa-serverless-runtime/sofa-serverless-base-plugin/pom.xml index 98ee27f17..f6209e8bd 100644 --- a/sofa-serverless-runtime/sofa-serverless-base-plugin/pom.xml +++ b/sofa-serverless-runtime/sofa-serverless-base-plugin/pom.xml @@ -51,13 +51,6 @@ mockito-core test - - - org.mockito - mockito-inline - test - - @@ -94,4 +87,4 @@ - \ No newline at end of file + diff --git a/sofa-serverless-runtime/sofa-serverless-base-plugin/src/main/java/com/alipay/sofa/serverless/plugin/manager/listener/StaticBatchInstallEventListener.java b/sofa-serverless-runtime/sofa-serverless-base-plugin/src/main/java/com/alipay/sofa/serverless/plugin/manager/listener/StaticBatchInstallEventListener.java index 26aa8d57b..f2ca4f446 100644 --- a/sofa-serverless-runtime/sofa-serverless-base-plugin/src/main/java/com/alipay/sofa/serverless/plugin/manager/listener/StaticBatchInstallEventListener.java +++ b/sofa-serverless-runtime/sofa-serverless-base-plugin/src/main/java/com/alipay/sofa/serverless/plugin/manager/listener/StaticBatchInstallEventListener.java @@ -37,8 +37,8 @@ /** * - * @author gouzhendong.gzd - * @version $Id: ApplicationContextEventListener, v 0.1 2023-11-21 11:26 gouzhendong.gzd Exp $ + * @author CodeNoobKingKc2 + * @version $Id: ApplicationContextEventListener, v 0.1 2023-11-21 11:26 CodeNoobKingKc2 Exp $ */ public class StaticBatchInstallEventListener implements ApplicationListener { diff --git a/sofa-serverless-runtime/sofa-serverless-base-plugin/src/test/java/com/alipay/sofa/serverless/plugin/manager/listener/StaticBatchInstallEventListenerTest.java b/sofa-serverless-runtime/sofa-serverless-base-plugin/src/test/java/com/alipay/sofa/serverless/plugin/manager/listener/StaticBatchInstallEventListenerTest.java index e554ba893..82efb2f3a 100644 --- a/sofa-serverless-runtime/sofa-serverless-base-plugin/src/test/java/com/alipay/sofa/serverless/plugin/manager/listener/StaticBatchInstallEventListenerTest.java +++ b/sofa-serverless-runtime/sofa-serverless-base-plugin/src/test/java/com/alipay/sofa/serverless/plugin/manager/listener/StaticBatchInstallEventListenerTest.java @@ -42,8 +42,8 @@ /** * - * @author gouzhendong.gzd - * @version $Id: ApplicationContextEventListenerTest, v 0.1 2023-11-21 11:32 gouzhendong.gzd Exp $ + * @author CodeNoobKingKc2 + * @version $Id: ApplicationContextEventListenerTest, v 0.1 2023-11-21 11:32 CodeNoobKingKc2 Exp $ */ @RunWith(MockitoJUnitRunner.class) public class StaticBatchInstallEventListenerTest { diff --git a/sofa-serverless-runtime/sofa-serverless-base-starter/pom.xml b/sofa-serverless-runtime/sofa-serverless-base-starter/pom.xml index a614ccb7a..7e1fa0e4f 100644 --- a/sofa-serverless-runtime/sofa-serverless-base-starter/pom.xml +++ b/sofa-serverless-runtime/sofa-serverless-base-starter/pom.xml @@ -16,19 +16,17 @@ com.alipay.sofa.serverless - sofa-serverless-adapter-apollo + sofa-serverless-adapter-log4j2 ${revision} - com.alipay.sofa.serverless - sofa-serverless-adapter-log4j2 + sofa-serverless-adapter-logback ${revision} - com.alipay.sofa.serverless - sofa-serverless-adapter-dubbo2.6 + sofa-serverless-adapter-apollo ${revision} diff --git a/sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/util/MultiBizProperties.java b/sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/util/MultiBizProperties.java new file mode 100644 index 000000000..3b91211ef --- /dev/null +++ b/sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/util/MultiBizProperties.java @@ -0,0 +1,450 @@ +/* + * 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.serverless.common.util; + +import java.io.*; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Support multi-business Properties
+ * Isolating configuration separation between different business modules
+ * The default value of the configuration of the base application is used
+ *

+ * If you want to use, you need to write the code in you base application + *

+ * + * MultiBizProperties.initSystem(); + * + */ +public class MultiBizProperties extends Properties { + + private final String bizClassLoaderName; + + private static final String BIZ_CLASS_LOADER = "com.alipay.sofa.ark.container.service.classloader.BizClassLoader"; + + private Map> modifiedKeysMap = new HashMap<>(); + + private final Properties baseProperties; + private Map bizPropertiesMap; + + private MultiBizProperties(String bizClassLoaderName, Properties baseProperties) { + this.bizPropertiesMap = new HashMap<>(); + this.baseProperties = baseProperties; + this.bizClassLoaderName = bizClassLoaderName; + } + + public MultiBizProperties(String bizClassLoaderName) { + this(bizClassLoaderName, new Properties()); + } + + public synchronized Object setProperty(String key, String value) { + addModifiedKey(key); + return getWriteProperties().setProperty(key, value); + } + + @Override + public String getProperty(String key) { + return getReadProperties().getProperty(key); + } + + @Override + public String getProperty(String key, String defaultValue) { + return getReadProperties().getProperty(key, defaultValue); + } + + @Override + public synchronized void load(Reader reader) throws IOException { + Properties properties = new Properties(); + properties.load(reader); + getWriteProperties().putAll(properties); + addModifiedKeys(properties.stringPropertyNames()); + } + + @Override + public synchronized void load(InputStream inStream) throws IOException { + Properties properties = new Properties(); + properties.load(inStream); + getWriteProperties().putAll(properties); + addModifiedKeys(properties.stringPropertyNames()); + } + + @Override + public void list(PrintStream out) { + getWriteProperties().list(out); + } + + @Override + public void list(PrintWriter out) { + getWriteProperties().list(out); + } + + @Override + public void save(OutputStream out, String comments) { + Properties properties = getWriteProperties(); + properties.save(out, comments); + } + + @Override + public void store(Writer writer, String comments) throws IOException { + Properties properties = getReadProperties(); + properties.store(writer, comments); + } + + @Override + public void store(OutputStream out, String comments) throws IOException { + Properties properties = getReadProperties(); + properties.store(out, comments); + } + + @Override + public synchronized void loadFromXML(InputStream in) throws IOException { + Properties properties = new Properties(); + properties.loadFromXML(in); + getWriteProperties().putAll(properties); + addModifiedKeys(properties.stringPropertyNames()); + } + + @Override + public void storeToXML(OutputStream os, String comment) throws IOException { + Properties properties = getReadProperties(); + properties.storeToXML(os, comment); + } + + @Override + public void storeToXML(OutputStream os, String comment, String encoding) throws IOException { + Properties properties = getReadProperties(); + properties.storeToXML(os, comment, encoding); + } + + @Override + public Enumeration propertyNames() { + return getReadProperties().propertyNames(); + } + + @Override + public Set stringPropertyNames() { + return getReadProperties().stringPropertyNames(); + } + + @Override + public synchronized boolean remove(Object key, Object value) { + boolean success = getWriteProperties().remove(key, value); + if (success) { + addModifiedKey(key.toString()); + } + return success; + } + + @Override + public synchronized Object get(Object key) { + return getReadProperties().get(key); + } + + @Override + public synchronized Object remove(Object key) { + if (key != null) { + addModifiedKey(key.toString()); + } + return getWriteProperties().remove(key); + } + + @Override + public synchronized Object put(Object key, Object value) { + String text = key == null ? null : key.toString(); + addModifiedKey(text); + return getWriteProperties().put(key, value); + } + + @Override + public synchronized boolean equals(Object o) { + return getReadProperties().equals(o); + } + + @Override + public synchronized String toString() { + return getReadProperties().toString(); + } + + @Override + public Collection values() { + return getReadProperties().values(); + } + + @Override + public synchronized int hashCode() { + return getReadProperties().hashCode(); + } + + @Override + public synchronized void clear() { + Set keys = baseProperties.stringPropertyNames(); + getWriteProperties().clear(); + addModifiedKeys(keys); + } + + @Override + public synchronized Object clone() { + MultiBizProperties mbp = new MultiBizProperties(bizClassLoaderName, baseProperties); + mbp.bizPropertiesMap = new HashMap<>(); + bizPropertiesMap.forEach((k, p) -> mbp.bizPropertiesMap.put(k, (Properties) p.clone())); + mbp.bizPropertiesMap.putAll(bizPropertiesMap); + mbp.modifiedKeysMap = new HashMap<>(); + modifiedKeysMap.forEach((k, s) -> mbp.modifiedKeysMap.put(k, new HashSet<>(s))); + return mbp; + } + + @Override + public synchronized boolean replace(Object key, Object oldValue, Object newValue) { + Object curValue = get(key); + if (!Objects.equals(curValue, oldValue) || (curValue == null && !containsKey(key))) { + return false; + } + put(key, newValue); + return true; + } + + @Override + public synchronized boolean isEmpty() { + return getReadProperties().isEmpty(); + } + + @Override + public synchronized Object replace(Object key, Object value) { + Object curValue; + if (((curValue = get(key)) != null) || containsKey(key)) { + curValue = put(key, value); + } + return curValue; + } + + @Override + public synchronized boolean containsKey(Object key) { + return getReadProperties().containsKey(key); + } + + @Override + public synchronized boolean contains(Object value) { + return getReadProperties().contains(value); + } + + @Override + public synchronized void replaceAll(BiFunction function) { + Map map = new HashMap(); + for (Map.Entry entry : entrySet()) { + Object k = entry.getKey(); + Object v = entry.getValue(); + v = function.apply(k, v); + map.put(k, v); + } + putAll(map); + } + + @Override + public synchronized int size() { + return getReadProperties().size(); + } + + @Override + public Set> entrySet() { + return getReadProperties().entrySet(); + } + + @Override + public synchronized void putAll(Map map) { + Set keys = new HashSet<>(); + for (Object key : map.keySet()) { + String text = key == null ? null : key.toString(); + keys.add(text); + } + addModifiedKeys(keys); + getWriteProperties().putAll(map); + } + + @Override + public synchronized Object computeIfAbsent(Object key, + Function mappingFunction) { + Object value = get(key); + if (value == null) { + Object newValue = mappingFunction.apply(key); + if (newValue != null) { + put(key, newValue); + return newValue; + } + } + return value; + } + + @Override + public synchronized Enumeration elements() { + return getReadProperties().elements(); + } + + @Override + public synchronized void forEach(BiConsumer action) { + getReadProperties().forEach(action); + } + + @Override + public synchronized Object putIfAbsent(Object key, Object value) { + Object v = get(key); + if (v == null) { + v = put(key, value); + } + return v; + } + + @Override + public synchronized Enumeration keys() { + return getReadProperties().keys(); + } + + @Override + public Set keySet() { + return getReadProperties().keySet(); + } + + @Override + public boolean containsValue(Object value) { + return getReadProperties().containsValue(value); + } + + @Override + public synchronized Object getOrDefault(Object key, Object defaultValue) { + return getReadProperties().getOrDefault(key, defaultValue); + } + + @Override + public synchronized Object computeIfPresent(Object key, + BiFunction remappingFunction) { + Object oldValue = get(key); + if (oldValue == null) { + return null; + } + Object newValue = remappingFunction.apply(key, oldValue); + if (newValue != null) { + put(key, newValue); + return newValue; + } + remove(key); + return null; + } + + @Override + public synchronized Object compute(Object key, + BiFunction remappingFunction) { + Object oldValue = get(key); + Object newValue = remappingFunction.apply(key, oldValue); + if (newValue == null) { + if (oldValue != null || containsKey(key)) { + remove(key); + } + return null; + } + put(key, newValue); + return newValue; + } + + @Override + public synchronized Object merge(Object key, Object value, + BiFunction remappingFunction) { + Object oldValue = get(key); + Object newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value); + if (newValue == null) { + remove(key); + } else { + put(key, newValue); + } + return newValue; + } + + private synchronized Properties getReadProperties() { + Properties bizProperties = getWriteProperties(); + if (bizProperties == baseProperties) { + return baseProperties; + } + Properties properties = new Properties(); + properties.putAll(baseProperties); + Set modifiedKeys = getModifiedKeys(); + if (modifiedKeys != null) { + modifiedKeys.forEach(properties::remove); + } + properties.putAll(bizProperties); + return properties; + } + + private synchronized Properties getWriteProperties() { + ClassLoader invokeClassLoader = Thread.currentThread().getContextClassLoader(); + if (bizPropertiesMap.containsKey(invokeClassLoader)) { + return bizPropertiesMap.get(invokeClassLoader); + } + for (ClassLoader classLoader = invokeClassLoader; classLoader != null; classLoader = classLoader.getParent()) { + String name = classLoader.getClass().getName(); + if (Objects.equals(name, bizClassLoaderName)) { + Properties props = bizPropertiesMap.computeIfAbsent(classLoader, k -> new Properties()); + bizPropertiesMap.put(invokeClassLoader, props); + return props; + } + } + bizPropertiesMap.put(invokeClassLoader, baseProperties); + return baseProperties; + } + + private synchronized Set getModifiedKeys() { + ClassLoader invokeClassLoader = Thread.currentThread().getContextClassLoader(); + if (modifiedKeysMap.containsKey(invokeClassLoader)) { + return modifiedKeysMap.get(invokeClassLoader); + } + for (ClassLoader classLoader = invokeClassLoader; classLoader != null; classLoader = classLoader.getParent()) { + String name = classLoader.getClass().getName(); + if (Objects.equals(name, bizClassLoaderName)) { + Set keys = modifiedKeysMap.computeIfAbsent(classLoader, k -> new HashSet<>()); + modifiedKeysMap.put(invokeClassLoader, keys); + return keys; + } + } + return null; + } + + private void addModifiedKey(String key) { + addModifiedKeys(Collections.singleton(key)); + } + + private void addModifiedKeys(Collection keys) { + Set modifiedKeys = getModifiedKeys(); + if (modifiedKeys != null && keys != null) { + modifiedKeys.addAll(keys); + } + } + + /** + * replace the system properties to multi biz properties
+ * if you want to use, you need invoke the method in base application + */ + public static void initSystem(String bizClassLoaderName) { + Properties properties = System.getProperties(); + MultiBizProperties multiBizProperties = new MultiBizProperties(bizClassLoaderName, + properties); + System.setProperties(multiBizProperties); + } + + public static void initSystem() { + initSystem(BIZ_CLASS_LOADER); + } +} diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/java/com/alipay/sofa/serverless/spring/loader/JarLauncherTest.java b/sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/util/OSUtils.java similarity index 60% rename from sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/java/com/alipay/sofa/serverless/spring/loader/JarLauncherTest.java rename to sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/util/OSUtils.java index 84c3b4d49..dae83627e 100644 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/test/java/com/alipay/sofa/serverless/spring/loader/JarLauncherTest.java +++ b/sofa-serverless-runtime/sofa-serverless-common/src/main/java/com/alipay/sofa/serverless/common/util/OSUtils.java @@ -14,17 +14,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.serverless.spring.loader; +package com.alipay.sofa.serverless.common.util; -import junit.framework.TestCase; +/** + * @author CodeNoobKing + * @data 2024/1/22 + */ +public class OSUtils { -public class JarLauncherTest extends TestCase { + static String OS_NAME_KEY = "os.name"; // 操作系统名称。 - public void test() throws Exception { - try { - JarLauncher.main(new String[] { "" }); - } catch (Exception e) { + /** + * 获取本地文件协议前缀。 + * + * @return 本地文件协议前缀。 + */ + public static String getLocalFileProtocolPrefix() { + String os = System.getProperty(OS_NAME_KEY); + if (os.toLowerCase().startsWith("win")) { + return "file:///"; + } else { + return "file://"; } } - -} \ No newline at end of file +} diff --git a/sofa-serverless-runtime/sofa-serverless-common/src/test/java/com/alipay/sofa/serverless/common/util/MultiBizPropertiesTest.java b/sofa-serverless-runtime/sofa-serverless-common/src/test/java/com/alipay/sofa/serverless/common/util/MultiBizPropertiesTest.java new file mode 100644 index 000000000..a4edfe6c4 --- /dev/null +++ b/sofa-serverless-runtime/sofa-serverless-common/src/test/java/com/alipay/sofa/serverless/common/util/MultiBizPropertiesTest.java @@ -0,0 +1,224 @@ +/* + * 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.serverless.common.util; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.*; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicInteger; + +public class MultiBizPropertiesTest { + private final String key1 = "test-key-1"; + private final String key2 = "test-key-2"; + + private final String key3 = "test-key-3"; + + private final String key4 = "test-key-4"; + private final String value1 = "test-value-1"; + private final String value2 = "test-value-2"; + + private final String value3 = "test-value-3"; + + private ClassLoader baseClassLoader; + + @Before + public void before() { + Thread thread = Thread.currentThread(); + baseClassLoader = thread.getContextClassLoader(); + + System.clearProperty(key1); + System.clearProperty(key2); + System.clearProperty(key3); + System.clearProperty(key4); + MultiBizProperties.initSystem(URLClassLoader.class.getName()); + } + + @After + public void after() { + Thread.currentThread().setContextClassLoader(baseClassLoader); + } + + @Test + public void testGetAndSet() { + //base: set key1=value1, base get key1=value1 + Thread thread = Thread.currentThread(); + thread.setContextClassLoader(baseClassLoader); + System.setProperty(key1, value1); + Assert.assertEquals(value1, System.getProperty(key1)); + //biz1: not set key1 value, biz1 get key1=value1 as base + ClassLoader loader1 = new URLClassLoader(new URL[0]); + thread.setContextClassLoader(loader1); + Assert.assertEquals(value1, System.getProperty(key1)); + //biz1: set key1=value2, biz1 get key1=value2 + System.setProperty(key1, value2); + Assert.assertEquals(value2, System.getProperty(key1)); + //base: still get key1=value1 + thread.setContextClassLoader(baseClassLoader); + Assert.assertEquals(value1, System.getProperty(key1)); + //biz2: not set key1 value, biz1 get key1=value1 as base + ClassLoader loader2 = new URLClassLoader(new URL[0]); + thread.setContextClassLoader(loader2); + Assert.assertEquals(value1, System.getProperty(key1)); + } + + @Test + public void testGetAndClear() { + //base: set key1=value1, base get key1=value1 + Thread thread = Thread.currentThread(); + thread.setContextClassLoader(baseClassLoader); + System.setProperty(key1, value1); + Assert.assertEquals(value1, System.getProperty(key1)); + //biz1: not set key1 value, biz1 get key1=value1 as base + ClassLoader loader1 = new URLClassLoader(new URL[0]); + thread.setContextClassLoader(loader1); + Assert.assertEquals(value1, System.getProperty(key1)); + //biz1: set key1=value2, biz1 remove key1,so biz1 get key1 is null + System.clearProperty(key1); + Assert.assertNull(System.getProperty(key1)); + //base: still get key1=value1 + thread.setContextClassLoader(baseClassLoader); + Assert.assertEquals(value1, System.getProperty(key1)); + //biz2: not set key1 value, biz1 get key1=value1 as base + ClassLoader loader2 = new URLClassLoader(new URL[0]); + thread.setContextClassLoader(loader2); + Assert.assertEquals(value1, System.getProperty(key1)); + //base: set key1=value2, base get key1=value2 + thread.setContextClassLoader(baseClassLoader); + System.setProperty(key1, value2); + Assert.assertEquals(value2, System.getProperty(key1)); + + //biz1: the key1 is removed, biz1 get key1 is null + thread.setContextClassLoader(loader1); + Assert.assertNull(System.getProperty(key1)); + + //biz2: not set key1 value, biz1 get key1=value1 as base + thread.setContextClassLoader(loader2); + Assert.assertEquals(value2, System.getProperty(key1)); + } + + @Test + public void testClone() { + Properties properties = System.getProperties(); + Properties other = (Properties) properties.clone(); + Assert.assertEquals(properties, other); + } + + @Test + public void testStoreAndLoad() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Properties properties = new MultiBizProperties(URLClassLoader.class.getName()); + properties.put(key1, value1); + properties.putAll(System.getProperties()); + int size = properties.size(); + properties.store(out, "test store"); + properties.clear(); + Assert.assertEquals(properties.size(), 0); + ByteArrayInputStream input = new ByteArrayInputStream(out.toByteArray()); + properties.load(input); + Assert.assertEquals(properties.size(), size); + + out = new ByteArrayOutputStream(); + properties.save(out, "test store"); + properties.clear(); + Assert.assertEquals(properties.size(), 0); + input = new ByteArrayInputStream(out.toByteArray()); + properties.load(input); + Assert.assertEquals(properties.size(), size); + + out = new ByteArrayOutputStream(); + properties.storeToXML(out, "test store"); + properties.clear(); + Assert.assertEquals(properties.size(), 0); + // System.out.println(out); + input = new ByteArrayInputStream(out.toByteArray()); + try { + properties.loadFromXML(input); + } catch (Throwable e) { + e.printStackTrace(); + } + + Assert.assertEquals(properties.size(), size); + Assert.assertTrue(properties.containsKey(key1)); + Assert.assertTrue(properties.contains(value1)); + + out = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter(out); + properties.list(writer); + writer.close(); + properties.clear(); + Assert.assertTrue(properties.isEmpty()); + + input = new ByteArrayInputStream(out.toByteArray()); + Reader reader = new InputStreamReader(input); + properties.load(reader); + reader.close(); + Assert.assertFalse(properties.isEmpty()); + } + + @Test + public void testRead() { + Properties properties = new MultiBizProperties(URLClassLoader.class.getName()); + properties.setProperty(key1, value1); + Assert.assertEquals(properties.keys().nextElement(), key1); + Assert.assertTrue(properties.containsValue(value1)); + Assert.assertEquals(properties.elements().nextElement(), value1); + + AtomicInteger num = new AtomicInteger(); + properties.forEach((k, v) -> { + Assert.assertEquals(k, key1); + Assert.assertEquals(v, value1); + num.incrementAndGet(); + }); + Assert.assertEquals(num.get(), 1); + + Assert.assertEquals(properties.getOrDefault(key1, value2), value1); + Assert.assertEquals(properties.getOrDefault(key2, value2), value2); + + + Assert.assertEquals(properties.putIfAbsent(key1, value2), value1); + Assert.assertNull(properties.putIfAbsent(key2, value2)); + + Assert.assertEquals(properties.computeIfAbsent(key3, k -> value1), value1); + Assert.assertEquals(properties.computeIfPresent(key3, (k, v) -> v + value2), value1 + value2); + Assert.assertEquals(properties.computeIfPresent(key3, (k, v) -> null), null); + Assert.assertEquals(properties.computeIfPresent(key4, (k, v) -> v + value2), null); + + + properties.setProperty(key3, value1); + Assert.assertEquals(properties.compute(key3, (k, v) -> v + value2), value1 + value2); + Assert.assertEquals(properties.compute(key3, (k, v) -> null), null); + } + + @Test + public void testReplace() { + Properties properties = new MultiBizProperties(URLClassLoader.class.getName()); + properties.setProperty(key1, value1); + properties.replace(key1, value2, value3); + Assert.assertNotEquals(properties.get(key1), value3); + properties.replace(key1, value1, value3); + Assert.assertEquals(properties.get(key1), value3); + Assert.assertEquals(properties.replace(key1, value2), value3); + properties.replaceAll((k, v) -> v + value1); + Assert.assertEquals(properties.get(key1), value2 + value1); + } +} \ No newline at end of file diff --git a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/main/java/com/alipay/sofa/serverless/spring/loader/JarLauncher.java b/sofa-serverless-runtime/sofa-serverless-common/src/test/java/com/alipay/sofa/serverless/common/util/OSUtilsTest.java similarity index 50% rename from sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/main/java/com/alipay/sofa/serverless/spring/loader/JarLauncher.java rename to sofa-serverless-runtime/sofa-serverless-common/src/test/java/com/alipay/sofa/serverless/common/util/OSUtilsTest.java index 2c539e82e..470226abf 100644 --- a/sofa-serverless-runtime/sofa-serverless-base-loader/sofa-serverless-spring-loader/src/main/java/com/alipay/sofa/serverless/spring/loader/JarLauncher.java +++ b/sofa-serverless-runtime/sofa-serverless-common/src/test/java/com/alipay/sofa/serverless/common/util/OSUtilsTest.java @@ -14,30 +14,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.serverless.spring.loader; +package com.alipay.sofa.serverless.common.util; -/** - * - * @author syd - * @version JarLauncher.java, v 0.1 2023年12月26日 14:54 syd - */ -import java.net.URL; +import org.junit.Assert; +import org.junit.Test; /** - * A JarLauncher to load classes with CachedLaunchedURLClassLoader - * - * @author zjulbj - * @daye 2023/12/26 - * @author bingjie.lbj + * @author CodeNoobKing + * @data 2024/1/22 */ -public class JarLauncher extends org.springframework.boot.loader.JarLauncher { - public static void main(String[] args) throws Exception { - new JarLauncher().launch(args); - } +public class OSUtilsTest { + + @Test + public void testGetLocalFileProtocolPrefix() { + try { + OSUtils.OS_NAME_KEY = "mock.os.name"; + System.setProperty("mock.os.name", "Windows 7"); + Assert.assertEquals("file:///", OSUtils.getLocalFileProtocolPrefix()); + + System.setProperty("mock.os.name", "Linux"); + Assert.assertEquals("file://", OSUtils.getLocalFileProtocolPrefix()); + + System.setProperty("mock.os.name", "Mac OS X"); + Assert.assertEquals("file://", OSUtils.getLocalFileProtocolPrefix()); + } finally { + OSUtils.OS_NAME_KEY = "os.name"; + } - @Override - protected ClassLoader createClassLoader(URL[] urls) throws Exception { - return new CachedLaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass() - .getClassLoader()); } }