diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 4b7ce349ce1e1d24322992a26f6d2abac5f9beee..5657c7b94a454d711f460582c209ec8a1ec18277 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -5,9 +5,11 @@ name: CI on: push: - branches: [ master ] + branches: + - '**' pull_request: - branches: [ master ] + branches: + - '**' jobs: build: @@ -16,10 +18,11 @@ jobs: matrix: jdk: [8, 11] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: + distribution: 'temurin' java-version: ${{ matrix.jdk }} - name: Install Zookeeper run: echo "Install Zookeeper 3.5.6" @@ -32,7 +35,7 @@ jobs: && sh ./tools/check_format.sh && mvn clean test -Pdefault - name: Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4fc69e1f2f780faca083159aa191d2567499368..b5996891308156c8a87c48c2af4c2b98f43ea49c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,9 +11,9 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' distribution: 'temurin' diff --git a/pom.xml b/pom.xml index e27558c29836a7d38c0fc20e595a7f6644f67135..a0accd6aeff6b9b9c4dde1da99e953cf4380602a 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.10 + 2.7.18 @@ -37,7 +37,7 @@ SOFABoot Build - 3.19.1 + 3.23.0 ${revision} 1.6.7 diff --git a/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/startup/StartupHealthCheckStageConfiguration.java b/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/startup/StartupHealthCheckStageConfiguration.java index c9c8fba479428bc637dbacf6794fc01b6b043484..fb98e97a9c7aa8a46538e3c84a2f5bd53058927f 100644 --- a/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/startup/StartupHealthCheckStageConfiguration.java +++ b/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/startup/StartupHealthCheckStageConfiguration.java @@ -29,7 +29,6 @@ import com.alipay.sofa.startup.stage.healthcheck.StartupReadinessCheckListener; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -45,7 +44,7 @@ public class StartupHealthCheckStageConfiguration { @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Bean - @ConditionalOnMissingBean(value = ReadinessCheckListener.class, search = SearchStrategy.CURRENT) + @ConditionalOnMissingBean(value = ReadinessCheckListener.class) public StartupReadinessCheckListener startupReadinessCheckListener(Environment environment, HealthCheckerProcessor healthCheckerProcessor, HealthIndicatorProcessor healthIndicatorProcessor, diff --git a/sofa-boot-project/sofa-boot-actuator/src/main/java/com/alipay/sofa/boot/actuator/beans/IsleBeansEndpoint.java b/sofa-boot-project/sofa-boot-actuator/src/main/java/com/alipay/sofa/boot/actuator/beans/IsleBeansEndpoint.java index 70d0461916dea4643de3249643763cabcdabab37..49ebb5de1f57cfb007b703a92dcd1c0594cd40c9 100644 --- a/sofa-boot-project/sofa-boot-actuator/src/main/java/com/alipay/sofa/boot/actuator/beans/IsleBeansEndpoint.java +++ b/sofa-boot-project/sofa-boot-actuator/src/main/java/com/alipay/sofa/boot/actuator/beans/IsleBeansEndpoint.java @@ -57,19 +57,33 @@ public class IsleBeansEndpoint extends BeansEndpoint { ApplicationBeans applicationBeans = super.beans(); ApplicationRuntimeModel applicationRuntimeModel = context.getBean( SofaBootConstants.APPLICATION, ApplicationRuntimeModel.class); - Map moduleApplicationContexts = getModuleApplicationContexts(applicationRuntimeModel); + + String springParentId = null; + for (Map.Entry entry : applicationBeans.getContexts().entrySet()) { + if (entry.getValue().getParentId() == null) { + springParentId = entry.getKey(); + break; + } + } + + Map moduleApplicationContexts = getModuleApplicationContexts( + applicationRuntimeModel, springParentId); applicationBeans.getContexts().putAll(moduleApplicationContexts); return applicationBeans; } - private Map getModuleApplicationContexts(ApplicationRuntimeModel applicationRuntimeModel) { + private Map getModuleApplicationContexts(ApplicationRuntimeModel applicationRuntimeModel,String springParentId) { Map contexts = new HashMap<>(); List installedModules = applicationRuntimeModel.getInstalled(); installedModules.forEach(descriptor -> { ApplicationContext applicationContext = descriptor.getApplicationContext(); + String parentId = descriptor.getSpringParent(); + if (parentId == null){ + parentId = springParentId; + } if (applicationContext instanceof ConfigurableApplicationContext) { ContextBeans contextBeans = describing((ConfigurableApplicationContext) applicationContext, - descriptor.getSpringParent()); + parentId); if (contextBeans != null) { contexts.put(descriptor.getModuleName(), contextBeans); } diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaRpcAutoConfiguration.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaRpcAutoConfiguration.java index b32226369c077f854bdf1ee0f120fd34947a718a..a60ee4b1da51927084e186614e1eb73eab6d66fb 100644 --- a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaRpcAutoConfiguration.java +++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/rpc/SofaRpcAutoConfiguration.java @@ -77,8 +77,13 @@ import java.util.Map; public class SofaRpcAutoConfiguration { @Bean @ConditionalOnMissingBean - public ProviderConfigContainer providerConfigContainer() { - return new ProviderConfigContainer(); + public ProviderConfigContainer providerConfigContainer(SofaBootRpcProperties sofaBootRpcProperties) { + ProviderConfigContainer providerConfigContainer = new ProviderConfigContainer(); + providerConfigContainer.setProviderRegisterWhiteList(sofaBootRpcProperties + .getProviderRegisterWhiteList()); + providerConfigContainer.setProviderRegisterBlackList(sofaBootRpcProperties + .getProviderRegisterBlackList()); + return providerConfigContainer; } @Bean diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/startup/SofaStartupIsleAutoConfiguration.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/startup/SofaStartupIsleAutoConfiguration.java index a6b51be327ac01390ea62cd2609ca1f8e2b73ed5..675313d667e2df20ae8740e248afb3da1d794c6b 100644 --- a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/startup/SofaStartupIsleAutoConfiguration.java +++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/startup/SofaStartupIsleAutoConfiguration.java @@ -29,7 +29,6 @@ import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -46,7 +45,7 @@ import org.springframework.context.support.AbstractApplicationContext; public class SofaStartupIsleAutoConfiguration { @Bean - @ConditionalOnMissingBean(value = SpringContextInstallStage.class, search = SearchStrategy.CURRENT) + @ConditionalOnMissingBean(value = SpringContextInstallStage.class) public StartupSpringContextInstallStage startupSpringContextInstallStage(ApplicationContext applicationContext, SofaModuleProperties sofaModuleProperties, StartupReporter startupReporter) { @@ -55,7 +54,7 @@ public class SofaStartupIsleAutoConfiguration { } @Bean - @ConditionalOnMissingBean(value = ModelCreatingStage.class, search = SearchStrategy.CURRENT) + @ConditionalOnMissingBean(value = ModelCreatingStage.class) public StartupModelCreatingStage startupModelCreatingStage(ApplicationContext applicationContext, SofaModuleProperties sofaModuleProperties, SofaModuleProfileChecker sofaModuleProfileChecker, diff --git a/sofa-boot-project/sofa-boot-core/healthcheck-sofa-boot/src/main/java/com/alipay/sofa/healthcheck/HealthCheckerProcessor.java b/sofa-boot-project/sofa-boot-core/healthcheck-sofa-boot/src/main/java/com/alipay/sofa/healthcheck/HealthCheckerProcessor.java index 2b441290453447e281f95f2491d602db2d46e1eb..aed67769f26dd3ff5ffed8269a23d9c6097c6db0 100644 --- a/sofa-boot-project/sofa-boot-core/healthcheck-sofa-boot/src/main/java/com/alipay/sofa/healthcheck/HealthCheckerProcessor.java +++ b/sofa-boot-project/sofa-boot-core/healthcheck-sofa-boot/src/main/java/com/alipay/sofa/healthcheck/HealthCheckerProcessor.java @@ -158,12 +158,12 @@ public class HealthCheckerProcessor implements ApplicationContextAware { String checkComponentNames = readinessHealthCheckers.values().stream() .map(HealthChecker::getComponentName).collect(Collectors.joining(",")); logger.info("SOFABoot HealthChecker readiness check {} item: {}.", - healthCheckers.size(), checkComponentNames); + readinessHealthCheckers.size(), checkComponentNames); boolean result; if (healthCheckProperties.isHealthCheckParallelEnable()) { - CountDownLatch countDownLatch = new CountDownLatch(healthCheckers.size()); + CountDownLatch countDownLatch = new CountDownLatch(readinessHealthCheckers.size()); AtomicBoolean parallelResult = new AtomicBoolean(true); - healthCheckers.forEach((String key, HealthChecker value) -> healthCheckExecutor.executeTask(() -> { + readinessHealthCheckers.forEach((String key, HealthChecker value) -> healthCheckExecutor.executeTask(() -> { try { if (!doHealthCheck(key, value, false, healthMap, true, false)) { parallelResult.set(false); diff --git a/sofa-boot-project/sofa-boot-core/healthcheck-sofa-boot/src/main/java/com/alipay/sofa/healthcheck/ReadinessCheckListener.java b/sofa-boot-project/sofa-boot-core/healthcheck-sofa-boot/src/main/java/com/alipay/sofa/healthcheck/ReadinessCheckListener.java index 8b5c86a14631c77a96df9830d7de5834ec7a9ae0..8efd79618183a53d2c33b445cbdd37284163cb3f 100644 --- a/sofa-boot-project/sofa-boot-core/healthcheck-sofa-boot/src/main/java/com/alipay/sofa/healthcheck/ReadinessCheckListener.java +++ b/sofa-boot-project/sofa-boot-core/healthcheck-sofa-boot/src/main/java/com/alipay/sofa/healthcheck/ReadinessCheckListener.java @@ -40,6 +40,7 @@ import org.springframework.util.StringUtils; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @@ -71,11 +72,11 @@ public class ReadinessCheckListener implements ApplicationContextAware, Ordered, private boolean healthCheckerStatus = true; - private Map healthCheckerDetails = new HashMap<>(); + private Map healthCheckerDetails = new ConcurrentHashMap<>(); private boolean healthIndicatorStatus = true; - private Map healthIndicatorDetails = new HashMap<>(); + private Map healthIndicatorDetails = new ConcurrentHashMap<>(); private boolean healthCallbackStatus = true; private boolean readinessCheckFinish = false; diff --git a/sofa-boot-project/sofa-boot-core/isle-sofa-boot/src/main/java/com/alipay/sofa/isle/stage/ModuleLogOutputStage.java b/sofa-boot-project/sofa-boot-core/isle-sofa-boot/src/main/java/com/alipay/sofa/isle/stage/ModuleLogOutputStage.java index 9e9b4772456cb694dac188c0c1b98a85d2ab1f4c..574e7bef875cdf6ba9f1a565842274a8f8a331d7 100644 --- a/sofa-boot-project/sofa-boot-core/isle-sofa-boot/src/main/java/com/alipay/sofa/isle/stage/ModuleLogOutputStage.java +++ b/sofa-boot-project/sofa-boot-core/isle-sofa-boot/src/main/java/com/alipay/sofa/isle/stage/ModuleLogOutputStage.java @@ -147,6 +147,7 @@ public class ModuleLogOutputStage extends AbstractPipelineStage { sb.append(prefix).append("[Module] ").append(dd.getName()).append(" [") .append(dd.getElapsedTime()).append(" ms]\n"); sb.append(((BeanLoadCostBeanFactory) beanFactory).outputBeanStats(indexPrefix)); + ((BeanLoadCostBeanFactory) beanFactory).clearParentStackThreadLocal(); } } stringBuilder.append(" [totalTime = ").append(totalTime).append(" ms, realTime = ") diff --git a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/pom.xml b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/pom.xml index a2bfc2c882b428e0b0f165a22070a228edbb9faf..bb491a79140504eb60234a6ccb5c1768493ce903 100644 --- a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/pom.xml +++ b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/pom.xml @@ -16,6 +16,7 @@ ${basedir}/../../.. 0.0.3 3.7.0 + 3.17.3 @@ -193,7 +194,7 @@ protobuf-maven-plugin 0.5.1 - com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier} + com.google.protobuf:protoc:${protobuf.protoc.version}:exe:${os.detected.classifier} grpc-java io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} build/generated/source/proto/test/java diff --git a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/common/RpcThreadPoolMonitor.java b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/common/RpcThreadPoolMonitor.java index 8d654f2db975ecd7e82f3e5980a7e446a3a40536..8d5c85e05f79f6a70ff50392d08a63145cec7b1e 100644 --- a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/common/RpcThreadPoolMonitor.java +++ b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/common/RpcThreadPoolMonitor.java @@ -82,6 +82,8 @@ public class RpcThreadPoolMonitor { StringBuilder sb = new StringBuilder(); sb.append("coreSize:" + threadPoolExecutor.getCorePoolSize() + ","); sb.append("maxPoolSize:" + threadPoolExecutor.getMaximumPoolSize() + ","); + sb.append("queueRemainingSize:" + + threadPoolExecutor.getQueue().remainingCapacity() + ","); sb.append("keepAliveTime:" + threadPoolExecutor.getKeepAliveTime(TimeUnit.MILLISECONDS) + "\n"); diff --git a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/config/SofaBootRpcProperties.java b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/config/SofaBootRpcProperties.java index f0d56f4c78163a4d325dcc3d7274205f7e292e23..f640cb74bde2279f48cea31a1136f2a1ddf91c88 100644 --- a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/config/SofaBootRpcProperties.java +++ b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/config/SofaBootRpcProperties.java @@ -24,6 +24,7 @@ import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -341,6 +342,10 @@ public class SofaBootRpcProperties implements EnvironmentAware { */ private String dynamicConfig; + private List providerRegisterWhiteList; + + private List providerRegisterBlackList; + public String getAftRegulationEffective() { return StringUtils.isEmpty(aftRegulationEffective) ? getDotString(new Object() { }.getClass().getEnclosingMethod().getName()) : aftRegulationEffective; @@ -938,6 +943,22 @@ public class SofaBootRpcProperties implements EnvironmentAware { this.boltProcessInIoThread = boltProcessInIoThread; } + public List getProviderRegisterWhiteList() { + return providerRegisterWhiteList; + } + + public void setProviderRegisterWhiteList(List providerRegisterWhiteList) { + this.providerRegisterWhiteList = providerRegisterWhiteList; + } + + public List getProviderRegisterBlackList() { + return providerRegisterBlackList; + } + + public void setProviderRegisterBlackList(List providerRegisterBlackList) { + this.providerRegisterBlackList = providerRegisterBlackList; + } + @Override public void setEnvironment(Environment environment) { this.environment = environment; diff --git a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/container/ProviderConfigContainer.java b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/container/ProviderConfigContainer.java index aa47214f5fd07ef16ebc64aad0148ab366520c95..582ba3fcfac8a9cb9c1db7ded55848ee95b976ef 100644 --- a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/container/ProviderConfigContainer.java +++ b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/container/ProviderConfigContainer.java @@ -16,14 +16,6 @@ */ package com.alipay.sofa.rpc.boot.container; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.slf4j.Logger; -import org.springframework.util.StringUtils; - import com.alipay.sofa.rpc.boot.config.SofaBootRpcConfigConstants; import com.alipay.sofa.rpc.boot.log.SofaBootRpcLoggerFactory; import com.alipay.sofa.rpc.boot.runtime.binding.RpcBinding; @@ -33,6 +25,14 @@ import com.alipay.sofa.rpc.config.ServerConfig; import com.alipay.sofa.rpc.registry.Registry; import com.alipay.sofa.rpc.registry.RegistryFactory; import com.alipay.sofa.runtime.spi.binding.Contract; +import org.slf4j.Logger; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; /** * ProviderConfig持有者.维护编程界面级别的RPC组件。 @@ -48,6 +48,10 @@ public class ProviderConfigContainer { */ private boolean allowPublish = false; + private List providerRegisterWhiteList; + + private List providerRegisterBlackList; + /** * ProviderConfig 缓存 */ @@ -73,6 +77,23 @@ public class ProviderConfigContainer { } } + private boolean allowProviderRegister(ProviderConfig providerConfig) { + if (CollectionUtils.isEmpty(providerRegisterWhiteList) + && CollectionUtils.isEmpty(providerRegisterBlackList)) { + return true; + } + String uniqueName = createUniqueNameByProvider(providerConfig); + if (!CollectionUtils.isEmpty(providerRegisterBlackList) + && providerRegisterBlackList.contains(uniqueName)) { + return false; + } + if (!CollectionUtils.isEmpty(providerRegisterWhiteList) + && !providerRegisterWhiteList.contains(uniqueName)) { + return false; + } + return true; + } + /** * 获取 ProviderConfig * @@ -110,7 +131,11 @@ public class ProviderConfigContainer { ServerConfig serverConfig = (ServerConfig) providerConfig.getServer().get(0); if (!serverConfig.getProtocol().equalsIgnoreCase( SofaBootRpcConfigConstants.RPC_PROTOCOL_DUBBO)) { - providerConfig.setRegister(true); + if (allowProviderRegister(providerConfig)) { + providerConfig.setRegister(true); + } else { + LOGGER.info("Provider will not register: [{}]", providerConfig.buildKey()); + } List registrys = providerConfig.getRegistry(); for (RegistryConfig registryConfig : registrys) { @@ -206,4 +231,30 @@ public class ProviderConfigContainer { .append(uniqueId).append(protocol).toString(); } + /** + * Create UniqueName by interfaceId and uniqueId + */ + private String createUniqueNameByProvider(ProviderConfig providerConfig) { + String uniqueId = ""; + if (StringUtils.hasText(providerConfig.getUniqueId())) { + uniqueId = ":" + providerConfig.getUniqueId(); + } + return providerConfig.getInterfaceId() + uniqueId; + } + + public void setProviderRegisterWhiteList(List providerRegisterWhiteList) { + this.providerRegisterWhiteList = providerRegisterWhiteList; + } + + public void setProviderRegisterBlackList(List providerRegisterBlackList) { + this.providerRegisterBlackList = providerRegisterBlackList; + } + + public List getProviderRegisterWhiteList() { + return providerRegisterWhiteList; + } + + public List getProviderRegisterBlackList() { + return providerRegisterBlackList; + } } diff --git a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/runtime/converter/RpcBindingConverter.java b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/runtime/converter/RpcBindingConverter.java index 05dbbaa1f60bb39394da7906f30addbf78a32ca2..08ef1c5fbdead1feb8d571af57e727ba45505bf9 100644 --- a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/runtime/converter/RpcBindingConverter.java +++ b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/main/java/com/alipay/sofa/rpc/boot/runtime/converter/RpcBindingConverter.java @@ -455,7 +455,7 @@ public abstract class RpcBindingConverter implements BindingConverter sampleFacadeB.sayHi("Sofa")).isInstanceOf(SofaRouteException.class). + hasMessageContaining("RPC-020060001"); + } +} diff --git a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/test/java/com/alipay/sofa/rpc/boot/test/provider/ProviderConfigContainerTestBase.java b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/test/java/com/alipay/sofa/rpc/boot/test/provider/ProviderConfigContainerTestBase.java new file mode 100644 index 0000000000000000000000000000000000000000..d22a3c1ba60edb4ce236385d299c05ed30b764a8 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/test/java/com/alipay/sofa/rpc/boot/test/provider/ProviderConfigContainerTestBase.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.rpc.boot.test.provider; + +import com.alipay.sofa.rpc.boot.container.ProviderConfigContainer; +import com.alipay.sofa.rpc.boot.test.bean.SampleFacade; +import com.alipay.sofa.rpc.boot.test.bean.SampleFacadeImpl; +import com.alipay.sofa.runtime.api.annotation.SofaReference; +import com.alipay.sofa.runtime.api.annotation.SofaReferenceBinding; +import com.alipay.sofa.runtime.api.annotation.SofaService; +import com.alipay.sofa.runtime.api.annotation.SofaServiceBinding; +import org.junit.After; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author huzijie + * @version ProviderConfigContainerTests.java, v 0.1 2023年09月27日 11:14 AM huzijie Exp $ + */ +@SpringBootApplication +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) +@Import(ProviderConfigContainerTestBase.RpcPublishConfiguration.class) +public class ProviderConfigContainerTestBase { + + @SofaReference(jvmFirst = false, binding = @SofaReferenceBinding(bindingType = "bolt")) + protected SampleFacade sampleFacadeA; + + @SofaReference(jvmFirst = false, binding = @SofaReferenceBinding(bindingType = "bolt"), uniqueId = "uniqueId") + protected SampleFacade sampleFacadeB; + + @Autowired + private ProviderConfigContainer providerConfigContainer; + + @After + public void clearExported() { + providerConfigContainer.unExportAllProviderConfig(); + } + + @Configuration + static class RpcPublishConfiguration { + + @SofaService(bindings = { @SofaServiceBinding(bindingType = "bolt") }) + @Bean + public SampleFacade providerA() { + return new SampleFacadeImpl(); + } + + @SofaService(bindings = { @SofaServiceBinding(bindingType = "bolt") }, uniqueId = "uniqueId") + @Bean + public SampleFacade providerB() { + return new SampleFacadeImpl(); + } + } + +} diff --git a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/test/java/com/alipay/sofa/rpc/boot/test/provider/WhiteListProviderConfigContainerTests.java b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/test/java/com/alipay/sofa/rpc/boot/test/provider/WhiteListProviderConfigContainerTests.java new file mode 100644 index 0000000000000000000000000000000000000000..dc2e83191c06b415095a626dbd4636ed8bb64b22 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/test/java/com/alipay/sofa/rpc/boot/test/provider/WhiteListProviderConfigContainerTests.java @@ -0,0 +1,37 @@ +/* + * 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.rpc.boot.test.provider; + +import com.alipay.sofa.rpc.core.exception.SofaRouteException; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.springframework.test.context.TestPropertySource; + +/** + * @author huzijie + * @version WhiteListProviderConfigContainerTests.java, v 0.1 2023年09月27日 11:27 AM huzijie Exp $ + */ +@TestPropertySource(properties = "com.alipay.sofa.rpc.providerRegisterWhiteList=com.alipay.sofa.rpc.boot.test.bean.SampleFacade:uniqueId") +public class WhiteListProviderConfigContainerTests extends ProviderConfigContainerTestBase { + + @Test + public void checkProviderExported() { + Assertions.assertThatThrownBy(() -> sampleFacadeA.sayHi("Sofa")).isInstanceOf(SofaRouteException.class). + hasMessageContaining("RPC-020060001"); + Assertions.assertThat(sampleFacadeB.sayHi("Sofa")).isEqualTo("hi Sofa!"); + } +} diff --git a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/test/resources/spring/test_all.xml b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/test/resources/spring/test_all.xml index f8914724dab3f9168af85e8ff1d787b384b6a030..6be043ae2ecf227196d9f7685afed16bf1782fa1 100644 --- a/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/test/resources/spring/test_all.xml +++ b/sofa-boot-project/sofa-boot-core/rpc-sofa-boot/src/test/resources/spring/test_all.xml @@ -106,6 +106,10 @@ interface="com.alipay.sofa.rpc.boot.test.bean.threadpool.ThreadPoolService"> + + + diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/SofaRuntimeProperties.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/SofaRuntimeProperties.java index b3a20c589477eb59b4a8fac8dfcde6623f2819b5..8c215b13e33e64289fd8a93b333240e69c7250f9 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/SofaRuntimeProperties.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/SofaRuntimeProperties.java @@ -41,10 +41,9 @@ public class SofaRuntimeProperties { private static boolean jvmFilterEnable = false; private static boolean serviceInterfaceTypeCheck = false; private static boolean dynamicJvmServiceCacheEnable = false; - private static boolean serviceNameWithBeanId = false; - private static boolean referenceHealthCheckMoreDetailEnable = false; + private static boolean serviceCanBeDuplicate = true; public static boolean isManualReadinessCallback(ClassLoader classLoader) { return manualReadinessCallbackMap.get(classLoader) != null @@ -192,4 +191,12 @@ public class SofaRuntimeProperties { public static void setReferenceHealthCheckMoreDetailEnable(boolean referenceHealthCheckMoreDetailEnable) { SofaRuntimeProperties.referenceHealthCheckMoreDetailEnable = referenceHealthCheckMoreDetailEnable; } + + public static boolean isServiceCanBeDuplicate() { + return serviceCanBeDuplicate; + } + + public static void setServiceCanBeDuplicate(boolean serviceCanBeDuplicate) { + SofaRuntimeProperties.serviceCanBeDuplicate = serviceCanBeDuplicate; + } } diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/compatibility/CompatibilityVerifierApplicationContextInitializer.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/compatibility/CompatibilityVerifierApplicationContextInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..7d901ece96175e7985fa1fd151c732aa3bf05af7 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/compatibility/CompatibilityVerifierApplicationContextInitializer.java @@ -0,0 +1,92 @@ +/* + * 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.runtime.compatibility; + +import com.alipay.sofa.boot.compatibility.CompatibilityVerifier; +import com.alipay.sofa.boot.compatibility.CompositeCompatibilityVerifier; +import com.alipay.sofa.runtime.log.SofaLogger; +import org.springframework.beans.BeanUtils; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.env.Environment; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * Implements for {@link ApplicationContextInitializer} to verify compatibilities. + * + * @author huzijie + * @version CompatibilityVerifierApplicationContextInitializer.java, v 0.1 2023年08月03日 4:44 PM huzijie Exp $ + */ +public class CompatibilityVerifierApplicationContextInitializer + implements + ApplicationContextInitializer { + + public static final String COMPATIBILITY_VERIFIER_ENABLED = "sofa.boot.compatibility-verifier.enabled"; + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + Environment environment = applicationContext.getEnvironment(); + if (!Boolean.parseBoolean(environment.getProperty(COMPATIBILITY_VERIFIER_ENABLED, "true"))) { + SofaLogger.info("Skip SOFABoot compatibility Verifier"); + return; + } + + // Load all CompatibilityVerifier and verify. + CompositeCompatibilityVerifier compositeCompatibilityVerifier = new CompositeCompatibilityVerifier( + getCompatibilityVerifierInstances(environment)); + compositeCompatibilityVerifier.verifyCompatibilities(); + } + + private List getCompatibilityVerifierInstances(Environment environment) { + // Use names and ensure unique to protect against duplicates + ClassLoader classLoader = this.getClass().getClassLoader(); + Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames( + CompatibilityVerifier.class, classLoader)); + List instances = createSpringFactoriesInstances( + CompatibilityVerifier.class, new Class[] { Environment.class }, classLoader, + new Object[] { environment }, names); + AnnotationAwareOrderComparator.sort(instances); + return instances; + } + + private List createSpringFactoriesInstances(Class type, Class[] parameterTypes, + ClassLoader classLoader, Object[] args, + Set names) { + List instances = new ArrayList<>(names.size()); + for (String name : names) { + try { + Class instanceClass = ClassUtils.forName(name, classLoader); + Assert.isAssignable(type, instanceClass); + Constructor constructor = instanceClass.getDeclaredConstructor(parameterTypes); + T instance = (T) BeanUtils.instantiateClass(constructor, args); + instances.add(instance); + } catch (Throwable ex) { + throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); + } + } + return instances; + } +} diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/configure/SofaRuntimeConfigurationProperties.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/configure/SofaRuntimeConfigurationProperties.java index 7002e0d2a1e8b4c6217080ae79b2f3799fda7f1f..1a6f416a819d855801a42dbf3bfc63e4f960807a 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/configure/SofaRuntimeConfigurationProperties.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/configure/SofaRuntimeConfigurationProperties.java @@ -162,4 +162,12 @@ public class SofaRuntimeConfigurationProperties { .setReferenceHealthCheckMoreDetailEnable(referenceHealthCheckMoreDetailEnable); } + public boolean isServiceDuplicate() { + return SofaRuntimeProperties.isServiceCanBeDuplicate(); + } + + public void setServiceDuplicate(boolean serviceCanBeDuplicate) { + SofaRuntimeProperties.setServiceCanBeDuplicate(serviceCanBeDuplicate); + } + } diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/factory/BeanLoadCostBeanFactory.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/factory/BeanLoadCostBeanFactory.java index db6e380d2a4eb9bc4f37dca26c079c8a3fc6f4d8..41b063da64968b669228628988b52691cf6198bf 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/factory/BeanLoadCostBeanFactory.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/factory/BeanLoadCostBeanFactory.java @@ -186,4 +186,8 @@ public class BeanLoadCostBeanFactory extends DefaultListableBeanFactory { } return rtn.toString(); } + + public void clearParentStackThreadLocal() { + parentStackThreadLocal.remove(); + } } diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/filter/JvmFilterHolder.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/filter/JvmFilterHolder.java index e8aacb45475ce99d82a370fd15305ad468e2f35b..cfa173e0e327d9878fea07795222fdcab9851a43 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/filter/JvmFilterHolder.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/filter/JvmFilterHolder.java @@ -18,9 +18,9 @@ package com.alipay.sofa.runtime.filter; import org.springframework.core.Ordered; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -28,7 +28,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * Created on 2020/8/18 */ public class JvmFilterHolder { - private static final List JVM_FILTERS = new ArrayList<>(); + private static final List JVM_FILTERS = new CopyOnWriteArrayList<>(); private static final AtomicBoolean filtersSorted = new AtomicBoolean(false); private static final Comparator comparator = (f1, f2) -> { diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/invoke/DynamicJvmServiceProxyFinder.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/invoke/DynamicJvmServiceProxyFinder.java index 1f7a326143f25fadae61e60d4a7406e0d05cd0ac..524b4d8fdb5fde6de15b6d0bc0fe50cbae5b47c8 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/invoke/DynamicJvmServiceProxyFinder.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/invoke/DynamicJvmServiceProxyFinder.java @@ -78,7 +78,7 @@ public class DynamicJvmServiceProxyFinder { } } - String interfaceType = contract.getInterfaceType().getCanonicalName(); + String interfaceType = contract.getInterfaceTypeCanonicalName(); String uniqueId = contract.getUniqueId(); for (SofaRuntimeManager sofaRuntimeManager : SofaFramework.getRuntimeSet()) { if (sofaRuntimeManager.getAppClassLoader().equals(clientClassloader)) { @@ -244,7 +244,7 @@ public class DynamicJvmServiceProxyFinder { for (ComponentInfo c : components) { ServiceComponent component = (ServiceComponent) c; Contract serviceContract = component.getService(); - if (serviceContract.getInterfaceType().getCanonicalName().equals(interfaceType) + if (serviceContract.getInterfaceTypeCanonicalName().equals(interfaceType) && uniqueId.equals(serviceContract.getUniqueId())) { return component; } @@ -351,7 +351,7 @@ public class DynamicJvmServiceProxyFinder { if (getDynamicJvmServiceProxyFinder().bizManagerService != null) { ReplayContext.clearPlaceHolder(); } - setClientClassloader(null); + clearClientClassloader(); } } @@ -381,6 +381,10 @@ public class DynamicJvmServiceProxyFinder { this.clientClassloader.set(clientClassloader); } + public void clearClientClassloader() { + this.clientClassloader.remove(); + } + private Method getTargetMethod(Method method, Class[] argumentTypes) { try { return targetService.getClass().getMethod(method.getName(), argumentTypes); diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/service/component/AbstractContract.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/service/component/AbstractContract.java index feeaabb5e9035c77f06a82bcf7f0093f8948a905..825ff9589d72c2e78c818f047affa4135dc0fe3b 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/service/component/AbstractContract.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/service/component/AbstractContract.java @@ -38,6 +38,8 @@ public abstract class AbstractContract implements Contract { protected String uniqueId = ""; /** interface class type */ protected Class interfaceType; + /** interface class type name*/ + protected String interfaceTypeCanonicalName; /** interface mode */ protected InterfaceMode interfaceMode = InterfaceMode.spring; /** properties of contract */ @@ -50,6 +52,7 @@ public abstract class AbstractContract implements Contract { this.uniqueId = uniqueId; } this.interfaceType = interfaceType; + this.interfaceTypeCanonicalName = interfaceType.getCanonicalName(); } protected AbstractContract(String uniqueId, Class interfaceType, InterfaceMode interfaceMode) { @@ -114,4 +117,9 @@ public abstract class AbstractContract implements Contract { public Map getProperty() { return property; } + + @Override + public String getInterfaceTypeCanonicalName() { + return interfaceTypeCanonicalName; + } } diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/service/component/ServiceComponent.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/service/component/ServiceComponent.java index cca7e10a84f86a7c46fb55271071d156970f13d5..f2182603515926edd5ea1086510e1e9453bb701c 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/service/component/ServiceComponent.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/service/component/ServiceComponent.java @@ -315,4 +315,9 @@ public class ServiceComponent extends AbstractComponent { healthResult.setHealthReport(report); return healthResult; } + + @Override + public boolean canBeDuplicate() { + return SofaRuntimeProperties.isServiceCanBeDuplicate(); + } } diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spi/binding/Contract.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spi/binding/Contract.java index 3a1dcd2cb8eeb3392c40c02cabfbddc3eab653ce..7b907a6d16c11599c6e41b53f198760e41a2e8fa 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spi/binding/Contract.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spi/binding/Contract.java @@ -99,4 +99,12 @@ public interface Contract { * @param bindings binding objects */ void addBinding(Set bindings); + + /** + * get interface type class canonical name + * @return get interface type class canonical name + */ + default String getInterfaceTypeCanonicalName() { + return getInterfaceType().getCanonicalName(); + } } diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spi/service/BindingConverterContext.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spi/service/BindingConverterContext.java index a978f4b6295383f4300539df97f2052a5fe7ed3f..d502cfd7573578ddc561c96a697060ef19ac67d1 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spi/service/BindingConverterContext.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spi/service/BindingConverterContext.java @@ -39,6 +39,8 @@ public class BindingConverterContext { private String repeatReferLimit; + private Class interfaceType; + public ClassLoader getAppClassLoader() { return appClassLoader; } @@ -94,4 +96,12 @@ public class BindingConverterContext { public void setRepeatReferLimit(String repeatReferLimit) { this.repeatReferLimit = repeatReferLimit; } + + public Class getInterfaceType() { + return interfaceType; + } + + public void setInterfaceType(Class interfaceType) { + this.interfaceType = interfaceType; + } } diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ServiceBeanFactoryPostProcessor.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ServiceBeanFactoryPostProcessor.java index bc02f9e43b0617d66498c6c0ce2e582f393c8e71..8774eb6f6bdd374b08b3e4a4931ff750acbb0227 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ServiceBeanFactoryPostProcessor.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ServiceBeanFactoryPostProcessor.java @@ -21,6 +21,7 @@ import com.alipay.sofa.boot.annotation.PlaceHolderBinder; import com.alipay.sofa.boot.error.ErrorCode; import com.alipay.sofa.boot.util.BeanDefinitionUtil; import com.alipay.sofa.boot.util.SmartAnnotationUtils; +import com.alipay.sofa.runtime.SofaRuntimeProperties; import com.alipay.sofa.runtime.api.ServiceRuntimeException; import com.alipay.sofa.runtime.api.annotation.SofaReference; import com.alipay.sofa.runtime.api.annotation.SofaReferenceBinding; @@ -48,6 +49,7 @@ import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefiniti import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; @@ -246,7 +248,9 @@ public class ServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor if (!registry.containsBeanDefinition(referenceId)) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); builder.getRawBeanDefinition().setScope(beanDefinition.getScope()); - builder.getRawBeanDefinition().setLazyInit(beanDefinition.isLazyInit()); + if (((AbstractBeanDefinition) beanDefinition).getLazyInit() != null) { + builder.getRawBeanDefinition().setLazyInit(beanDefinition.isLazyInit()); + } builder.getRawBeanDefinition().setBeanClass(ReferenceFactoryBean.class); builder.addAutowiredProperty(AbstractContractDefinitionParser.SOFA_RUNTIME_CONTEXT); builder @@ -313,7 +317,9 @@ public class ServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor if (!registry.containsBeanDefinition(serviceId)) { builder.getRawBeanDefinition().setScope(beanDefinition.getScope()); - builder.setLazyInit(beanDefinition.isLazyInit()); + if (((AbstractBeanDefinition) beanDefinition).getLazyInit() != null) { + builder.setLazyInit(beanDefinition.isLazyInit()); + } builder.getRawBeanDefinition().setBeanClass(ServiceFactoryBean.class); builder.addAutowiredProperty(AbstractContractDefinitionParser.SOFA_RUNTIME_CONTEXT); builder @@ -323,8 +329,10 @@ public class ServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor interfaceType); builder.addPropertyValue(AbstractContractDefinitionParser.UNIQUE_ID_PROPERTY, sofaServiceAnnotation.uniqueId()); - builder.addPropertyValue(AbstractContractDefinitionParser.BINDINGS, - getSofaServiceBinding(sofaServiceAnnotation, sofaServiceAnnotation.bindings())); + builder.addPropertyValue( + AbstractContractDefinitionParser.BINDINGS, + getSofaServiceBinding(sofaServiceAnnotation, sofaServiceAnnotation.bindings(), + interfaceType)); builder.addPropertyReference(ServiceDefinitionParser.REF, beanId); builder.addPropertyValue(ServiceDefinitionParser.BEAN_ID, beanId); builder.addPropertyValue(AbstractContractDefinitionParser.DEFINITION_BUILDING_API_TYPE, @@ -332,12 +340,17 @@ public class ServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor builder.addDependsOn(beanId); registry.registerBeanDefinition(serviceId, builder.getBeanDefinition()); } else { - SofaLogger.warn("SofaService was already registered: {}", serviceId); + if (SofaRuntimeProperties.isServiceCanBeDuplicate()) { + SofaLogger.warn("SofaService was already registered: {}", serviceId); + } else { + throw new ServiceRuntimeException(ErrorCode.convert("01-00203", serviceId)); + } } } private List getSofaServiceBinding(SofaService sofaServiceAnnotation, - SofaServiceBinding[] sofaServiceBindings) { + SofaServiceBinding[] sofaServiceBindings, + Class interfaceType) { List bindings = new ArrayList<>(); for (SofaServiceBinding sofaServiceBinding : sofaServiceBindings) { BindingConverter bindingConverter = bindingConverterFactory @@ -351,6 +364,7 @@ public class ServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor bindingConverterContext.setApplicationContext(applicationContext); bindingConverterContext.setAppName(sofaRuntimeContext.getAppName()); bindingConverterContext.setAppClassLoader(sofaRuntimeContext.getAppClassLoader()); + bindingConverterContext.setInterfaceType(interfaceType); Binding binding = bindingConverter.convert(sofaServiceAnnotation, sofaServiceBinding, bindingConverterContext); bindings.add(binding); diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/resources/META-INF/spring.factories b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/resources/META-INF/spring.factories index 8a1b04916b7a52e75371830ddb8c79bd7543f45b..f3026be4afe18fd44967601c44398312622787f9 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/resources/META-INF/spring.factories +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/resources/META-INF/spring.factories @@ -1 +1,2 @@ -org.springframework.context.ApplicationContextInitializer=com.alipay.sofa.runtime.SofaRuntimeSpringContextInitializer +org.springframework.context.ApplicationContextInitializer=com.alipay.sofa.runtime.SofaRuntimeSpringContextInitializer,\ + com.alipay.sofa.runtime.compatibility.CompatibilityVerifierApplicationContextInitializer diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/ServiceDuplicateTest.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/ServiceDuplicateTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7c6835604cad1f4acab5f5d011eca045ba9715c3 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/ServiceDuplicateTest.java @@ -0,0 +1,132 @@ +/* + * 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.runtime.test; + +import com.alipay.sofa.boot.error.ErrorCode; +import com.alipay.sofa.runtime.api.ServiceRuntimeException; +import com.alipay.sofa.runtime.api.client.ServiceClient; +import com.alipay.sofa.runtime.api.client.param.ServiceParam; +import com.alipay.sofa.runtime.api.component.ComponentName; +import com.alipay.sofa.runtime.configure.SofaRuntimeConfigurationProperties; +import com.alipay.sofa.runtime.spi.util.ComponentNameFactory; +import com.alipay.sofa.runtime.spring.ServiceBeanFactoryPostProcessor; +import com.alipay.sofa.runtime.spring.bean.SofaBeanNameGenerator; +import com.alipay.sofa.runtime.test.beans.facade.SampleService; +import com.alipay.sofa.runtime.test.beans.service.ClientFactoryBean; +import com.alipay.sofa.runtime.test.beans.service.SofaServiceBeanService; +import com.alipay.sofa.runtime.test.configuration.RuntimeConfiguration; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +import static com.alipay.sofa.runtime.service.component.ServiceComponent.SERVICE_COMPONENT_TYPE; + +/** + * ServiceDuplicateTest + * + * @author xunfang + * @version ServiceDuplicateTest.java, v 0.1 2023/7/24 + */ +@RunWith(SpringRunner.class) +@SpringBootTest +@TestPropertySource(properties = { "spring.application.name=ServiceDuplicateTest", + "com.alipay.sofa.boot.service-duplicate=false" }) +@Import({ SofaServiceBeanService.class, ClientFactoryBean.class }) +public class ServiceDuplicateTest implements BeanFactoryAware { + @Autowired + private SampleService sofaServiceBeanService; + + @Autowired + private ClientFactoryBean clientFactoryBean; + + @Autowired + private ApplicationContext ctx; + + private BeanFactory beanFactory; + + @Autowired + private ServiceBeanFactoryPostProcessor serviceBeanFactoryPostProcessor; + + @Test + public void testAnnotationServiceDuplicate() { + SofaRuntimeConfigurationProperties configurationProperties = ctx + .getBean(SofaRuntimeConfigurationProperties.class); + Assert.assertFalse(configurationProperties.isServiceDuplicate()); + + String serviceId = SofaBeanNameGenerator.generateSofaServiceBeanName(SampleService.class, + "sofaServiceBeanService", null); + + try { + serviceBeanFactoryPostProcessor + .postProcessBeanFactory((ConfigurableListableBeanFactory) beanFactory); + throw new IllegalStateException("Service: " + serviceId + + " cannot be registered duplicate"); + } catch (ServiceRuntimeException e) { + Assert.assertTrue(e.getMessage().contains(ErrorCode.convert("01-00203", serviceId))); + } + + } + + @Test + public void testDynamicServiceDuplicate() { + SofaRuntimeConfigurationProperties configurationProperties = ctx + .getBean(SofaRuntimeConfigurationProperties.class); + Assert.assertFalse(configurationProperties.isServiceDuplicate()); + + ServiceClient serviceClient = clientFactoryBean.getClientFactory().getClient( + ServiceClient.class); + ServiceParam serviceParam = new ServiceParam(); + serviceParam.setInstance(sofaServiceBeanService); + serviceParam.setInterfaceType(SampleService.class); + serviceParam.setUniqueId("sofaServiceBeanService"); + ComponentName componentName = ComponentNameFactory.createComponentName( + SERVICE_COMPONENT_TYPE, SampleService.class, "sofaServiceBeanService"); + try { + serviceClient.service(serviceParam); + throw new IllegalStateException("Service: " + componentName + + " cannot be registered duplicate"); + } catch (ServiceRuntimeException e) { + Assert + .assertTrue(e.getMessage().contains(ErrorCode.convert("01-03002", componentName))); + } + + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Configuration(proxyBeanMethods = false) + @EnableAutoConfiguration + @Import({ RuntimeConfiguration.class }) + static class ServiceBeanTestConfiguration { + + } +} diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/SofaEventHandlerTest.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/SofaEventHandlerTest.java index 245ec27e59598222ab10269d2d9d49433e8033b1..2a569f1717e1d3c2f977276128a2a3aa137281be 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/SofaEventHandlerTest.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/SofaEventHandlerTest.java @@ -43,7 +43,6 @@ import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.context.ConfigurableApplicationContext; @@ -127,7 +126,7 @@ public class SofaEventHandlerTest { Mockito.when(sofaRuntimeManager.getComponentManager()).thenReturn(((SofaRuntimeContext) ctx.getBean("sofaRuntimeContext")) .getComponentManager()); - Mockito.when(contract.getInterfaceType()).thenAnswer((Answer) invocationOnMock -> SampleService.class); + Mockito.when(contract.getInterfaceTypeCanonicalName()).thenReturn("com.alipay.sofa.runtime.test.beans.facade.SampleService"); Mockito.when(contract.getUniqueId()).thenReturn(""); Mockito.when(contract.getBinding(JvmBinding.JVM_BINDING_TYPE)).thenReturn(new JvmBinding()); Mockito.when(invocation.getArguments()).thenReturn(new Object[] {}); diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/SofaServiceAndReferenceTest.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/SofaServiceAndReferenceTest.java index 043fa98b7ed7efa60a591f7978eb2986501a3f27..f2d05323c6525498cfc447c368ac3b153c3e27c3 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/SofaServiceAndReferenceTest.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/SofaServiceAndReferenceTest.java @@ -29,6 +29,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -100,7 +101,7 @@ public class SofaServiceAndReferenceTest { Map properties = new HashMap<>(); properties.put("spring.application.name", "SofaServiceAndReferenceTest"); SpringApplication springApplication = new SpringApplication( - TestSofaReferenceOnMethodConfiguration.class, RuntimeConfiguration.class); + TestSofaServiceOnMethodConfiguration.class, RuntimeConfiguration.class); springApplication.setWebApplicationType(WebApplicationType.NONE); springApplication.setDefaultProperties(properties); ApplicationContext ctx = springApplication.run(); @@ -173,7 +174,7 @@ public class SofaServiceAndReferenceTest { properties.put("spring.application.name", "SofaServiceAndReferenceTest"); properties.put("logging.path", logRootPath); SpringApplication springApplication = new SpringApplication( - TestSofaReferenceOnMethodConfiguration.class, RuntimeConfiguration.class); + TestSofaServiceOnMethodConfiguration.class, RuntimeConfiguration.class); springApplication.setWebApplicationType(WebApplicationType.NONE); springApplication.setDefaultProperties(properties); springApplication.run(); @@ -186,6 +187,53 @@ public class SofaServiceAndReferenceTest { + SofaServiceAndReferenceTest.class.getDeclaredField("staticSampleService"))); } + @Test + public void testSofaServiceBeanLazyInit() throws IOException { + String logRootPath = StringUtils.hasText(System.getProperty("logging.path")) ? System + .getProperty("logging.path") : "./logs"; + File sofaLog = new File(logRootPath + File.separator + "sofa-runtime" + File.separator + + "sofa-default.log"); + FileUtils.write(sofaLog, "", System.getProperty("file.encoding")); + + Map properties = new HashMap<>(); + properties.put("spring.application.name", "SofaServiceAndReferenceTest"); + properties.put("logging.path", logRootPath); + SpringApplication springApplication = new SpringApplication( + TestSofaServiceConfiguration.class, RuntimeConfiguration.class); + springApplication.setLazyInitialization(true); + springApplication.setWebApplicationType(WebApplicationType.NONE); + springApplication.setDefaultProperties(properties); + ConfigurableApplicationContext context = springApplication.run(); + + String serviceBeanName = SofaBeanNameGenerator.generateSofaServiceBeanName( + SampleService.class, ""); + Assert.assertTrue(context.getBeanFactory().getBeanDefinition(serviceBeanName).isLazyInit()); + } + + @Test + public void testSofaReferenceBeanLazyInit() throws IOException { + String logRootPath = StringUtils.hasText(System.getProperty("logging.path")) ? System + .getProperty("logging.path") : "./logs"; + File sofaLog = new File(logRootPath + File.separator + "sofa-runtime" + File.separator + + "sofa-default.log"); + FileUtils.write(sofaLog, "", System.getProperty("file.encoding")); + + Map properties = new HashMap<>(); + properties.put("spring.application.name", "SofaServiceAndReferenceTest"); + properties.put("logging.path", logRootPath); + SpringApplication springApplication = new SpringApplication( + TestSofaReferenceOnMethodConfiguration.class, RuntimeConfiguration.class); + springApplication.setLazyInitialization(true); + springApplication.setWebApplicationType(WebApplicationType.NONE); + springApplication.setDefaultProperties(properties); + ConfigurableApplicationContext context = springApplication.run(); + + String referenceBeanName = SofaBeanNameGenerator.generateSofaReferenceBeanName( + SampleService.class, ""); + Assert.assertTrue(context.getBeanFactory().getBeanDefinition(referenceBeanName) + .isLazyInit()); + } + @Configuration(proxyBeanMethods = false) static class MultipleBindingsSofaServiceConfiguration { /** @@ -209,6 +257,14 @@ public class SofaServiceAndReferenceTest { @Configuration(proxyBeanMethods = false) static class TestSofaReferenceOnMethodConfiguration { + @Bean + public SampleService sampleService(@SofaReference SampleService sampleService) { + return new DefaultSampleService("TestSofaReferenceConfiguration"); + } + } + + @Configuration(proxyBeanMethods = false) + static class TestSofaServiceOnMethodConfiguration { @Bean public SofaServiceAndReferenceTest sofaServiceAndReferenceTest() { return new SofaServiceAndReferenceTest(); diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/beans/service/ClientFactoryBean.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/beans/service/ClientFactoryBean.java new file mode 100644 index 0000000000000000000000000000000000000000..a8c9aa47d31ebfe4accac37073ec797bec18364b --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/beans/service/ClientFactoryBean.java @@ -0,0 +1,42 @@ +/* + * 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.runtime.test.beans.service; + +import com.alipay.sofa.runtime.api.aware.ClientFactoryAware; +import com.alipay.sofa.runtime.api.client.ClientFactory; +import org.springframework.stereotype.Component; + +/** + * ClientFactoryBean + * + * @author xunfang + * @version ClientFactoryBean.java, v 0.1 2023/7/24 + */ +@Component +public class ClientFactoryBean implements ClientFactoryAware { + + private ClientFactory clientFactory; + + @Override + public void setClientFactory(ClientFactory clientFactory) { + this.clientFactory = clientFactory; + } + + public ClientFactory getClientFactory() { + return clientFactory; + } +} diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/compatibility/CompatibilityVerifierApplicationContextInitializerTest.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/compatibility/CompatibilityVerifierApplicationContextInitializerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..99bedcb203599c26a83f19069e9759b1cdb4e91d --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/compatibility/CompatibilityVerifierApplicationContextInitializerTest.java @@ -0,0 +1,72 @@ +/* + * 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.runtime.test.compatibility; + +import com.alipay.sofa.boot.compatibility.CompatibilityNotMetException; +import com.alipay.sofa.runtime.compatibility.CompatibilityVerifierApplicationContextInitializer; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.context.support.GenericXmlApplicationContext; +import org.springframework.mock.env.MockEnvironment; + +/** + * @author huzijie + * @version CompatibilityVerifierApplicationContextInitializerTest.java, v 0.1 2023年08月07日 12:11 PM huzijie Exp $ + */ +public class CompatibilityVerifierApplicationContextInitializerTest { + + private final MockEnvironment mockEnvironment = new MockEnvironment(); + + private final GenericApplicationContext applicationContext = new GenericXmlApplicationContext(); + + @Before + public void setUp() { + TestCompatibilityVerifier.invoked = false; + applicationContext.setEnvironment(mockEnvironment); + mockEnvironment.setProperty("enable.test.compatibility", "true"); + } + + @Test + public void enableKey() { + CompatibilityVerifierApplicationContextInitializer initializer = new CompatibilityVerifierApplicationContextInitializer(); + try { + initializer.initialize(applicationContext); + Assert.fail(); + } catch (CompatibilityNotMetException e) { + Assert.assertTrue(e.getMessage().contains("description: test error")); + Assert.assertTrue(e.getMessage().contains("action: test action")); + } + + Assert.assertTrue(TestCompatibilityVerifier.invoked); + } + + @Test + public void disableKey() { + mockEnvironment.setProperty("sofa.boot.compatibility-verifier.enabled", "false"); + CompatibilityVerifierApplicationContextInitializer initializer = new CompatibilityVerifierApplicationContextInitializer(); + initializer.initialize(applicationContext); + + Assert.assertFalse(TestCompatibilityVerifier.invoked); + } + + @Before + public void clear() { + TestCompatibilityVerifier.invoked = false; + } +} diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/compatibility/TestCompatibilityVerifier.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/compatibility/TestCompatibilityVerifier.java new file mode 100644 index 0000000000000000000000000000000000000000..399154bf768ea7201537485b27ea6417fe213f71 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/java/com/alipay/sofa/runtime/test/compatibility/TestCompatibilityVerifier.java @@ -0,0 +1,47 @@ +/* + * 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.runtime.test.compatibility; + +import com.alipay.sofa.boot.compatibility.CompatibilityVerifier; +import com.alipay.sofa.boot.compatibility.VerificationResult; +import org.springframework.core.env.Environment; +import org.springframework.util.Assert; + +/** + * @author huzijie + * @version TestCompatibilityVerifier.java, v 0.1 2023年08月07日 12:19 PM huzijie Exp $ + */ +public class TestCompatibilityVerifier implements CompatibilityVerifier { + + public static boolean invoked = false; + + private final Environment environment; + + public TestCompatibilityVerifier(Environment environment) { + this.environment = environment; + } + + @Override + public VerificationResult verify() { + Assert.notNull(environment, "environment must not null"); + invoked = true; + if (!environment.containsProperty("enable.test.compatibility")) { + return VerificationResult.compatible(); + } + return VerificationResult.notCompatible("test error", "test action"); + } +} diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/resources/META-INF/spring.factories b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/resources/META-INF/spring.factories new file mode 100644 index 0000000000000000000000000000000000000000..57fc4a6b957eb205f05626ddda4cf91918205df5 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/test/resources/META-INF/spring.factories @@ -0,0 +1 @@ +com.alipay.sofa.boot.compatibility.CompatibilityVerifier=com.alipay.sofa.runtime.test.compatibility.TestCompatibilityVerifier \ No newline at end of file diff --git a/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/main/java/com/alipay/sofa/startup/StartupReporter.java b/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/main/java/com/alipay/sofa/startup/StartupReporter.java index 6b8fcee80853bfaec079af890b83c5e46acd5e58..357115845ebcbb471321d6fea0f8d646582aa887 100644 --- a/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/main/java/com/alipay/sofa/startup/StartupReporter.java +++ b/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/main/java/com/alipay/sofa/startup/StartupReporter.java @@ -52,15 +52,23 @@ public class StartupReporter { public static final String SPRING_CONTEXT_BEAN_FACTORY_POST_PROCESSOR = "spring.context.bean-factory.post-process"; + public static final String SPRING_BEAN_POST_PROCESSOR = "spring.context.beans.post-process"; + + public static final String SPRING_CONFIG_CLASSES_ENHANCE = "spring.context.config-classes.enhance"; + public static final Collection SPRING_BEAN_INSTANTIATE_TYPES = new HashSet<>(); public static final Collection SPRING_CONTEXT_POST_PROCESSOR_TYPES = new HashSet<>(); + public static final Collection SPRING_CONFIG_CLASSES_ENHANCE_TYPES = new HashSet<>(); + static { SPRING_BEAN_INSTANTIATE_TYPES.add(SPRING_BEANS_INSTANTIATE); SPRING_BEAN_INSTANTIATE_TYPES.add(SPRING_BEANS_SMART_INSTANTIATE); SPRING_CONTEXT_POST_PROCESSOR_TYPES.add(SPRING_CONTEXT_BEANDEF_REGISTRY_POST_PROCESSOR); SPRING_CONTEXT_POST_PROCESSOR_TYPES.add(SPRING_CONTEXT_BEAN_FACTORY_POST_PROCESSOR); + SPRING_CONFIG_CLASSES_ENHANCE_TYPES.add(SPRING_CONFIG_CLASSES_ENHANCE); + SPRING_CONFIG_CLASSES_ENHANCE_TYPES.add(SPRING_BEAN_POST_PROCESSOR); } private final StartupStaticsModel startupStaticsModel = new StartupStaticsModel(); @@ -175,7 +183,9 @@ public class StartupReporter { private boolean filterBeanInitializeByCost(BeanStat beanStat) { String name = beanStat.getBeanType(); - if (SPRING_BEAN_INSTANTIATE_TYPES.contains(name)) { + if (SPRING_BEAN_INSTANTIATE_TYPES.contains(name) + || SPRING_CONTEXT_POST_PROCESSOR_TYPES.contains(name) + || SPRING_CONFIG_CLASSES_ENHANCE_TYPES.contains(name)) { return beanStat.getCost() >= beanInitCostThreshold; } else { return true; diff --git a/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/main/java/com/alipay/sofa/startup/stage/StartupSpringApplicationRunListener.java b/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/main/java/com/alipay/sofa/startup/stage/StartupSpringApplicationRunListener.java index 628db78dfbac595ce16ba4462296353d3b8c566d..d91f45e22888fe096e0fd92bb45072cd30d855c9 100644 --- a/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/main/java/com/alipay/sofa/startup/stage/StartupSpringApplicationRunListener.java +++ b/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/main/java/com/alipay/sofa/startup/stage/StartupSpringApplicationRunListener.java @@ -157,7 +157,7 @@ public class StartupSpringApplicationRunListener implements SpringApplicationRun @Override public int getOrder() { - return Ordered.LOWEST_PRECEDENCE; + return Ordered.LOWEST_PRECEDENCE - 10; } private String getStartedMessage(Environment environment, Duration timeTakenToStartup) { diff --git a/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/test/java/com/alipay/sofa/startup/test/configuration/SofaStartupHealthCheckAutoConfiguration.java b/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/test/java/com/alipay/sofa/startup/test/configuration/SofaStartupHealthCheckAutoConfiguration.java index 72215cc758d6e3c727b330016349f9b2e9cb82c9..d133f1c8fb7abadf3276f6666d02ecda9d5634e9 100644 --- a/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/test/java/com/alipay/sofa/startup/test/configuration/SofaStartupHealthCheckAutoConfiguration.java +++ b/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/test/java/com/alipay/sofa/startup/test/configuration/SofaStartupHealthCheckAutoConfiguration.java @@ -27,7 +27,6 @@ import com.alipay.sofa.startup.StartupReporter; import com.alipay.sofa.startup.stage.healthcheck.StartupReadinessCheckListener; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -41,7 +40,7 @@ import org.springframework.core.env.Environment; public class SofaStartupHealthCheckAutoConfiguration { @Bean - @ConditionalOnMissingBean(value = ReadinessCheckListener.class, search = SearchStrategy.CURRENT) + @ConditionalOnMissingBean(value = ReadinessCheckListener.class) public StartupReadinessCheckListener startupReadinessCheckListener(Environment environment, HealthCheckerProcessor healthCheckerProcessor, HealthIndicatorProcessor healthIndicatorProcessor, diff --git a/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/test/java/com/alipay/sofa/startup/test/configuration/SofaStartupIsleAutoConfiguration.java b/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/test/java/com/alipay/sofa/startup/test/configuration/SofaStartupIsleAutoConfiguration.java index 1e76efb2a1ac30a91383e3540e1b912c0bdfdeab..04f99fe4a0d4fb3f7e758ba27c5a8b91874992cb 100644 --- a/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/test/java/com/alipay/sofa/startup/test/configuration/SofaStartupIsleAutoConfiguration.java +++ b/sofa-boot-project/sofa-boot-core/startup-sofa-boot/src/test/java/com/alipay/sofa/startup/test/configuration/SofaStartupIsleAutoConfiguration.java @@ -40,7 +40,6 @@ import com.alipay.sofa.startup.stage.isle.StartupSpringContextInstallStage; import com.alipay.sofa.startup.test.stage.TestModelCreatingStage; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -59,7 +58,7 @@ import java.util.List; public class SofaStartupIsleAutoConfiguration { @Bean - @ConditionalOnMissingBean(value = SpringContextInstallStage.class, search = SearchStrategy.CURRENT) + @ConditionalOnMissingBean(value = SpringContextInstallStage.class) public StartupSpringContextInstallStage startupSpringContextInstallStage(ApplicationContext applicationContext, SofaModuleProperties sofaModuleProperties, StartupReporter startupReporter) { @@ -68,7 +67,7 @@ public class SofaStartupIsleAutoConfiguration { } @Bean - @ConditionalOnMissingBean(value = ModelCreatingStage.class, search = SearchStrategy.CURRENT) + @ConditionalOnMissingBean(value = ModelCreatingStage.class) public StartupModelCreatingStage startupModelCreatingStage(ApplicationContext applicationContext, SofaModuleProperties sofaModuleProperties, SofaModuleProfileChecker sofaModuleProfileChecker, diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/pom.xml b/sofa-boot-project/sofa-boot-core/test-sofa-boot/pom.xml index 699b899a52019e7da2e5d843208f0dc52e356e07..9f93a77be7aaf9b8e5fcbbbcef97643fac8a56ab 100644 --- a/sofa-boot-project/sofa-boot-core/test-sofa-boot/pom.xml +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/pom.xml @@ -29,12 +29,35 @@ provided + + org.mockito + mockito-core + provided + + + + com.alipay.sofa + isle-sofa-boot + true + + junit junit provided + + org.springframework.boot + spring-boot-starter-test + test + + + + org.assertj + assertj-core + test + diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/InjectorMockTestExecutionListener.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/InjectorMockTestExecutionListener.java new file mode 100644 index 0000000000000000000000000000000000000000..9dacebe9c80dcc3bca164556384bba01b53eeaff --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/InjectorMockTestExecutionListener.java @@ -0,0 +1,130 @@ +/* + * 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.test.mock.injector; + +import com.alipay.sofa.boot.error.ErrorCode; +import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector; +import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector; +import com.alipay.sofa.test.mock.injector.definition.Definition; +import com.alipay.sofa.test.mock.injector.parser.DefinitionParser; +import com.alipay.sofa.test.mock.injector.resolver.BeanInjectorResolver; +import com.alipay.sofa.test.mock.injector.resolver.BeanInjectorStub; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.core.Ordered; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestExecutionListener; +import org.springframework.test.context.support.AbstractTestExecutionListener; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; + +/** + * {@link TestExecutionListener} to enable {@link MockBeanInjector} and + * {@link SpyBeanInjector} support. + * + * @author pengym + * @version InjectorMockTestExecutionListener.java, v 0.1 2023年08月07日 15:51 pengym + */ +public class InjectorMockTestExecutionListener extends AbstractTestExecutionListener { + + static final String STUBBED_FIELDS = "_SOFA_BOOT_STUBBED_FIELDS"; + + static final String STUBBED_DEFINITIONS = "_SOFA_BOOT_STUBBED_DEFINITIONS"; + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + public void prepareTestInstance(TestContext testContext) { + // parse annotations and inject fields + injectFields(testContext); + } + + private void injectFields(TestContext testContext) { + // create definitions form annotation + DefinitionParser parser = new DefinitionParser(); + parser.parse(testContext.getTestClass()); + testContext.setAttribute(STUBBED_DEFINITIONS, parser.getDefinitions()); + + // create stubs form definitions + Collection beanInjectorStubs = createStubs(parser, testContext); + testContext.setAttribute(STUBBED_FIELDS, beanInjectorStubs); + + // inject mock/spy to test class fields + injectTestClass(parser, testContext); + } + + private void injectTestClass(DefinitionParser parser, TestContext testContext) { + parser.getDefinitions().forEach(definition -> { + Field field = parser.getField(definition); + if (field != null) { + Object target = testContext.getTestInstance(); + ReflectionUtils.makeAccessible(field); + Object existingValue = ReflectionUtils.getField(field, target); + Object injectValue = definition.getMockInstance(); + if (existingValue == injectValue) { + return; + } + Assert.state(existingValue == null, () -> ErrorCode.convert("01-30000", existingValue, field, injectValue)); + ReflectionUtils.setField(field, target, injectValue); + } + }); + } + + private Collection createStubs(DefinitionParser parser, + TestContext testContext) { + Collection beanInjectorStubs = new ArrayList<>(); + BeanInjectorResolver resolver = new BeanInjectorResolver( + testContext.getApplicationContext()); + for (Definition definition : parser.getDefinitions()) { + BeanInjectorStub field = resolver.resolveStub(definition); + if (field != null) { + beanInjectorStubs.add(field); + } + } + return beanInjectorStubs; + } + + @Override + @SuppressWarnings("unchecked") + public void beforeTestMethod(TestContext testContext) { + Set stubbedDefinitions = (Set) testContext.getAttribute(STUBBED_DEFINITIONS); + if (!CollectionUtils.isEmpty(stubbedDefinitions)) { + stubbedDefinitions.stream().filter(definition -> definition.getReset().equals(MockReset.BEFORE)).forEach(Definition::resetMock); + } + } + + @Override + @SuppressWarnings("unchecked") + public void afterTestMethod(TestContext testContext) { + Set stubbedDefinitions = (Set) testContext.getAttribute(STUBBED_DEFINITIONS); + if (!CollectionUtils.isEmpty(stubbedDefinitions)) { + stubbedDefinitions.stream().filter(definition -> definition.getReset().equals(MockReset.AFTER)).forEach(Definition::resetMock); + } + Collection beanStubbedFields = (Collection) testContext.getAttribute(STUBBED_FIELDS); + if (!CollectionUtils.isEmpty(beanStubbedFields)) { + beanStubbedFields.forEach(BeanInjectorStub::reset); + } + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/MockBeanInjector.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/MockBeanInjector.java new file mode 100644 index 0000000000000000000000000000000000000000..fc020f311b270d9f2e7eed4da1afe00bef59ee9c --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/MockBeanInjector.java @@ -0,0 +1,152 @@ +/* + * 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.test.mock.injector.annotation; + +import org.mockito.Answers; +import org.mockito.MockSettings; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.core.annotation.AliasFor; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that can be used to create mocks and inject mock to a target bean's field. + *

+ * Injector target bean can be found by type or by {@link #name() bean name}. When registered by + * type, any existing single bean of a matching type (including subclasses) in the context + * will be found for injector. If no suitable bean could be found, {@link IllegalStateException} will be thrown. + *

+ * Field in target bean will be found by {@link #field()}. If no field could be found, {@link IllegalStateException} will be thrown. + *

+ * + * Typical usage might be:

+ * @RunWith(SpringRunner.class)
+ * public class ExampleServiceTest {
+ *
+ *      @Autowired
+ *      private ExampleService service;
+ *
+ *      @MockBeanInjector(type = ExampleService.class, field = "fieldA")
+ *      private FieldAClass mock;
+ *
+ *      @Test
+ *      public void testInjectExampleServiceFieldA() {
+ *          // 1. mock external dependency
+ *          given(mock.callSomeMethod(...))
+ *              .willReturn(...);
+ *
+ *          // 2. perform testing
+ *          service.doSomething();
+ *
+ *          // 3. behavioral-driven testing / standard unit-testing
+ *          then(mock)
+ *              .should(atLeastOnce())
+ *              .callSomeMethod(...);
+ *
+ *          assertThat(...)...;
+ *      }
+ *
+ *      #064;Configuration
+ *      @Import(ExampleService.class) // A @Component injected with ExampleService
+ *      static class Config {
+ *      }
+ * }
+ * 
+ * If there is more than one bean of the requested type, qualifier metadata must be + * specified at field level:
+ * @RunWith(SpringRunner.class)
+ * public class ExampleTests {
+ *
+ *     @MockBeanInjector(type = ExampleService.class, field = "fieldA")
+ *     @Qualifier("example")
+ *     private ExampleService service;
+ *
+ *     ...
+ * }
+ * 
+ * @author pengym + * @version MockBeanInjector.java, v 0.1 2023年08月07日 15:32 pengym + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MockBeanInjector { + + /** + * The name for field which should inject the mock. + *

When can not find the target field, an {@link IllegalStateException} will be thrown. + */ + String field(); + + /** + * The name of the bean to inject the mock to a field. + * @return the name of the target bean + */ + String name() default ""; + + /** + * The class type of the bean to inject the mock to a field. This is an alias of {@link #type()} which can be used for + * brevity if no other attributes are defined. See {@link #type()} for details. + * @return the class ype of the target bean + */ + @AliasFor("type") + Class value() default void.class; + + /** + * The class type of the bean to inject the mock to a field + * @return the class ype of the target bean + */ + @AliasFor("value") + Class type() default void.class; + + /** + * The application context id to find the target bean. If not specified, the root application context will be used. + *

When can not find the target SOFA module for the specified module name, an {@link IllegalStateException} will be thrown. + */ + String module() default ""; + + /** + * Any extra interfaces that should also be declared on the mock. See + * {@link MockSettings#extraInterfaces(Class...)} for details. + * @return any extra interfaces + */ + Class[] extraInterfaces() default {}; + + /** + * The {@link Answers} type to use on the mock. + * @return the answer type + */ + Answers answer() default Answers.RETURNS_DEFAULTS; + + /** + * If the generated mock is serializable. See {@link MockSettings#serializable()} for + * details. + * @return if the mock is serializable + */ + boolean serializable() default false; + + /** + * The reset mode to apply to the mock. The default is {@link MockReset#AFTER} + * meaning that mocks are automatically reset after each test method is invoked. + * @return the reset mode + */ + MockReset reset() default MockReset.AFTER; +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/SpyBeanInjector.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/SpyBeanInjector.java new file mode 100644 index 0000000000000000000000000000000000000000..94b3e76fa75190a29e1c4a4e3331dbd519c9f527 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/SpyBeanInjector.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.test.mock.injector.annotation; + +import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.core.annotation.AliasFor; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that can be used to create spies and inject spy to a target bean's field. + *

+ * Injector target bean can be found by type or by {@link #name() bean name}. When registered by + * type, any existing single bean of a matching type (including subclasses) in the context + * will be found for injector. If no suitable bean could be found, {@link IllegalStateException} will be thrown. + *

+ * Field in target bean will be found by {@link #field()}. If no field could be found, {@link IllegalStateException} will be thrown. + *

+ * + * Typical usage might be:

+ * @RunWith(SpringRunner.class)
+ * public class ExampleServiceTest {
+ *
+ *      @Autowired
+ *      private ExampleService service;
+ *
+ *      @SpyBeanInjector(type = ExampleService.class, field = "fieldA")
+ *      private FieldAClass spy;
+ *
+ *      @Test
+ *      public void testInjectExampleServiceFieldA() {
+ *          // 1. spy external dependency
+ *          given(spy.callSomeMethod(...))
+ *              .willReturn(...);
+ *
+ *          // 2. perform testing
+ *          service.doSomething();
+ *
+ *          // 3. behavioral-driven testing / standard unit-testing
+ *          then(spy)
+ *              .should(atLeastOnce())
+ *              .callSomeMethod(...);
+ *
+ *          assertThat(...)...;
+ *      }
+ *
+ *      #064;Configuration
+ *      @Import(ExampleService.class) // A @Component injected with ExampleService
+ *      static class Config {
+ *      }
+ * }
+ * 
+ * If there is more than one bean of the requested type, qualifier metadata must be + * specified at field level:
+ * @RunWith(SpringRunner.class)
+ * public class ExampleTests {
+ *
+ *     @SpyBeanInjector(type = ExampleService.class, field = "fieldA")
+ *     @Qualifier("example")
+ *     private ExampleService service;
+ *
+ *     ...
+ * }
+ * 
+ * @author pengym + * @version SpyBeanInjector.java, v 0.1 2023年08月07日 15:38 pengym + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SpyBeanInjector { + + /** + * The name for field which should inject the spy. + *

When can not find the target field, an {@link IllegalStateException} will be thrown. + */ + String field(); + + /** + * The name of the bean to inject the spy to a field. + * @return the name of the target bean + */ + String name() default ""; + + /** + * The class type of the bean to inject the spy to a field. This is an alias of {@link #type()} which can be used for + * brevity if no other attributes are defined. See {@link #type()} for details. + * @return the class type of the target bean + */ + @AliasFor("type") + Class value() default void.class; + + /** + * The class type of the bean to inject the spy to a field + * @return the class ype of the target bean + */ + @AliasFor("value") + Class type() default void.class; + + /** + * The application context id to find the target bean. If not specified, the root application context will be used. + *

When can not find the target SOFA module for the specified module name, an {@link IllegalStateException} will be thrown. + */ + String module() default ""; + + /** + * The reset mode to apply to the spy. The default is {@link MockReset#AFTER} + * meaning that spies are automatically reset after each test method is invoked. + * @return the reset mode + */ + MockReset reset() default MockReset.AFTER; + + /** + * Indicates that Mockito methods such as {@link Mockito#verify(Object) verify(mock)} + * should use the {@code target} of AOP advised beans, rather than the proxy itself. + * If set to {@code false} you may need to use the result of + * {@link org.springframework.test.util.AopTestUtils#getUltimateTargetObject(Object) + * AopTestUtils.getUltimateTargetObject(...)} when calling Mockito methods. + * @return {@code true} if the target of AOP advised beans is used or {@code false} if + * the proxy is used directly + */ + boolean proxyTargetAware() default true; + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/Definition.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/Definition.java new file mode 100644 index 0000000000000000000000000000000000000000..1a6effac597e08443003c24be13ccdea0a5e6eb1 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/Definition.java @@ -0,0 +1,132 @@ +/* + * 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.test.mock.injector.definition; + +import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.core.ResolvableType; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Base class for {@link MockDefinition} or {@link SpyDefinition} + * + * @author pengym + */ +public abstract class Definition { + + private static final int MULTIPLIER = 31; + + private final String name; + + private final ResolvableType type; + + private final String module; + + private final String field; + + private final MockReset reset; + + private final QualifierDefinition qualifier; + + private final ResolvableType mockType; + + protected Object mockInstance; + + public Definition(ResolvableType mockType, String name, ResolvableType type, String module, + String field, MockReset reset, QualifierDefinition qualifier) { + Assert.notNull(mockType, "MockType must not be null"); + this.mockType = mockType; + this.type = type; + this.name = name; + this.module = module; + this.field = field; + this.reset = (reset != null) ? reset : MockReset.AFTER; + this.qualifier = qualifier; + } + + public String getName() { + return name; + } + + public ResolvableType getType() { + return type; + } + + public String getModule() { + return module; + } + + public String getField() { + return field; + } + + public MockReset getReset() { + return reset; + } + + public ResolvableType getMockType() { + return this.mockType; + } + + public QualifierDefinition getQualifier() { + return qualifier; + } + + public Object getMockInstance() { + return mockInstance; + } + + public void resetMock() { + if (mockInstance != null) { + Mockito.reset(mockInstance); + } + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || !getClass().isAssignableFrom(obj.getClass())) { + return false; + } + Definition other = (Definition) obj; + boolean result = true; + result = result && ObjectUtils.nullSafeEquals(this.mockType, other.mockType); + result = result && ObjectUtils.nullSafeEquals(this.name, other.name); + result = result && ObjectUtils.nullSafeEquals(this.type, other.type); + result = result && ObjectUtils.nullSafeEquals(this.module, other.module); + result = result && ObjectUtils.nullSafeEquals(this.field, other.field); + result = result && ObjectUtils.nullSafeEquals(this.reset, other.reset); + result = result && ObjectUtils.nullSafeEquals(this.qualifier, other.qualifier); + return result; + } + + @Override + public int hashCode() { + int result = 1; + result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.mockType); + result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.name); + result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.type); + result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.module); + result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.field); + result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.reset); + result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.qualifier); + return result; + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/MockDefinition.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/MockDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..3d7abdd264f92eb8b9cea13f1c4aa1637028506d --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/MockDefinition.java @@ -0,0 +1,129 @@ +/* + * 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.test.mock.injector.definition; + +import org.mockito.Answers; +import org.mockito.MockSettings; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.core.ResolvableType; +import org.springframework.core.style.ToStringCreator; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import static org.mockito.Mockito.mock; + +/** + * A complete definition that can be used to create a Mockito mock. + * + * @author pengym + * @version MockDefinition.java, v 0.1 2023年08月07日 19:42 pengym + */ +public class MockDefinition extends Definition { + + private static final int MULTIPLIER = 31; + + private final Set> extraInterfaces; + + private final Answers answer; + + private final boolean serializable; + + public MockDefinition(ResolvableType resolvableType, String name, ResolvableType type, + String module, String field, Class[] extraInterfaces, Answers answer, + boolean serializable, MockReset reset, QualifierDefinition qualifier) { + super(resolvableType, name, type, module, field, reset, qualifier); + this.extraInterfaces = asClassSet(extraInterfaces); + this.answer = (answer != null) ? answer : Answers.RETURNS_DEFAULTS; + this.serializable = serializable; + } + + public Set> getExtraInterfaces() { + return extraInterfaces; + } + + public Answers getAnswer() { + return answer; + } + + public boolean isSerializable() { + return serializable; + } + + @SuppressWarnings("unchecked") + public T createMock() { + if (mockInstance == null) { + MockSettings settings = MockReset.withSettings(getReset()); + if (!this.extraInterfaces.isEmpty()) { + settings.extraInterfaces(ClassUtils.toClassArray(this.extraInterfaces)); + } + settings.defaultAnswer(this.answer); + if (this.serializable) { + settings.serializable(); + } + mockInstance = (T) mock(this.getMockType().resolve(), settings); + } + return (T) mockInstance; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != getClass()) { + return false; + } + MockDefinition other = (MockDefinition) obj; + boolean result = super.equals(obj); + result = result && ObjectUtils.nullSafeEquals(this.extraInterfaces, other.extraInterfaces); + result = result && ObjectUtils.nullSafeEquals(this.answer, other.answer); + result = result && this.serializable == other.serializable; + return result; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.extraInterfaces); + result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.answer); + result = MULTIPLIER * result + Boolean.hashCode(this.serializable); + return result; + } + + @Override + public String toString() { + return new ToStringCreator(this).append("mockType", this.getMockType()) + .append("name", this.getName()).append("type", this.getType()) + .append("module", this.getModule()).append("field", this.getField()) + .append("extraInterfaces", this.extraInterfaces).append("answer", this.answer) + .append("serializable", this.serializable).append("reset", getReset()) + .append("qualifier", getQualifier()).toString(); + } + + private Set> asClassSet(Class[] classes) { + Set> classSet = new LinkedHashSet<>(); + if (classes != null) { + classSet.addAll(Arrays.asList(classes)); + } + return Collections.unmodifiableSet(classSet); + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinition.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..fc895cafa8b98d156e464009e8b190257154d96b --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinition.java @@ -0,0 +1,112 @@ +/* + * 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.test.mock.injector.definition; + +import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector; +import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.annotation.MergedAnnotations; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; + +/** + * see org.springframework.boot.test.mock.mockito.QualifierDefinition + * + * @author huzijie + * @version QualifierDefinition.java, v 0.1 2023年08月15日 10:57 AM huzijie Exp $ + */ +public class QualifierDefinition { + + private final Field field; + + private final DependencyDescriptor descriptor; + + private final Set annotations; + + public QualifierDefinition(Field field, Set annotations) { + // We can't use the field or descriptor as part of the context key + // but we can assume that if two fields have the same qualifiers then + // it's safe for Spring to use either for qualifier logic + this.field = field; + this.descriptor = new DependencyDescriptor(field, true); + this.annotations = annotations; + } + + public boolean matches(ConfigurableListableBeanFactory beanFactory, String beanName) { + return beanFactory.isAutowireCandidate(beanName, this.descriptor); + } + + public void applyTo(RootBeanDefinition definition) { + definition.setQualifiedElement(this.field); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || !getClass().isAssignableFrom(obj.getClass())) { + return false; + } + QualifierDefinition other = (QualifierDefinition) obj; + return this.annotations.equals(other.annotations); + } + + @Override + public int hashCode() { + return this.annotations.hashCode(); + } + + public static QualifierDefinition forElement(AnnotatedElement element) { + if (element != null && element instanceof Field) { + Field field = (Field) element; + Set annotations = getQualifierAnnotations(field); + if (!annotations.isEmpty()) { + return new QualifierDefinition(field, annotations); + } + } + return null; + } + + private static Set getQualifierAnnotations(Field field) { + // Assume that any annotations other than @MockBean/@SpyBean are qualifiers + Annotation[] candidates = field.getDeclaredAnnotations(); + Set annotations = new HashSet<>(candidates.length); + for (Annotation candidate : candidates) { + if (!isMockOrSpyAnnotation(candidate.annotationType())) { + annotations.add(candidate); + } + } + return annotations; + } + + private static boolean isMockOrSpyAnnotation(Class type) { + if (type.equals(MockBeanInjector.class) || type.equals(SpyBeanInjector.class)) { + return true; + } + MergedAnnotations metaAnnotations = MergedAnnotations.from(type); + return metaAnnotations.isPresent(MockBeanInjector.class) + || metaAnnotations.isPresent(SpyBeanInjector.class); + } + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinition.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinition.java new file mode 100644 index 0000000000000000000000000000000000000000..ddf6fb32f055cb7bcb329628fa9c8a89606329d7 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinition.java @@ -0,0 +1,123 @@ +/* + * 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.test.mock.injector.definition; + +import org.mockito.AdditionalAnswers; +import org.mockito.MockSettings; +import org.mockito.Mockito; +import org.mockito.listeners.VerificationStartedEvent; +import org.mockito.listeners.VerificationStartedListener; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.core.ResolvableType; +import org.springframework.core.style.ToStringCreator; +import org.springframework.test.util.AopTestUtils; +import org.springframework.util.Assert; + +import java.lang.reflect.Proxy; + +import static org.mockito.Mockito.mock; + +/** + * A complete definition that can be used to create a Mockito spy. + * + * @author pengym + * @version SpyDefinition.java, v 0.1 2023年08月07日 19:42 pengym + */ +public class SpyDefinition extends Definition { + + private static final int MULTIPLIER = 31; + + private final boolean proxyTargetAware; + + public SpyDefinition(ResolvableType resolvableType, String name, ResolvableType type, + String module, String field, MockReset reset, boolean proxyTargetAware, + QualifierDefinition qualifier) { + super(resolvableType, name, type, module, field, reset, qualifier); + this.proxyTargetAware = proxyTargetAware; + } + + @SuppressWarnings("unchecked") + public T createSpy(Object instance) { + if (mockInstance == null) { + Assert.notNull(instance, "Instance must not be null"); + Assert.isInstanceOf(this.getMockType().resolve(), instance); + if (Mockito.mockingDetails(instance).isSpy()) { + return (T) instance; + } + MockSettings settings = MockReset.withSettings(getReset()); + if (this.proxyTargetAware) { + settings + .verificationStartedListeners(new SpyDefinition.SpringAopBypassingVerificationStartedListener()); + } + Class toSpy; + if (Proxy.isProxyClass(instance.getClass())) { + settings.defaultAnswer(AdditionalAnswers.delegatesTo(instance)); + toSpy = this.getMockType().toClass(); + } else { + settings.defaultAnswer(Mockito.CALLS_REAL_METHODS); + settings.spiedInstance(instance); + toSpy = instance.getClass(); + } + mockInstance = mock(toSpy, settings); + } + return (T) mockInstance; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != getClass()) { + return false; + } + SpyDefinition other = (SpyDefinition) obj; + boolean result = super.equals(obj); + result = result && this.proxyTargetAware == other.proxyTargetAware; + return result; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = MULTIPLIER * result + Boolean.hashCode(this.proxyTargetAware); + return result; + } + + @Override + public String toString() { + return new ToStringCreator(this).append("mockType", this.getMockType()) + .append("name", this.getName()).append("type", this.getType()) + .append("module", this.getModule()).append("field", this.getField()) + .append("proxyTargetAware", this.proxyTargetAware).append("reset", getReset()) + .append("qualifier", getQualifier()).toString(); + } + + /** + * A {@link VerificationStartedListener} that bypasses any proxy created by Spring AOP + * when the verification of a spy starts. + */ + private static final class SpringAopBypassingVerificationStartedListener implements + VerificationStartedListener { + + @Override + public void onVerificationStarted(VerificationStartedEvent event) { + event.setMock(AopTestUtils.getUltimateTargetObject(event.getMock())); + } + + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParser.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParser.java new file mode 100644 index 0000000000000000000000000000000000000000..7aae84c4650dca8cce10d626cefb8f2e92ea843b --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParser.java @@ -0,0 +1,118 @@ +/* + * 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.test.mock.injector.parser; + +import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector; +import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector; +import com.alipay.sofa.test.mock.injector.definition.Definition; +import com.alipay.sofa.test.mock.injector.definition.MockDefinition; +import com.alipay.sofa.test.mock.injector.definition.QualifierDefinition; +import com.alipay.sofa.test.mock.injector.definition.SpyDefinition; +import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; +import org.springframework.test.context.TestContext; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** + * Parser for processing the {@link MockBeanInjector} and {@link SpyBeanInjector} annotations for the testClass + * + * @author pengym + * @version SofaBootTestAnnotationParser.java, v 0.1 2023年08月07日 17:52 pengym + */ +public class DefinitionParser { + + private final Set definitions; + + private final Map definitionFields; + + public DefinitionParser() { + this.definitions = new LinkedHashSet<>(); + this.definitionFields = new LinkedHashMap<>(); + } + + /** + * Parse the {@link MockBeanInjector} and {@link SpyBeanInjector} annotations for the testClass + * + * @param testClass The testClass, see {@link TestContext#getTestClass()} + */ + public void parse(Class testClass) { + Assert.notNull(testClass, "testClass must not be null"); + ReflectionUtils.doWithFields(testClass, field -> parseTestField(field, testClass)); + } + + private void parseTestField(Field testField, Class testClass) { + final MergedAnnotations mergedAnnotations = MergedAnnotations.from(testField, SearchStrategy.SUPERCLASS); + mergedAnnotations + .stream(MockBeanInjector.class) + .map(MergedAnnotation::synthesize) + .forEach(annotation -> parseSofaMockBeanAnnotation(annotation, testField, testClass)); + + mergedAnnotations + .stream(SpyBeanInjector.class) + .map(MergedAnnotation::synthesize) + .forEach(annotation -> parseSofaSpyBeanAnnotation(annotation, testField, testClass)); + } + + private void parseSofaMockBeanAnnotation(MockBeanInjector annotation, Field field, + Class testClass) { + ResolvableType typesToMock = deduceType(field, testClass); + MockDefinition mockDefinition = new MockDefinition(typesToMock, annotation.name(), + ResolvableType.forClass(annotation.value()), annotation.module(), annotation.field(), + annotation.extraInterfaces(), annotation.answer(), annotation.serializable(), + annotation.reset(), QualifierDefinition.forElement(field)); + registerDefinition(mockDefinition, field, "mock"); + } + + private void registerDefinition(Definition definition, Field field, String type) { + boolean isNewDefinition = this.definitions.add(definition); + Assert.state(isNewDefinition, () -> "Duplicate " + type + " definition " + definition); + this.definitionFields.put(definition, field); + } + + private void parseSofaSpyBeanAnnotation(SpyBeanInjector annotation, Field field, + Class testClass) { + ResolvableType typesToMock = deduceType(field, testClass); + SpyDefinition spyDefinition = new SpyDefinition(typesToMock, annotation.name(), + ResolvableType.forClass(annotation.value()), annotation.module(), annotation.field(), + annotation.reset(), annotation.proxyTargetAware(), + QualifierDefinition.forElement(field)); + registerDefinition(spyDefinition, field, "spy"); + } + + private ResolvableType deduceType(Field field, Class source) { + return (field.getGenericType() instanceof java.lang.reflect.TypeVariable) ? ResolvableType + .forField(field, source) : ResolvableType.forField(field); + } + + public Set getDefinitions() { + return Collections.unmodifiableSet(this.definitions); + } + + public Field getField(Definition definition) { + return this.definitionFields.get(definition); + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolver.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..0852fd357b6fb5994896fccd52c9caa778d2df6c --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolver.java @@ -0,0 +1,210 @@ +/* + * 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.test.mock.injector.resolver; + +import com.alipay.sofa.boot.constant.SofaBootConstants; +import com.alipay.sofa.boot.error.ErrorCode; +import com.alipay.sofa.isle.IsleDeploymentModel; +import com.alipay.sofa.test.mock.injector.definition.Definition; +import com.alipay.sofa.test.mock.injector.definition.QualifierDefinition; +import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.core.ResolvableType; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + * A resolve used to find inject target bean and create {@link BeanInjectorStub}. + * + * @author pengym + * @version BeanInjectorResolver.java, v 0.1 2023年08月07日 16:58 pengym + */ +public class BeanInjectorResolver { + + private static final String ISLE_MARKER_CLASS = "com.alipay.sofa.isle.ApplicationRuntimeModel"; + + private static final boolean ISLE_MODEL_EXIST = ClassUtils.isPresent( + ISLE_MARKER_CLASS, + null); + + private final ApplicationContext rootApplicationContext; + + private final Map isleApplicationContexts = new LinkedHashMap<>(); + + public BeanInjectorResolver(ApplicationContext applicationContext) { + this.rootApplicationContext = applicationContext; + if (ISLE_MODEL_EXIST) { + if (rootApplicationContext.containsBean(SofaBootConstants.APPLICATION)) { + IsleDeploymentModel isleDeploymentModel = applicationContext.getBean( + SofaBootConstants.APPLICATION, IsleDeploymentModel.class); + isleApplicationContexts + .putAll(isleDeploymentModel.getModuleApplicationContextMap()); + } + } + } + + public BeanInjectorStub resolveStub(Definition definition) { + // find target application context + ApplicationContext applicationContext = getApplicationContext(definition); + ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) applicationContext + .getAutowireCapableBeanFactory(); + + // find target beanName + String beanName = getBeanName(beanFactory, definition); + + // find target bean instance + if (!beanFactory.containsBean(beanName)) { + throw new IllegalStateException(ErrorCode.convert("01-30005", beanName)); + } + Object bean = resolveTargetObject(beanFactory.getBean(beanName)); + + // inject target bean field + return injectTargetBeanField(bean, beanName, definition); + } + + private ApplicationContext getApplicationContext(Definition definition) { + String module = definition.getModule(); + if (StringUtils.hasText(module)) { + ApplicationContext applicationContext = isleApplicationContexts.get(module); + if (applicationContext == null) { + throw new IllegalStateException(ErrorCode.convert("01-30002", module, definition)); + } + return applicationContext; + } else { + return rootApplicationContext; + } + } + + private BeanInjectorStub injectTargetBeanField(Object bean, String beanName, + Definition definition) { + String fieldName = definition.getField(); + Field targetField = ReflectionUtils.findField(bean.getClass(), fieldName); + + if (targetField == null) { + throw new IllegalStateException("Unable to inject target field to bean " + beanName + + ", can not find field " + fieldName + " in " + + bean.getClass()); + } + + BeanInjectorStub beanStubbedField = new BeanInjectorStub(definition, targetField, bean); + beanStubbedField.inject(); + return beanStubbedField; + } + + private Object resolveTargetObject(Object obj) { + if (!AopUtils.isAopProxy(obj) && !AopUtils.isJdkDynamicProxy(obj)) { + return obj; + } + + // AopProxy or JdkDynamicProxy + return AopProxyUtils.getSingletonTarget(obj); + } + + private String getBeanName(ConfigurableListableBeanFactory beanFactory, Definition definition) { + if (StringUtils.hasText(definition.getName())) { + return definition.getName(); + } + Set existingBeans = getExistingBeans(beanFactory, definition.getType(), + definition.getQualifier()); + if (existingBeans.isEmpty()) { + throw new IllegalStateException(ErrorCode.convert("01-30003", definition.getType())); + } + if (existingBeans.size() == 1) { + return existingBeans.iterator().next(); + } + String primaryCandidate = determinePrimaryCandidate(beanFactory, existingBeans, + definition.getType()); + if (primaryCandidate != null) { + return primaryCandidate; + } + throw new IllegalStateException(ErrorCode.convert("01-30004", definition.getType(), + existingBeans)); + } + + private Set getExistingBeans(ConfigurableListableBeanFactory beanFactory, + ResolvableType type, QualifierDefinition qualifier) { + Set candidates = new TreeSet<>(); + for (String candidate : getExistingBeans(beanFactory, type)) { + if (qualifier == null || qualifier.matches(beanFactory, candidate)) { + candidates.add(candidate); + } + } + return candidates; + } + + private Set getExistingBeans(ConfigurableListableBeanFactory beanFactory, ResolvableType resolvableType) { + Set beans = new LinkedHashSet<>( + Arrays.asList(beanFactory.getBeanNamesForType(resolvableType, true, false))); + Class type = resolvableType.resolve(Object.class); + String typeName = type.getName(); + for (String beanName : beanFactory.getBeanNamesForType(FactoryBean.class, true, false)) { + beanName = BeanFactoryUtils.transformedBeanName(beanName); + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + Object attribute = beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE); + if (resolvableType.equals(attribute) || type.equals(attribute) || typeName.equals(attribute)) { + beans.add(beanName); + } + } + beans.removeIf(this::isScopedTarget); + return beans; + } + + private String determinePrimaryCandidate(ConfigurableListableBeanFactory beanFactory, + Collection candidateBeanNames, + ResolvableType type) { + String primaryBeanName = null; + for (String candidateBeanName : candidateBeanNames) { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(candidateBeanName); + if (beanDefinition.isPrimary()) { + if (primaryBeanName != null) { + throw new NoUniqueBeanDefinitionException(type.resolve(), + candidateBeanNames.size(), + "more than one 'primary' bean found among candidates: " + + Collections.singletonList(candidateBeanNames)); + } + primaryBeanName = candidateBeanName; + } + } + return primaryBeanName; + } + + private boolean isScopedTarget(String beanName) { + try { + return ScopedProxyUtils.isScopedTarget(beanName); + } catch (Throwable ex) { + return false; + } + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStub.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStub.java new file mode 100644 index 0000000000000000000000000000000000000000..72f63bfd236f68a18669eeb4fd1fa9e5f204f4ba --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStub.java @@ -0,0 +1,84 @@ +/* + * 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.test.mock.injector.resolver; + +import com.alipay.sofa.boot.error.ErrorCode; +import com.alipay.sofa.test.mock.injector.definition.Definition; +import com.alipay.sofa.test.mock.injector.definition.MockDefinition; +import com.alipay.sofa.test.mock.injector.definition.SpyDefinition; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; + +/** + * A bean injector stub which could transform target field value. + * + * @author huzijie + * @version BeanStubbedField.java, v 0.1 2023年08月17日 7:31 PM huzijie Exp $ + */ +public class BeanInjectorStub { + + /** + * Mock/Spy Definition + */ + private final Definition definition; + + /** + * Field to inject + */ + private final Field field; + + /** + * The original value of the injected field + */ + private final Object originalValue; + + /** + * The bean to inject field + */ + private final Object bean; + + public BeanInjectorStub(Definition definition, Field field, Object bean) { + this.definition = definition; + this.field = field; + this.bean = bean; + ReflectionUtils.makeAccessible(field); + this.originalValue = ReflectionUtils.getField(field, bean); + if (definition instanceof SpyDefinition && this.originalValue == null) { + throw new IllegalStateException(ErrorCode.convert("01-30001", field)); + } + } + + /** + * Inject the mock/spy to target field. + */ + public void inject() { + if (definition instanceof MockDefinition) { + ReflectionUtils.setField(field, bean, ((MockDefinition) definition).createMock()); + } else if (definition instanceof SpyDefinition) { + ReflectionUtils.setField(field, bean, + ((SpyDefinition) definition).createSpy(originalValue)); + } + } + + /** + * Reset the target field value. + */ + public void reset() { + ReflectionUtils.setField(field, bean, originalValue); + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/resources/META-INF/spring.factories b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000000000000000000000000000000000..41b6565dfcd0ecf7659b3222068cebcb04669249 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +# Add TestExecutionListener +org.springframework.test.context.TestExecutionListener=com.alipay.sofa.test.mock.injector.InjectorMockTestExecutionListener diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockApplicationContextCacheTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockApplicationContextCacheTests.java new file mode 100644 index 0000000000000000000000000000000000000000..31e89d319659282ca582084c8ce81f088879e190 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockApplicationContextCacheTests.java @@ -0,0 +1,122 @@ +/* + * 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.test.mock.injector; + +import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector; +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTestContextBootstrapper; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.BootstrapContext; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate; +import org.springframework.test.context.cache.DefaultContextCache; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Ensure mock not dirty application context cache. + * + * @author huzijie + * @version InjectorMockApplicationContextCacheTests.java, v 0.1 2023年08月21日 7:32 PM huzijie Exp $ + */ +public class InjectorMockApplicationContextCacheTests { + + private final DefaultContextCache contextCache = new DefaultContextCache(); + + private final DefaultCacheAwareContextLoaderDelegate delegate = new DefaultCacheAwareContextLoaderDelegate( + this.contextCache); + + @After + @SuppressWarnings("unchecked") + public void clearCache() { + Map contexts = (Map) ReflectionTestUtils + .getField(this.contextCache, "contextMap"); + for (ApplicationContext context : contexts.values()) { + if (context instanceof ConfigurableApplicationContext) { + ((ConfigurableApplicationContext) context).close(); + } + } + this.contextCache.clear(); + } + + @Test + public void useCacheWhenResolveInjectorMockBeanAnnotation() { + bootstrapContext(TestClass.class); + assertThat(this.contextCache.size()).isOne(); + bootstrapContext(MockedBeanTestClass.class); + assertThat(this.contextCache.size()).isOne(); + } + + @SuppressWarnings("rawtypes") + private void bootstrapContext(Class testClass) { + SpringBootTestContextBootstrapper bootstrapper = new SpringBootTestContextBootstrapper(); + BootstrapContext bootstrapContext = mock(BootstrapContext.class); + given((Class) bootstrapContext.getTestClass()).willReturn(testClass); + bootstrapper.setBootstrapContext(bootstrapContext); + given(bootstrapContext.getCacheAwareContextLoaderDelegate()).willReturn(this.delegate); + TestContext testContext = bootstrapper.buildTestContext(); + testContext.getApplicationContext(); + } + + @SpringBootTest(classes = TestConfiguration.class, properties = "spring.application.name=test") + static class TestClass { + + } + + @SpringBootTest(classes = TestConfiguration.class, properties = "spring.application.name=test") + static class MockedBeanTestClass { + + @MockBeanInjector(field = "testBean", type = InjectBean.class) + private TestBean testBean; + + } + + @Configuration + static class TestConfiguration { + + @Bean + TestBean testBean() { + return new TestBean(); + } + + @Bean + InjectBean injectBean() { + return new InjectBean(); + } + + } + + static class TestBean { + + } + + static class InjectBean { + + private TestBean testBean; + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockTestExecutionListenerTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockTestExecutionListenerTests.java new file mode 100644 index 0000000000000000000000000000000000000000..3b53e87cf095073f22add6d4d29685726ae19a5b --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockTestExecutionListenerTests.java @@ -0,0 +1,166 @@ +/* + * 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.test.mock.injector; + +import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector; +import com.alipay.sofa.test.mock.injector.definition.MockDefinition; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import com.alipay.sofa.test.mock.injector.resolver.BeanInjectorStub; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.ResolvableType; +import org.springframework.test.context.TestContext; + +import java.util.HashSet; +import java.util.Set; + +import static com.alipay.sofa.test.mock.injector.InjectorMockTestExecutionListener.STUBBED_DEFINITIONS; +import static com.alipay.sofa.test.mock.injector.InjectorMockTestExecutionListener.STUBBED_FIELDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; + +/** + * Tests for {@link InjectorMockTestExecutionListener}. + * + * @author huzijie + * @version InjectorMockTestExecutionListenerTests.java, v 0.1 2023年08月21日 4:41 PM huzijie Exp $ + */ +public class InjectorMockTestExecutionListenerTests { + + private final InjectorMockTestExecutionListener listener = new InjectorMockTestExecutionListener(); + + @Test + public void prepareTestInstanceShouldInjectMockBean() { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext( + TargetBean.class); + + WithMockBean instance = new WithMockBean(); + TestContext testContext = mockTestContext(instance); + given(testContext.getApplicationContext()).willReturn(applicationContext); + this.listener.prepareTestInstance(testContext); + ExampleService mock = instance.getMockBean(); + assertThat(mock).isNotNull(); + assertThat(Mockito.mockingDetails(mock).isMock()).isTrue(); + + TargetBean targetBean = applicationContext.getBean(TargetBean.class); + ExampleService injectField = targetBean.getFieldA(); + assertThat(mock).isEqualTo(injectField); + + verify(testContext, times(2)).setAttribute(anyString(), any()); + } + + @Test + public void prepareTestInstanceWhenInjectTargetAlreadyExist() { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TargetBean.class); + + WithMockBean instance = new WithMockBean(); + TestContext testContext = mockTestContext(instance); + given(testContext.getApplicationContext()).willReturn(applicationContext); + this.listener.prepareTestInstance(testContext); + assertThatIllegalStateException().isThrownBy(() -> this.listener.prepareTestInstance(testContext)) + .withMessageContaining("The existing value"); + } + + @Test + public void beforeTestMethodShouldRestMock() { + WithMockBean instance = new WithMockBean(); + TestContext testContext = mockTestContext(instance); + + MockDefinition definition = createMockDefinition(ExampleService.class, MockReset.BEFORE); + ExampleService mock = definition.createMock(); + when(mock.greeting()).thenReturn("abc"); + assertThat(mock.greeting()).isEqualTo("abc"); + + Set result = new HashSet(); + result.add(definition); + given(testContext.getAttribute(eq(STUBBED_DEFINITIONS))).willReturn(result); + this.listener.beforeTestMethod(testContext); + + assertThat(mock.greeting()).isEqualTo(null); + } + + @Test + public void afterTestMethodShouldRestMock() { + WithMockBean instance = new WithMockBean(); + TestContext testContext = mockTestContext(instance); + + MockDefinition definition = createMockDefinition(ExampleService.class, MockReset.AFTER); + ExampleService mock = definition.createMock(); + when(mock.greeting()).thenReturn("abc"); + assertThat(mock.greeting()).isEqualTo("abc"); + + Set result = new HashSet(); + result.add(definition); + given(testContext.getAttribute(eq(STUBBED_DEFINITIONS))).willReturn(result); + this.listener.afterTestMethod(testContext); + + assertThat(mock.greeting()).isEqualTo(null); + } + + @Test + public void afterTestClassShouldRestInjectStubs() { + WithMockBean instance = new WithMockBean(); + TestContext testContext = mockTestContext(instance); + + BeanInjectorStub beanInjectorStub = mock(BeanInjectorStub.class); + + Set result = new HashSet(); + result.add(beanInjectorStub); + given(testContext.getAttribute(eq(STUBBED_FIELDS))).willReturn(result); + this.listener.afterTestMethod(testContext); + + verify(beanInjectorStub, only()).reset(); + } + + private MockDefinition createMockDefinition(Class clazz, MockReset mockReset) { + return new MockDefinition(ResolvableType.forClass(clazz), null, null, null, null, null, + null, false, mockReset, null); + } + + private TestContext mockTestContext(Object instance) { + TestContext testContext = mock(TestContext.class); + given(testContext.getTestInstance()).willReturn(instance); + given(testContext.getTestClass()).willReturn((Class) instance.getClass()); + return testContext; + } + + @Configuration + static class TargetBean { + + private ExampleService fieldA; + + public ExampleService getFieldA() { + return fieldA; + } + } + + static class WithMockBean { + + public ExampleService getMockBean() { + return mockBean; + } + + @MockBeanInjector(field = "fieldA", type = TargetBean.class) + private ExampleService mockBean; + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/MockDefinitionTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/MockDefinitionTests.java new file mode 100644 index 0000000000000000000000000000000000000000..15388be94b52b6bcba8b699febe88aebe05b6160 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/MockDefinitionTests.java @@ -0,0 +1,101 @@ +/* + * 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.test.mock.injector.definition; + +import com.alipay.sofa.test.mock.injector.example.ExampleExtraInterface; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import org.junit.Test; +import org.mockito.Answers; +import org.mockito.Mockito; +import org.mockito.mock.MockCreationSettings; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.core.ResolvableType; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link MockDefinition}. + * + * @author huzijie + * @version MockDefinitionTests.java, v 0.1 2023年08月21日 3:30 PM huzijie Exp $ + */ +public class MockDefinitionTests { + + private static final ResolvableType EXAMPLE_SERVICE_TYPE = ResolvableType + .forClass(ExampleService.class); + + @Test + public void classToMockMustNotBeNull() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new MockDefinition(null,null,null, null, null, + null, null, false, null, null)) + .withMessageContaining("MockType must not be null"); + } + + @Test + public void createWithDefaults() { + MockDefinition definition = new MockDefinition(EXAMPLE_SERVICE_TYPE, null, null, null, + "Field", null, null, false, null, null); + assertThat(definition.getName()).isNull(); + assertThat(definition.getModule()).isNull(); + assertThat(definition.getField()).isEqualTo("Field"); + assertThat(definition.getMockType()).isEqualTo(EXAMPLE_SERVICE_TYPE); + assertThat(definition.getType()).isEqualTo(null); + assertThat(definition.getExtraInterfaces()).isEmpty(); + assertThat(definition.getAnswer()).isEqualTo(Answers.RETURNS_DEFAULTS); + assertThat(definition.isSerializable()).isFalse(); + assertThat(definition.getReset()).isEqualTo(MockReset.AFTER); + assertThat(definition.getQualifier()).isNull(); + } + + @Test + public void createExplicit() { + QualifierDefinition qualifier = mock(QualifierDefinition.class); + MockDefinition definition = new MockDefinition(EXAMPLE_SERVICE_TYPE, "name", + EXAMPLE_SERVICE_TYPE, "Module", "Field", + new Class[] { ExampleExtraInterface.class }, Answers.RETURNS_SMART_NULLS, true, + MockReset.BEFORE, qualifier); + assertThat(definition.getName()).isEqualTo("name"); + assertThat(definition.getModule()).isEqualTo("Module"); + assertThat(definition.getField()).isEqualTo("Field"); + assertThat(definition.getType()).isEqualTo(EXAMPLE_SERVICE_TYPE); + assertThat(definition.getMockType()).isEqualTo(EXAMPLE_SERVICE_TYPE); + assertThat(definition.getExtraInterfaces()).containsExactly(ExampleExtraInterface.class); + assertThat(definition.getAnswer()).isEqualTo(Answers.RETURNS_SMART_NULLS); + assertThat(definition.isSerializable()).isTrue(); + assertThat(definition.getReset()).isEqualTo(MockReset.BEFORE); + assertThat(definition.getQualifier()).isEqualTo(qualifier); + } + + @Test + public void createMock() { + MockDefinition definition = new MockDefinition(EXAMPLE_SERVICE_TYPE, "name", + EXAMPLE_SERVICE_TYPE, "Module", "Field", + new Class[] { ExampleExtraInterface.class }, Answers.RETURNS_SMART_NULLS, true, + MockReset.BEFORE, null); + ExampleService mock = definition.createMock(); + MockCreationSettings settings = Mockito.mockingDetails(mock).getMockCreationSettings(); + assertThat(mock).isEqualTo(definition.getMockInstance()); + assertThat(mock).isInstanceOf(ExampleService.class); + assertThat(mock).isInstanceOf(ExampleExtraInterface.class); + assertThat(settings.getDefaultAnswer()).isEqualTo(Answers.RETURNS_SMART_NULLS); + assertThat(settings.isSerializable()).isTrue(); + } + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinitionTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinitionTests.java new file mode 100644 index 0000000000000000000000000000000000000000..2819d91df5298bda2a45039a3b0ad6935be705a8 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinitionTests.java @@ -0,0 +1,164 @@ +/* + * 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.test.mock.injector.definition; + +import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.ReflectionUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.then; + +/** + * Tests for {@link QualifierDefinition}. + * + * @author huzijie + * @version QualifierDefinitionTests.java, v 0.1 2023年08月21日 3:30 PM huzijie Exp $ + */ +@RunWith(MockitoJUnitRunner.class) +public class QualifierDefinitionTests { + + @Mock + private ConfigurableListableBeanFactory beanFactory; + + @Test + public void forElementFieldIsNullShouldReturnNull() { + assertThat(QualifierDefinition.forElement((Field) null)).isNull(); + } + + @Test + public void forElementWhenElementIsNotFieldShouldReturnNull() { + assertThat(QualifierDefinition.forElement(getClass())).isNull(); + } + + @Test + public void forElementWhenElementIsFieldWithNoQualifiersShouldReturnNull() { + QualifierDefinition definition = QualifierDefinition.forElement(ReflectionUtils.findField( + ConfigA.class, "noQualifier")); + assertThat(definition).isNull(); + } + + @Test + public void forElementWhenElementIsFieldWithQualifierShouldReturnDefinition() { + QualifierDefinition definition = QualifierDefinition.forElement(ReflectionUtils.findField( + ConfigA.class, "directQualifier")); + assertThat(definition).isNotNull(); + } + + @Test + public void matchesShouldCallBeanFactory() { + Field field = ReflectionUtils.findField(ConfigA.class, "directQualifier"); + QualifierDefinition qualifierDefinition = QualifierDefinition.forElement(field); + qualifierDefinition.matches(this.beanFactory, "bean"); + then(this.beanFactory).should() + .isAutowireCandidate(eq("bean"), argThat( + (dependencyDescriptor) -> { + assertThat(dependencyDescriptor.getAnnotatedElement()).isEqualTo(field); + return true; + })); + } + + @Test + public void applyToShouldSetQualifierElement() { + Field field = ReflectionUtils.findField(ConfigA.class, "directQualifier"); + QualifierDefinition qualifierDefinition = QualifierDefinition.forElement(field); + RootBeanDefinition definition = new RootBeanDefinition(); + qualifierDefinition.applyTo(definition); + assertThat(definition.getQualifiedElement()).isEqualTo(field); + } + + @Test + public void hashCodeAndEqualsShouldWorkOnDifferentClasses() { + QualifierDefinition directQualifier1 = QualifierDefinition.forElement(ReflectionUtils + .findField(ConfigA.class, "directQualifier")); + QualifierDefinition directQualifier2 = QualifierDefinition.forElement(ReflectionUtils + .findField(ConfigB.class, "directQualifier")); + QualifierDefinition differentDirectQualifier1 = QualifierDefinition + .forElement(ReflectionUtils.findField(ConfigA.class, "differentDirectQualifier")); + QualifierDefinition differentDirectQualifier2 = QualifierDefinition + .forElement(ReflectionUtils.findField(ConfigB.class, "differentDirectQualifier")); + QualifierDefinition customQualifier1 = QualifierDefinition.forElement(ReflectionUtils + .findField(ConfigA.class, "customQualifier")); + QualifierDefinition customQualifier2 = QualifierDefinition.forElement(ReflectionUtils + .findField(ConfigB.class, "customQualifier")); + assertThat(directQualifier1).hasSameHashCodeAs(directQualifier2); + assertThat(differentDirectQualifier1).hasSameHashCodeAs(differentDirectQualifier2); + assertThat(customQualifier1).hasSameHashCodeAs(customQualifier2); + assertThat(differentDirectQualifier1).isEqualTo(differentDirectQualifier1) + .isEqualTo(differentDirectQualifier2).isNotEqualTo(directQualifier2); + assertThat(directQualifier1).isEqualTo(directQualifier1).isEqualTo(directQualifier2) + .isNotEqualTo(differentDirectQualifier1); + assertThat(customQualifier1).isEqualTo(customQualifier1).isEqualTo(customQualifier2) + .isNotEqualTo(differentDirectQualifier1); + } + + @Configuration(proxyBeanMethods = false) + static class ConfigA { + + @MockBeanInjector(field = "Field") + private Object noQualifier; + + @MockBeanInjector(field = "Field") + @Qualifier("test") + private Object directQualifier; + + @MockBeanInjector(field = "Field") + @Qualifier("different") + private Object differentDirectQualifier; + + @MockBeanInjector(field = "Field") + @CustomQualifier + private Object customQualifier; + + } + + static class ConfigB { + + @MockBeanInjector(field = "Field") + @Qualifier("test") + private Object directQualifier; + + @MockBeanInjector(field = "Field") + @Qualifier("different") + private Object differentDirectQualifier; + + @MockBeanInjector(field = "Field") + @CustomQualifier + private Object customQualifier; + + } + + @Qualifier + @Retention(RetentionPolicy.RUNTIME) + public @interface CustomQualifier { + + } + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinitionTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinitionTests.java new file mode 100644 index 0000000000000000000000000000000000000000..15fd537736e252de57e898a594ab3836e78c1cea --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinitionTests.java @@ -0,0 +1,114 @@ +/* + * 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.test.mock.injector.definition; + +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller; +import com.alipay.sofa.test.mock.injector.example.RealExampleService; +import org.junit.Test; +import org.mockito.Answers; +import org.mockito.Mockito; +import org.mockito.mock.MockCreationSettings; +import org.springframework.boot.test.mock.mockito.MockReset; +import org.springframework.core.ResolvableType; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link SpyDefinition}. + * + * @author huzijie + * @version SpyDefinitionTests.java, v 0.1 2023年08月21日 3:31 PM huzijie Exp $ + */ +public class SpyDefinitionTests { + + private static final ResolvableType REAL_SERVICE_TYPE = ResolvableType + .forClass(ExampleService.class); + + @Test + public void classToSpyMustNotBeNull() { + assertThatIllegalArgumentException().isThrownBy(() -> new SpyDefinition(null, null, null, + null, null,null, true, null)) + .withMessageContaining("MockType must not be null"); + } + + @Test + public void createWithDefaults() { + SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE, null, null, null, null, + null, true, null); + assertThat(definition.getName()).isNull(); + assertThat(definition.getType()).isNull(); + assertThat(definition.getModule()).isNull(); + assertThat(definition.getField()).isNull(); + assertThat(definition.getMockType()).isEqualTo(REAL_SERVICE_TYPE); + assertThat(definition.getReset()).isEqualTo(MockReset.AFTER); + assertThat(definition.getQualifier()).isNull(); + } + + @Test + public void createExplicit() { + QualifierDefinition qualifier = mock(QualifierDefinition.class); + SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE, "name", REAL_SERVICE_TYPE, + "Module", "Field", MockReset.BEFORE, false, qualifier); + assertThat(definition.getName()).isEqualTo("name"); + assertThat(definition.getType()).isEqualTo(REAL_SERVICE_TYPE); + assertThat(definition.getModule()).isEqualTo("Module"); + assertThat(definition.getField()).isEqualTo("Field"); + assertThat(definition.getMockType()).isEqualTo(REAL_SERVICE_TYPE); + assertThat(definition.getReset()).isEqualTo(MockReset.BEFORE); + assertThat(definition.getQualifier()).isEqualTo(qualifier); + } + + @Test + public void createSpy() { + SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE, "name", REAL_SERVICE_TYPE, + "Module", "Field", MockReset.BEFORE, false, null); + RealExampleService spy = definition.createSpy(new RealExampleService("hello")); + MockCreationSettings settings = Mockito.mockingDetails(spy).getMockCreationSettings(); + assertThat(spy).isInstanceOf(ExampleService.class); + assertThat(spy).isEqualTo(definition.getMockInstance()); + assertThat(settings.getDefaultAnswer()).isEqualTo(Answers.CALLS_REAL_METHODS); + } + + @Test + public void createSpyWhenNullInstanceShouldThrowException() { + SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE, + "name", REAL_SERVICE_TYPE, "Module", "Field", MockReset.BEFORE, false, null); + assertThatIllegalArgumentException().isThrownBy(() -> definition.createSpy(null)) + .withMessageContaining("Instance must not be null"); + } + + @Test + public void createSpyWhenWrongInstanceShouldThrowException() { + SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE, + "name", REAL_SERVICE_TYPE, "Module", "Field", MockReset.BEFORE, false, null); + assertThatIllegalArgumentException().isThrownBy(() -> definition.createSpy(new ExampleServiceCaller())) + .withMessageContaining("must be an instance of"); + } + + @Test + public void createSpyTwice() { + SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE, "name", REAL_SERVICE_TYPE, + "Module", "Field", MockReset.BEFORE, false, null); + Object instance = new RealExampleService("hello"); + instance = definition.createSpy(instance); + assertThat(instance).isEqualTo(definition.createSpy(instance)); + } + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleExtraInterface.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleExtraInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..8c279a2ec72a575f53150d19d60c2813aa02ae07 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleExtraInterface.java @@ -0,0 +1,26 @@ +/* + * 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.test.mock.injector.example; + +/** + * @author huzijie + * @version ExampleExtraInterface.java, v 0.1 2023年08月21日 3:18 PM huzijie Exp $ + */ +public interface ExampleExtraInterface { + + void doExtra(); +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleService.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleService.java new file mode 100644 index 0000000000000000000000000000000000000000..f31b93e4f7fac78cc75773c2f7cd3fc908097e46 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleService.java @@ -0,0 +1,28 @@ +/* + * 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.test.mock.injector.example; + +/** + * @author huzijie + * @version ExampleService.java, v 0.1 2023年08月21日 3:14 PM huzijie Exp $ + */ +public interface ExampleService { + + String greeting(); + + String hello(); +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCaller.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCaller.java new file mode 100644 index 0000000000000000000000000000000000000000..c949a78992ccce51dc4a49ce7a091db748ed77fd --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCaller.java @@ -0,0 +1,45 @@ +/* + * 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.test.mock.injector.example; + +/** + * @author huzijie + * @version ExampleServiceCaller.java, v 0.1 2023年08月21日 3:16 PM huzijie Exp $ + */ +public class ExampleServiceCaller implements ExampleServiceCallerInterface { + + private ExampleService service; + + public ExampleService getService() { + return this.service; + } + + public void setService(ExampleService service) { + this.service = service; + } + + @Override + public String sayGreeting() { + return this.service.greeting(); + } + + @Override + public String sayHello() { + return service.hello(); + } + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCallerInterface.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCallerInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..d3c5727150479f7206b26ee570db7586ca26ccdf --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCallerInterface.java @@ -0,0 +1,29 @@ +/* + * 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.test.mock.injector.example; + +/** + * @author huzijie + * @version ExampleServiceCallerInterface.java, v 0.1 2023年08月21日 3:16 PM huzijie Exp $ + */ +public interface ExampleServiceCallerInterface { + + String sayGreeting(); + + String sayHello(); + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/RealExampleService.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/RealExampleService.java new file mode 100644 index 0000000000000000000000000000000000000000..7dad35632863771976623b353d35ced515a9cd70 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/RealExampleService.java @@ -0,0 +1,41 @@ +/* + * 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.test.mock.injector.example; + +/** + * @author huzijie + * @version RealExampleService.java, v 0.1 2023年08月21日 3:16 PM huzijie Exp $ + */ +public class RealExampleService implements ExampleService { + + private final String greeting; + + public RealExampleService(String greeting) { + this.greeting = greeting; + } + + @Override + public String greeting() { + return this.greeting; + } + + @Override + public String hello() { + return "hello"; + } + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToAopProxyBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToAopProxyBeanTests.java new file mode 100644 index 0000000000000000000000000000000000000000..e4d3b2ca369f32346c6a090ab8ee26688de1a711 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToAopProxyBeanTests.java @@ -0,0 +1,79 @@ +/* + * 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.test.mock.injector.integration; + +import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller; +import com.alipay.sofa.test.mock.injector.example.ExampleServiceCallerInterface; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link MockBeanInjector} with aop proxy bean. + * + * @author huzijie + * @version InjectMockToNormalBeanTests.java, v 0.1 2023年08月21日 7:53 PM huzijie Exp $ + */ +@SpringBootTest(classes = TestSofaBootApplication.class) +@RunWith(SpringRunner.class) +@Import(InjectMockToAopProxyBeanTests.Config.class) +public class InjectMockToAopProxyBeanTests { + + @MockBeanInjector(field = "service", type = ExampleServiceCallerInterface.class) + private ExampleService exampleService; + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void checkMock() { + when(exampleService.greeting()).thenReturn("amock"); + ExampleServiceCallerInterface bean = this.applicationContext + .getBean(ExampleServiceCallerInterface.class); + assertThat(bean.sayGreeting()).isEqualTo("amock"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + ExampleServiceCallerInterface exampleServiceCaller() { + return new ExampleServiceCaller(); + } + + @Bean + BeanNameAutoProxyCreator beanNameAutoProxyCreator() { + BeanNameAutoProxyCreator autoProxyCreator = new BeanNameAutoProxyCreator(); + autoProxyCreator.setBeanNames("exampleServiceCaller"); + return autoProxyCreator; + } + + } + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToFactoryBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToFactoryBeanTests.java new file mode 100644 index 0000000000000000000000000000000000000000..131638965e7af95b1d23a180a0b9e9d2f17dd353 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToFactoryBeanTests.java @@ -0,0 +1,91 @@ +/* + * 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.test.mock.injector.integration; + +import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.mockito.Mockito.when; + +/** + * Tests for {@link MockBeanInjector} with factory bean. + * + * @author huzijie + * @version InjectMockToFactoryBeanTests.java, v 0.1 2023年08月21日 7:53 PM huzijie Exp $ + */ +@SpringBootTest(classes = TestSofaBootApplication.class) +@RunWith(SpringRunner.class) +@Import(InjectMockToFactoryBeanTests.Config.class) +public class InjectMockToFactoryBeanTests { + + @MockBeanInjector(field = "service", type = ExampleServiceCaller.class) + private ExampleService exampleService; + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void checkMock() { + when(exampleService.greeting()).thenReturn("amock"); + ExampleServiceCaller bean = this.applicationContext.getBean(ExampleServiceCaller.class); + Assertions.assertThat(bean.sayGreeting()).isEqualTo("amock"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + TestFactoryBean testFactoryBean() { + return new TestFactoryBean(); + } + + } + + static class TestFactoryBean implements FactoryBean { + + private ExampleServiceCaller exampleServiceCaller = new ExampleServiceCaller(); + + @Override + public ExampleServiceCaller getObject() { + return exampleServiceCaller; + } + + @Override + public Class getObjectType() { + return ExampleServiceCaller.class; + } + + @Override + public boolean isSingleton() { + return false; + } + + } + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanExtensionTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanExtensionTests.java new file mode 100644 index 0000000000000000000000000000000000000000..824cb26880ca4962f2acd1a063faa36c8c7c7951 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanExtensionTests.java @@ -0,0 +1,28 @@ +/* + * 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.test.mock.injector.integration; + +/** + * Concrete implementation of {@link InjectMockToGenericBeanTestBase}. + * + * @author huzijie + * @version InjectMockToGenericBeanExtensionTests.java, v 0.1 2023年08月21日 8:19 PM huzijie Exp $ + */ +public class InjectMockToGenericBeanExtensionTests + extends + InjectMockToGenericBeanTestBase { +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanTestBase.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanTestBase.java new file mode 100644 index 0000000000000000000000000000000000000000..018907ed3d43dd7d27714d8e9472969e1fe36046 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanTestBase.java @@ -0,0 +1,92 @@ +/* + * 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.test.mock.injector.integration; + +import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MockBeanInjector} with generic bean. + * + * @author huzijie + * @version InjectMockToGenericBeanTestBase.java, v 0.1 2023年08月21日 7:53 PM huzijie Exp $ + */ +@SpringBootTest(classes = TestSofaBootApplication.class) +@RunWith(SpringRunner.class) +@Import(InjectMockToGenericBeanTestBase.Config.class) +abstract class InjectMockToGenericBeanTestBase, U extends InjectMockToGenericBeanTestBase.Something> { + + @MockBeanInjector(field = "something", name = "thing") + private U something; + + @Autowired + private ApplicationContext applicationContext; + + @Test + @SuppressWarnings("unchecked") + public void checkMock() { + T bean = (T) this.applicationContext.getBean(Thing.class); + assertThat(bean.getSomething()).isEqualTo(something); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + ThingImpl thing() { + return new ThingImpl(); + } + + } + + abstract static class Thing { + + protected T something; + + T getSomething() { + return this.something; + } + + void setSomething(T something) { + this.something = something; + } + + } + + static class SomethingImpl extends Something { + + } + + static class ThingImpl extends Thing { + + } + + static class Something { + + } + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToNormalBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToNormalBeanTests.java new file mode 100644 index 0000000000000000000000000000000000000000..f8c6c9f6c3165a27a29b9ab7aff5bfbfeb65cc74 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToNormalBeanTests.java @@ -0,0 +1,69 @@ +/* + * 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.test.mock.injector.integration; + +import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.mockito.Mockito.when; + +/** + * Tests for {@link MockBeanInjector} with normal bean. + * + * @author huzijie + * @version InjectMockToNormalBeanTests.java, v 0.1 2023年08月21日 7:53 PM huzijie Exp $ + */ +@SpringBootTest(classes = TestSofaBootApplication.class) +@RunWith(SpringRunner.class) +@Import(InjectMockToNormalBeanTests.Config.class) +public class InjectMockToNormalBeanTests { + + @MockBeanInjector(field = "service", type = ExampleServiceCaller.class) + private ExampleService exampleService; + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void checkMock() { + when(exampleService.greeting()).thenReturn("amock"); + ExampleServiceCaller bean = this.applicationContext.getBean(ExampleServiceCaller.class); + Assertions.assertThat(bean.sayGreeting()).isEqualTo("amock"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + ExampleServiceCaller exampleServiceCaller() { + return new ExampleServiceCaller(); + } + + } + +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToAopProxyBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToAopProxyBeanTests.java new file mode 100644 index 0000000000000000000000000000000000000000..90458773aa5683d32412b658320e5c26de00501a --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToAopProxyBeanTests.java @@ -0,0 +1,83 @@ +/* + * 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.test.mock.injector.integration; + +import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller; +import com.alipay.sofa.test.mock.injector.example.ExampleServiceCallerInterface; +import com.alipay.sofa.test.mock.injector.example.RealExampleService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link SpyBeanInjector} with aop proxy bean. + * + * @author huzijie + * @version InjectSpyToAopProxyBeanTests.java, v 0.1 2023年08月21日 8:24 PM huzijie Exp $ + */ +@SpringBootTest(classes = TestSofaBootApplication.class) +@RunWith(SpringRunner.class) +@Import(InjectSpyToAopProxyBeanTests.Config.class) +public class InjectSpyToAopProxyBeanTests { + + @SpyBeanInjector(field = "service", type = ExampleServiceCaller.class) + private ExampleService exampleService; + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void checkSpy() { + when(exampleService.greeting()).thenReturn("aspy"); + ExampleServiceCallerInterface bean = this.applicationContext + .getBean(ExampleServiceCallerInterface.class); + assertThat(bean.sayGreeting()).isEqualTo("aspy"); + assertThat(bean.sayHello()).isEqualTo("hello"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + ExampleServiceCallerInterface exampleServiceCaller() { + ExampleServiceCaller exampleServiceCaller = new ExampleServiceCaller(); + exampleServiceCaller.setService(new RealExampleService("greeting")); + return exampleServiceCaller; + } + + @Bean + BeanNameAutoProxyCreator beanNameAutoProxyCreator() { + BeanNameAutoProxyCreator autoProxyCreator = new BeanNameAutoProxyCreator(); + autoProxyCreator.setBeanNames("exampleServiceCaller"); + autoProxyCreator.setProxyTargetClass(true); + return autoProxyCreator; + } + + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToFactoryBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToFactoryBeanTests.java new file mode 100644 index 0000000000000000000000000000000000000000..a25c423e9cccf7912e59c0048b6d463349387d13 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToFactoryBeanTests.java @@ -0,0 +1,97 @@ +/* + * 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.test.mock.injector.integration; + +import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller; +import com.alipay.sofa.test.mock.injector.example.RealExampleService; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.mockito.Mockito.when; + +/** + * Tests for {@link SpyBeanInjector} with factory bean. + * + * @author huzijie + * @version InjectSpyToFactoryBeanTests.java, v 0.1 2023年08月21日 8:24 PM huzijie Exp $ + */ +@SpringBootTest(classes = TestSofaBootApplication.class) +@RunWith(SpringRunner.class) +@Import(InjectSpyToFactoryBeanTests.Config.class) +public class InjectSpyToFactoryBeanTests { + + @SpyBeanInjector(field = "service", type = ExampleServiceCaller.class) + private ExampleService exampleService; + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void checkSpy() { + when(exampleService.greeting()).thenReturn("aspy"); + ExampleServiceCaller bean = this.applicationContext.getBean(ExampleServiceCaller.class); + Assertions.assertThat(bean.sayGreeting()).isEqualTo("aspy"); + Assertions.assertThat(bean.sayHello()).isEqualTo("hello"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + TestFactoryBean testFactoryBean() { + return new TestFactoryBean(); + } + + } + + static class TestFactoryBean implements FactoryBean { + + private final ExampleServiceCaller exampleServiceCaller; + + public TestFactoryBean() { + exampleServiceCaller = new ExampleServiceCaller(); + exampleServiceCaller.setService(new RealExampleService("greeting")); + } + + @Override + public ExampleServiceCaller getObject() { + return exampleServiceCaller; + } + + @Override + public Class getObjectType() { + return ExampleServiceCaller.class; + } + + @Override + public boolean isSingleton() { + return false; + } + + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToNormalBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToNormalBeanTests.java new file mode 100644 index 0000000000000000000000000000000000000000..95dc97b61c5af5a94a1dbf8db5f15a1e268c46f4 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToNormalBeanTests.java @@ -0,0 +1,72 @@ +/* + * 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.test.mock.injector.integration; + +import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller; +import com.alipay.sofa.test.mock.injector.example.RealExampleService; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.mockito.Mockito.when; + +/** + * Tests for {@link SpyBeanInjector} with normal bean. + * + * @author huzijie + * @version InjectSpyToNormalBeanTests.java, v 0.1 2023年08月21日 8:24 PM huzijie Exp $ + */ +@SpringBootTest(classes = TestSofaBootApplication.class) +@RunWith(SpringRunner.class) +@Import(InjectSpyToNormalBeanTests.Config.class) +public class InjectSpyToNormalBeanTests { + + @SpyBeanInjector(field = "service", type = ExampleServiceCaller.class) + private ExampleService exampleService; + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void checkSpy() { + when(exampleService.greeting()).thenReturn("aspy"); + ExampleServiceCaller bean = this.applicationContext.getBean(ExampleServiceCaller.class); + Assertions.assertThat(bean.sayGreeting()).isEqualTo("aspy"); + Assertions.assertThat(bean.sayHello()).isEqualTo("hello"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + ExampleServiceCaller exampleServiceCaller() { + ExampleServiceCaller exampleServiceCaller = new ExampleServiceCaller(); + exampleServiceCaller.setService(new RealExampleService("greeting")); + return exampleServiceCaller; + } + + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyWithJdkProxyTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyWithJdkProxyTests.java new file mode 100644 index 0000000000000000000000000000000000000000..131deda57adac39c95bb017b168019be9c24b4df --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyWithJdkProxyTests.java @@ -0,0 +1,77 @@ +/* + * 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.test.mock.injector.integration; + +import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import java.lang.reflect.Proxy; + +import static org.mockito.Mockito.when; + +/** + * Tests for {@link SpyBeanInjector} with a JDK proxy. + * + * @author huzijie + * @version InjectSpyWithJdkProxyTests.java, v 0.1 2023年08月21日 8:35 PM huzijie Exp $ + */ +@SpringBootTest(classes = TestSofaBootApplication.class) +@RunWith(SpringRunner.class) +@Import(InjectSpyWithJdkProxyTests.Config.class) +public class InjectSpyWithJdkProxyTests { + + @SpyBeanInjector(field = "service", type = ExampleServiceCaller.class) + private ExampleService exampleService; + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void checkSpy() { + when(exampleService.greeting()).thenReturn("aspy"); + ExampleServiceCaller bean = this.applicationContext.getBean(ExampleServiceCaller.class); + Assertions.assertThat(bean.sayGreeting()).isEqualTo("aspy"); + Assertions.assertThat(bean.sayHello()).isEqualTo("jdkProxy"); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + ExampleServiceCaller exampleServiceCaller() { + ExampleServiceCaller exampleServiceCaller = new ExampleServiceCaller(); + exampleServiceCaller.setService(exampleService()); + return exampleServiceCaller; + } + + private ExampleService exampleService() { + return (ExampleService) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] { ExampleService.class }, (proxy, method, args) -> "jdkProxy"); + } + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/TestSofaBootApplication.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/TestSofaBootApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..883f80bc71da9d0b224e324559bdc5dfd99c7cf6 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/TestSofaBootApplication.java @@ -0,0 +1,33 @@ +/* + * 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.test.mock.injector.integration; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author huzijie + * @version TestSofaBootApplication.java, v 0.1 2023年08月17日 8:46 PM huzijie Exp $ + */ +@SpringBootApplication +public class TestSofaBootApplication { + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(); + application.run(args); + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParserTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParserTests.java new file mode 100644 index 0000000000000000000000000000000000000000..1bc410317fb96dd68ebcaf54ddb550d9461ea1d5 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParserTests.java @@ -0,0 +1,140 @@ +/* + * 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.test.mock.injector.parser; + +import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector; +import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector; +import com.alipay.sofa.test.mock.injector.definition.Definition; +import com.alipay.sofa.test.mock.injector.definition.MockDefinition; +import com.alipay.sofa.test.mock.injector.definition.SpyDefinition; +import com.alipay.sofa.test.mock.injector.example.ExampleExtraInterface; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import org.junit.Test; +import org.mockito.Answers; +import org.springframework.boot.test.mock.mockito.MockReset; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link DefinitionParser}. + * + * @author huzijie + * @version DefinitionParserTests.java, v 0.1 2023年08月21日 2:57 PM huzijie Exp $ + */ +public class DefinitionParserTests { + + private final DefinitionParser parser = new DefinitionParser(); + + @Test + public void parseSingleMockBeanInjector() { + this.parser.parse(SingleMockBeanInjector.class); + assertThat(getDefinitions()).hasSize(1); + assertThat(getMockDefinition(0).getMockType().resolve()).isEqualTo(ExampleService.class); + } + + @Test + public void parseMockBeanInjectorAttributes() { + this.parser.parse(MockBeanInjectorAttributes.class); + assertThat(getDefinitions()).hasSize(1); + MockDefinition definition = getMockDefinition(0); + assertThat(definition.getField()).isEqualTo("Field"); + assertThat(definition.getName()).isEqualTo("Name"); + assertThat(definition.getModule()).isEqualTo("Module"); + assertThat(definition.getMockType().resolve()).isEqualTo(ExampleService.class); + assertThat(definition.getExtraInterfaces()).containsExactly(ExampleExtraInterface.class); + assertThat(definition.getAnswer()).isEqualTo(Answers.RETURNS_SMART_NULLS); + assertThat(definition.isSerializable()).isTrue(); + assertThat(definition.getReset()).isEqualTo(MockReset.NONE); + assertThat(definition.getQualifier()).isNull(); + } + + @Test + public void parseDuplicateMockBeanInjector() { + assertThatIllegalStateException().isThrownBy(() -> this.parser.parse(DuplicateMockBeanInjector.class)) + .withMessageContaining("Duplicate mock definition"); + } + + @Test + public void parseSingleSpyBeanInjector() { + this.parser.parse(SingleSpyBeanInjector.class); + assertThat(getDefinitions()).hasSize(1); + assertThat(getSpyDefinition(0).getMockType().resolve()).isEqualTo(ExampleService.class); + } + + @Test + public void parseSpyBeanInjectorAttributes() { + this.parser.parse(SpyBeanInjectorAttributes.class); + assertThat(getDefinitions()).hasSize(1); + SpyDefinition definition = getSpyDefinition(0); + assertThat(definition.getField()).isEqualTo("Field"); + assertThat(definition.getName()).isEqualTo("Name"); + assertThat(definition.getModule()).isEqualTo("Module"); + assertThat(definition.getMockType().resolve()).isEqualTo(ExampleService.class); + assertThat(definition.getReset()).isEqualTo(MockReset.NONE); + assertThat(definition.getQualifier()).isNull(); + } + + private MockDefinition getMockDefinition(int index) { + return (MockDefinition) getDefinitions().get(index); + } + + private SpyDefinition getSpyDefinition(int index) { + return (SpyDefinition) getDefinitions().get(index); + } + + private List getDefinitions() { + return new ArrayList<>(this.parser.getDefinitions()); + } + + static class SingleMockBeanInjector { + + @MockBeanInjector(field = "exampleService", type = ExampleService.class) + private ExampleService exampleService; + + } + + static class MockBeanInjectorAttributes { + + @MockBeanInjector(field = "Field", module = "Module", name = "Name", type = ExampleService.class, extraInterfaces = ExampleExtraInterface.class, answer = Answers.RETURNS_SMART_NULLS, serializable = true, reset = MockReset.NONE) + private ExampleService exampleService; + } + + static class DuplicateMockBeanInjector { + + @MockBeanInjector(field = "exampleService", type = ExampleService.class) + private ExampleService exampleServiceA; + + @MockBeanInjector(field = "exampleService", type = ExampleService.class) + private ExampleService exampleServiceB; + } + + static class SingleSpyBeanInjector { + + @SpyBeanInjector(field = "exampleService", type = ExampleService.class) + private ExampleService exampleService; + } + + static class SpyBeanInjectorAttributes { + + @SpyBeanInjector(field = "Field", module = "Module", name = "Name", type = ExampleService.class, reset = MockReset.NONE, proxyTargetAware = false) + private ExampleService exampleService; + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolverTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolverTests.java new file mode 100644 index 0000000000000000000000000000000000000000..c6c1b65a0e8cb2bb958781bceee9688ba5fd7697 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolverTests.java @@ -0,0 +1,400 @@ +/* + * 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.test.mock.injector.resolver; + +import com.alipay.sofa.boot.constant.SofaBootConstants; +import com.alipay.sofa.isle.IsleDeploymentModel; +import com.alipay.sofa.test.mock.injector.definition.Definition; +import com.alipay.sofa.test.mock.injector.definition.MockDefinition; +import com.alipay.sofa.test.mock.injector.definition.QualifierDefinition; +import com.alipay.sofa.test.mock.injector.definition.SpyDefinition; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import com.alipay.sofa.test.mock.injector.example.RealExampleService; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.ResolvableType; +import org.springframework.util.ReflectionUtils; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link BeanInjectorResolver}. + * + * @author huzijie + * @version BeanInjectorResolverTests.java, v 0.1 2023年08月21日 5:35 PM huzijie Exp $ + */ +public class BeanInjectorResolverTests { + + @Test + public void targetModuleExist() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext( + TargetClass.class); + IsleDeploymentModel isleDeploymentModel = mock(IsleDeploymentModel.class); + Map map = new HashMap(); + map.put("testModule", applicationContext); + when(isleDeploymentModel.getModuleApplicationContextMap()).thenReturn(map); + applicationContext.getBeanFactory().registerSingleton(SofaBootConstants.APPLICATION, + isleDeploymentModel); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class), + "targetClass", null, "testModule", "exampleService", null, null, false, null, null); + BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition); + stub.inject(); + + TargetClass targetClass = applicationContext.getBean(TargetClass.class); + assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue(); + } + + @Test + public void targetModuleNotExist() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(TargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition( + ResolvableType.forClass(ExampleService.class), "targetClass", null, "testModule", "exampleService", + null, null, false, null, null); + assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition)) + .withMessageContaining("Unable to find target module [testModule] when resolve injector"); + } + + @Test + public void findTargetBeanByName() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext( + TargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class), + "targetClass", null, null, "exampleService", null, null, false, null, null); + BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition); + stub.inject(); + + TargetClass targetClass = applicationContext.getBean(TargetClass.class); + assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue(); + } + + @Test + public void findTargetBeanByNameButNoBeanExist() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(TargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition( + ResolvableType.forClass(ExampleService.class), "noExistBean", null, null, "exampleService", + null, null, false, null, null); + assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition)) + .withMessageContaining("Unable to create bean injector to bean [noExistBean] target bean not exist"); + } + + @Test + public void findTargetBeanByClass() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext( + TargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class), + null, ResolvableType.forClass(TargetClass.class), null, "exampleService", null, null, + false, null, null); + BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition); + stub.inject(); + + TargetClass targetClass = applicationContext.getBean(TargetClass.class); + assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue(); + } + + @Test + public void findTargetBeanByClassButNoBeanExist() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(TargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition( + ResolvableType.forClass(ExampleService.class), null, ResolvableType.forClass(ExampleService.class), null, "exampleService", + null, null, false, null, null); + assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition)) + .withMessageContaining("expected a single matching bean to injector but no bean found"); + } + + @Test + public void findTargetBeanByClassButNoBeanExistCausedByScope() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScopeTargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition( + ResolvableType.forClass(ExampleService.class), null, ResolvableType.forClass(ExampleService.class), null, "exampleService", + null, null, false, null, null); + assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition)) + .withMessageContaining("expected a single matching bean to injector but no bean found"); + } + + @Test + public void findTargetBeanByClassButMultiBeanFound() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(MultiTargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition( + ResolvableType.forClass(ExampleService.class), null, ResolvableType.forClass(TargetClass.class), null, "exampleService", + null, null, false, null, null); + assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition)) + .withMessageContaining("expected a single matching bean to injector but found [targetClassA, targetClassB]"); + } + + @Test + public void findTargetBeanByClassWithQualifier() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext( + MultiTargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class), + null, ResolvableType.forClass(TargetClass.class), null, "exampleService", null, null, + false, null, QualifierDefinition.forElement(ReflectionUtils.findField( + QualifierClass.class, "targetClassField"))); + BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition); + stub.inject(); + + TargetClass targetClass = applicationContext.getBean("targetClassA", TargetClass.class); + assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue(); + } + + @Test + public void findTargetBeanByClassWithPrimary() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext( + OnePrimaryTargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class), + null, ResolvableType.forClass(TargetClass.class), null, "exampleService", null, null, + false, null, null); + BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition); + stub.inject(); + + TargetClass targetClass = applicationContext.getBean("targetClassA", TargetClass.class); + assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue(); + } + + @Test + public void findTargetBeanByClassButMultiPrimaryBeanFound() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(MultiPrimaryTargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition( + ResolvableType.forClass(ExampleService.class), null, ResolvableType.forClass(TargetClass.class), null, "exampleService", + null, null, false, null, null); + assertThatThrownBy(() -> beanInjectorResolver.resolveStub(definition)) + .isInstanceOf(NoUniqueBeanDefinitionException.class) + .hasMessageContaining("more than one 'primary' bean found among candidates: [[targetClassA, targetClassB]]"); + } + + @Test + public void targetFieldCannotBeFound() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(TargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition( + ResolvableType.forClass(ExampleService.class), "targetClass", null, null, "exampleServiceA", + null, null, false, null, null); + assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition)) + .withMessageContaining("Unable to inject target field to bean targetClass, can not find field exampleServiceA in class com.alipay.sofa.test.mock.injector.resolver.BeanInjectorResolverTests$TargetClass"); + } + + @Test + public void jdkProxyBeanInject() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext( + JdkProxyTargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class), + "targetClass", null, null, "exampleService", null, null, false, null, null); + BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition); + stub.inject(); + + TargetInterface targetClass = applicationContext.getBean(TargetInterface.class); + assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue(); + } + + @Test + public void cglibProxyBeanInject() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext( + CglibProxyTargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class), + "targetClass", null, null, "exampleService", null, null, false, null, null); + BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition); + stub.inject(); + + TargetInterface targetClass = applicationContext.getBean(TargetInterface.class); + assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue(); + } + + @Test + public void spyTargetBeanWhenFieldIsNull() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext( + TargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + + Definition definition = new SpyDefinition(ResolvableType.forClass(ExampleService.class), + "targetClass", null, null, "exampleService", null, false, null); + assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition)) + .withMessageContaining("Unable to create spy to inject target field private com.alipay.sofa.test.mock.injector.example.ExampleService com.alipay.sofa.test.mock.injector.resolver.BeanInjectorResolverTests$TargetClass.exampleService when origin value is null"); + } + + @Test + public void spyTargetBean() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext( + TargetClass.class); + BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext); + TargetClass targetClass = applicationContext.getBean(TargetClass.class); + targetClass.setExampleService(new RealExampleService("test")); + + Definition definition = new SpyDefinition(ResolvableType.forClass(ExampleService.class), + "targetClass", null, null, "exampleService", null, false, null); + BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition); + stub.inject(); + + assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isSpy()).isTrue(); + } + + interface TargetInterface { + + ExampleService getExampleService(); + + } + + @Configuration(value = "targetClass", proxyBeanMethods = false) + static class TargetClass implements TargetInterface { + + private ExampleService exampleService; + + public ExampleService getExampleService() { + return exampleService; + } + + public void setExampleService(ExampleService exampleService) { + this.exampleService = exampleService; + } + } + + @Configuration + static class MultiTargetClass { + + @Bean + public TargetClass targetClassA() { + return new TargetClass(); + } + + @Bean + public TargetClass targetClassB() { + return new TargetClass(); + } + } + + static class QualifierClass { + + @Qualifier("targetClassA") + private TargetClass targetClassField; + } + + @Configuration + static class ScopeTargetClass { + + @Bean + @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) + public TargetClass targetClass() { + return new TargetClass(); + } + } + + @Configuration + static class OnePrimaryTargetClass { + + @Bean + @Primary + public TargetClass targetClassA() { + return new TargetClass(); + } + + @Bean + public TargetClass targetClassB() { + return new TargetClass(); + } + } + + @Configuration + static class MultiPrimaryTargetClass { + + @Bean + @Primary + public TargetClass targetClassA() { + return new TargetClass(); + } + + @Bean + @Primary + public TargetClass targetClassB() { + return new TargetClass(); + } + } + + @Configuration + static class JdkProxyTargetClass { + + @Bean + public TargetInterface targetClass() { + return new TargetClass(); + } + + @Bean + public BeanNameAutoProxyCreator beanNameAutoProxyCreator() { + BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator(); + beanNameAutoProxyCreator.setBeanNames("targetClass"); + beanNameAutoProxyCreator.setProxyTargetClass(false); + return beanNameAutoProxyCreator; + } + } + + @Configuration + static class CglibProxyTargetClass { + + @Bean + public TargetInterface targetClass() { + return new TargetClass(); + } + + @Bean + public BeanNameAutoProxyCreator beanNameAutoProxyCreator() { + BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator(); + beanNameAutoProxyCreator.setBeanNames("targetClass"); + beanNameAutoProxyCreator.setProxyTargetClass(true); + return beanNameAutoProxyCreator; + } + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStubTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStubTests.java new file mode 100644 index 0000000000000000000000000000000000000000..f16381beb1e0b7682cfb7a4d9927bc1c20d3896f --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStubTests.java @@ -0,0 +1,98 @@ +/* + * 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.test.mock.injector.resolver; + +import com.alipay.sofa.test.mock.injector.definition.MockDefinition; +import com.alipay.sofa.test.mock.injector.definition.SpyDefinition; +import com.alipay.sofa.test.mock.injector.example.ExampleService; +import com.alipay.sofa.test.mock.injector.example.RealExampleService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link BeanInjectorStub}. + * + * @author huzijie + * @version BeanInjectorStubTests.java, v 0.1 2023年08月21日 4:27 PM huzijie Exp $ + */ +@RunWith(MockitoJUnitRunner.class) +public class BeanInjectorStubTests { + + @Mock + private MockDefinition mockDefinition; + + @Mock + private SpyDefinition spyDefinition; + + private final Field field = ReflectionUtils.findField(TargetClass.class, + "exampleService"); + + private final ExampleService exampleService = new RealExampleService("real"); + + @Test + public void mockBeanInjectorStub() { + TargetClass targetClass = new TargetClass(); + BeanInjectorStub beanInjectorStub = new BeanInjectorStub(mockDefinition, field, targetClass); + assertThat(targetClass.getExampleService()).isNull(); + + when(mockDefinition.createMock()).thenReturn(exampleService); + beanInjectorStub.inject(); + + assertThat(targetClass.getExampleService()).isEqualTo(exampleService); + + beanInjectorStub.reset(); + assertThat(targetClass.getExampleService()).isNull(); + } + + @Test + public void spyBeanInjectorStub() { + TargetClass targetClass = new TargetClass(); + RealExampleService realExampleService = new RealExampleService("real"); + targetClass.setExampleService(realExampleService); + BeanInjectorStub beanInjectorStub = new BeanInjectorStub(spyDefinition, field, targetClass); + + when(spyDefinition.createSpy(any())).thenReturn(exampleService); + beanInjectorStub.inject(); + + assertThat(targetClass.getExampleService()).isEqualTo(exampleService); + + beanInjectorStub.reset(); + assertThat(targetClass.getExampleService()).isEqualTo(realExampleService); + } + + static class TargetClass { + + public void setExampleService(ExampleService exampleService) { + this.exampleService = exampleService; + } + + private ExampleService exampleService; + + public ExampleService getExampleService() { + return exampleService; + } + } +} diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/config/application.properties b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/config/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..8a067cade5fa6c5f9aa10d4aa46ffff6f21378a6 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/config/application.properties @@ -0,0 +1,4 @@ +spring.application.name=smoke-tests-test +logging.path=./logs + + diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/logback.xml b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/logback.xml new file mode 100644 index 0000000000000000000000000000000000000000..9b6f953223f61ad3869a16ef9133f33701da3ae4 --- /dev/null +++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + + + %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg%n + + + + + + + + + \ No newline at end of file diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreator.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreator.java new file mode 100644 index 0000000000000000000000000000000000000000..7c2ee76a290858e8d13a7d85f27b39381b26cf39 --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreator.java @@ -0,0 +1,74 @@ +/* + * 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.boot.aop.framework.autoproxy; + +import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.PatternMatchUtils; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Extension for {@link BeanNameAutoProxyCreator} to support exclude specify bean names. + * + * @author huzijie + * @version ExcludeBeanNameAutoProxyCreator.java, v 0.1 2024年01月04日 4:24 PM huzijie Exp $ + */ +public class ExcludeBeanNameAutoProxyCreator extends BeanNameAutoProxyCreator { + + @Nullable + private List excludeBeanNames; + + /** + * Set the names of the beans that should not automatically get wrapped with proxies. + * A name can specify a prefix to match by ending with "*", e.g. "myBean,tx*" + * will match the bean named "myBean" and all beans whose name start with "tx". + *

NOTE: In case of a FactoryBean, only the objects created by the + * FactoryBean will get proxied. This default behavior applies as of Spring 2.0. + * If you intend to proxy a FactoryBean instance itself (a rare use case, but + * Spring 1.2's default behavior), specify the bean name of the FactoryBean + * including the factory-bean prefix "&": e.g. "&myFactoryBean". + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.beans.factory.BeanFactory#FACTORY_BEAN_PREFIX + */ + public void setExcludeBeanNames(String... beanNames) { + Assert.notEmpty(beanNames, "'excludeBeanNames' must not be empty"); + this.excludeBeanNames = new ArrayList<>(beanNames.length); + for (String mappedName : beanNames) { + this.excludeBeanNames.add(StringUtils.trimWhitespace(mappedName)); + } + } + + @Override + protected boolean isMatch(String beanName, String mappedName) { + return super.isMatch(beanName, mappedName) && !isExcluded(beanName); + } + + private boolean isExcluded(String beanName) { + if (excludeBeanNames != null) { + for (String mappedName : this.excludeBeanNames) { + if (PatternMatchUtils.simpleMatch(mappedName, beanName)) { + return true; + } + } + } + return false; + } +} diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractJarVersionVerifier.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractJarVersionVerifier.java new file mode 100644 index 0000000000000000000000000000000000000000..bb51de040e781e38b2a945b124da436c21ea6c34 --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractJarVersionVerifier.java @@ -0,0 +1,66 @@ +/* + * 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.boot.compatibility; + +import org.springframework.core.env.Environment; + +import java.util.Collection; + +/** + * Abstract class for {@link AbstractSwitchableCompatibilityVerifier} to verify jar compatibility. + * + * @author huzijie + * @version AbstractJarVersionVerifier.java, v 0.1 2023年08月03日 5:14 PM huzijie Exp $ + */ +public abstract class AbstractJarVersionVerifier extends AbstractSwitchableCompatibilityVerifier { + + public AbstractJarVersionVerifier(Environment environment) { + super(environment); + } + + @Override + public CompatibilityPredicate compatibilityPredicate() { + return () -> { + Collection compatibilityPredicates = getJarCompatibilityPredicates(); + if (compatibilityPredicates == null) { + return true; + } + return compatibilityPredicates.stream().allMatch(CompatibilityPredicate::isCompatible); + }; + } + + @Override + public String errorDescription() { + return String.format("SOFABoot is not compatible with jar [%s] for current version.", + name()); + } + + @Override + public String action() { + return String.format( + "Change [%s] to appropriate version," + + "you can visit this doc [%s] and find an appropriate version," + + "If you want to disable this check, just set the property [%s=false].", + name(), doc(), this.enableKey); + } + + public abstract Collection getJarCompatibilityPredicates(); + + public abstract String name(); + + public abstract String doc(); +} diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractSwitchableCompatibilityVerifier.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractSwitchableCompatibilityVerifier.java new file mode 100644 index 0000000000000000000000000000000000000000..a005ec28ce420b1ebbc237482d2a3791b5db9930 --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractSwitchableCompatibilityVerifier.java @@ -0,0 +1,61 @@ +/* + * 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.boot.compatibility; + +import org.springframework.core.env.Environment; + +/** + * Abstract class for {@link CompatibilityVerifier} to support switch. + * + * @author huzijie + * @version AbstractSwitchableCompatibilityVerifier.java, v 0.1 2023年08月03日 6:10 PM huzijie Exp $ + */ +public abstract class AbstractSwitchableCompatibilityVerifier implements CompatibilityVerifier { + + private static final String ENABLE_KEY_FORMAT = "sofa.boot.compatibility-verifier.%s.enabled"; + + protected final Environment environment; + + protected String enableKey; + + public AbstractSwitchableCompatibilityVerifier(Environment environment) { + this.environment = environment; + } + + @Override + public VerificationResult verify() { + this.enableKey = String.format(ENABLE_KEY_FORMAT, enableKey()); + if (!Boolean.parseBoolean(environment.getProperty(enableKey, "true"))) { + return VerificationResult.compatible(); + } + + CompatibilityPredicate compatibilityPredicate = compatibilityPredicate(); + boolean matches = compatibilityPredicate.isCompatible(); + if (matches) { + return VerificationResult.compatible(); + } + return VerificationResult.notCompatible(errorDescription(), action()); + } + + public abstract CompatibilityPredicate compatibilityPredicate(); + + public abstract String errorDescription(); + + public abstract String action(); + + public abstract String enableKey(); +} diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityNotMetException.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityNotMetException.java new file mode 100644 index 0000000000000000000000000000000000000000..c9dc5778d6bd0a2434366a6f127bd8ac688f1eb7 --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityNotMetException.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.boot.compatibility; + +import java.util.List; + +/** + * Exception for not compatibility met. + * + * @author huzijie + * @version CompatibilityNotMetException.java, v 0.1 2023年08月03日 4:40 PM huzijie Exp $ + */ +public class CompatibilityNotMetException extends RuntimeException { + + final List results; + + CompatibilityNotMetException(List results, String errorMessage) { + super("Compatibility checks have failed: " + errorMessage); + this.results = results; + } +} diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityPredicate.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityPredicate.java new file mode 100644 index 0000000000000000000000000000000000000000..d5d43745b2b19e3361a56efc456b14558dc1f351 --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityPredicate.java @@ -0,0 +1,33 @@ +/* + * 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.boot.compatibility; + +/** + * Interface for Predicate compatibility verify result, for form spring cloud. + * + * @author huzijie + * @version CompatibilityPredicate.java, v 0.1 2023年08月03日 4:35 PM huzijie Exp $ + */ +public interface CompatibilityPredicate { + + /** + * whether is compatible + * @return compatible result + */ + boolean isCompatible(); + +} diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityVerifier.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityVerifier.java new file mode 100644 index 0000000000000000000000000000000000000000..6310da1ad87f5203f7c6664b3d6b81b969fc39b1 --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityVerifier.java @@ -0,0 +1,33 @@ +/* + * 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.boot.compatibility; + +/** + * Interface for compatibility verifier, for form spring cloud. + * + * @author huzijie + * @version CompatibilityVerifier.java, v 0.1 2023年08月03日 4:08 PM huzijie Exp $ + */ +public interface CompatibilityVerifier { + + /** + * verify compatibility + * @return verify result + */ + VerificationResult verify(); + +} diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompositeCompatibilityVerifier.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompositeCompatibilityVerifier.java new file mode 100644 index 0000000000000000000000000000000000000000..b1d6d382c5cc499898f9ddae26211f7dacf73669 --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompositeCompatibilityVerifier.java @@ -0,0 +1,56 @@ +/* + * 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.boot.compatibility; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Composite compatibility verifier. + * + * @author huzijie + * @version CompositeCompatibilityVerifier.java, v 0.1 2023年08月03日 4:40 PM huzijie Exp $ + */ +public class CompositeCompatibilityVerifier { + + private final List verifiers; + + public CompositeCompatibilityVerifier(List verifiers) { + this.verifiers = verifiers; + } + + public void verifyCompatibilities() { + List errors = verifierErrors(); + if (errors.isEmpty()) { + return; + } + String errorMessage = errors.stream().map(VerificationResult::toErrorMessage).collect(Collectors.toList()).toString(); + throw new CompatibilityNotMetException(errors, errorMessage); + } + + private List verifierErrors() { + List errors = new ArrayList<>(); + for (CompatibilityVerifier verifier : this.verifiers) { + VerificationResult result = verifier.verify(); + if (result.isNotCompatible()) { + errors.add(result); + } + } + return errors; + } +} diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/VerificationResult.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/VerificationResult.java new file mode 100644 index 0000000000000000000000000000000000000000..b011c26bf5d99e5bf2851b293b49e6d30f2149ab --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/VerificationResult.java @@ -0,0 +1,88 @@ +/* + * 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.boot.compatibility; + +import org.springframework.util.StringUtils; + +import java.util.Objects; + +/** + * Verification result. + * + * @author huzijie + * @version VerificationResult.java, v 0.1 2023年08月03日 4:08 PM huzijie Exp $ + */ +public class VerificationResult { + + private final String description; + + private final String action; + + // if OK + private VerificationResult() { + this.description = ""; + this.action = ""; + } + + // if not OK + private VerificationResult(String errorDescription, String action) { + this.description = errorDescription; + this.action = action; + } + + public static VerificationResult compatible() { + return new VerificationResult(); + } + + public static VerificationResult notCompatible(String errorDescription, String action) { + return new VerificationResult(errorDescription, action); + } + + public boolean isNotCompatible() { + return StringUtils.hasText(this.description) || StringUtils.hasText(this.action); + } + + public String toErrorMessage() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("\n"); + stringBuilder.append("VerificationResult:"); + stringBuilder.append("\n"); + stringBuilder.append("—— description: "); + stringBuilder.append(description); + stringBuilder.append("\n"); + stringBuilder.append("—— action: "); + stringBuilder.append(action); + return stringBuilder.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof VerificationResult)) { + return false; + } + VerificationResult that = (VerificationResult) o; + return description.equals(that.description) && action.equals(that.action); + } + + @Override + public int hashCode() { + return Objects.hash(description, action); + } +} diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/listener/SofaConfigSourceSupportListener.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/listener/SofaConfigSourceSupportListener.java index f730cf5611897f6b72d950a85626e848c36bea55..356f5db7caf4d9ad8d469bcff473428b3f6b578d 100644 --- a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/listener/SofaConfigSourceSupportListener.java +++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/listener/SofaConfigSourceSupportListener.java @@ -25,6 +25,8 @@ import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.util.StringUtils; +import java.util.concurrent.atomic.AtomicBoolean; + /** * add a config source based on {@link ConfigurableEnvironment} * @author huzijie @@ -34,32 +36,40 @@ public class SofaConfigSourceSupportListener implements ApplicationListener, Ordered { - private static final int SOFA_BOOT_CONFIG_SOURCE_ORDER = ApplicationListenerOrderConstants.SOFA_CONFIG_SOURCE_SUPPORT_LISTENER_ORDER; + private static final int SOFA_BOOT_CONFIG_SOURCE_ORDER = ApplicationListenerOrderConstants.SOFA_CONFIG_SOURCE_SUPPORT_LISTENER_ORDER; + + private final AtomicBoolean registered = new AtomicBoolean(); @Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { - ConfigurableEnvironment environment = event.getEnvironment(); - SofaConfigs.addConfigSource(new AbstractConfigSource() { - @Override - public int getOrder() { - return SOFA_BOOT_CONFIG_SOURCE_ORDER; - } + registerSofaConfigs(event.getEnvironment()); + } + + private void registerSofaConfigs(ConfigurableEnvironment environment) { + if (registered.compareAndSet(false, true)) { + SofaConfigs.addConfigSource(new AbstractConfigSource() { + + @Override + public int getOrder() { + return SOFA_BOOT_CONFIG_SOURCE_ORDER; + } - @Override - public String getName() { - return "SOFABootEnv"; - } + @Override + public String getName() { + return "SOFABootEnv"; + } - @Override - public String doGetConfig(String key) { - return environment.getProperty(key); - } + @Override + public String doGetConfig(String key) { + return environment.getProperty(key); + } - @Override - public boolean hasKey(String key) { - return !StringUtils.isEmpty(environment.getProperty(key)); - } - }); + @Override + public boolean hasKey(String key) { + return !StringUtils.isEmpty(environment.getProperty(key)); + } + }); + } } @Override diff --git a/sofa-boot-project/sofa-boot/src/main/resources/sofa-boot/log-codes.properties b/sofa-boot-project/sofa-boot/src/main/resources/sofa-boot/log-codes.properties index f6744ac735e74745723223d0069421d26e5f51c9..5920a0a727ea646fc7f48a20d5f6025bb84f0a72 100644 --- a/sofa-boot-project/sofa-boot/src/main/resources/sofa-boot/log-codes.properties +++ b/sofa-boot-project/sofa-boot/src/main/resources/sofa-boot/log-codes.properties @@ -2,7 +2,7 @@ #Runtime error ##SOFA service error -01-00000=Must contains the target object whiling registering Service +01-00000=Must contain the target object whiling registering Service 01-00001=Can't find BindingAdapter of type %s while registering service %s 01-00002=PreOut Binding [%s] for [%s] occur exception 01-00003=PreOut Binding [%s] occur exception @@ -18,9 +18,10 @@ 01-00102=Unable to get implementation of reference component, there's some error occurred when register this reference component 01-00103=Bean [%s] of type [%s] has already annotated by @SofaService can not be registered using xml. Please check it 01-00104=Bean [%s] type is [%s] not isAssignableFrom [%s] , please check it -01-00200=Can not found binding converter for binding type %s +01-00200=Can not find binding converter for binding type %s 01-00201=Interface type is null. Interface type is required while publish a service 01-00202=Argument delay must be a positive integer or zero +01-00203=SofaService [%s] was already registered, please remove the duplicate registration 01-00400=JVM Reference[%s#%s] can not find the corresponding JVM service. Please check if there is a SOFA deployment publish the corresponding JVM service. If this exception occurred when the application starts up, please add Require-Module to SOFA deployment's MANIFEST.MF to indicate the startup dependency of SOFA modules ##Extension error @@ -58,7 +59,7 @@ 01-11004=Interrupted when wait for Spring Application Context refresh 01-11005=Error occurred when get Spring Application Context refresh future 01-11006=Cannot register module deployment for module name '[%s]': replacing '[%s]' with '[%s]' -01-11007=Some module context(s) %s failed to refresh, please see /logs/sofa-runtime/common-error.log for further information, you could set 'com.alipay.sofa.boot.ignoreModuleInstallFailure=true' to ignore it +01-11007=Some module context(s) %s failed to refresh, please see /logs/sofa-runtime/common-error.log to find root cause ##Module dependency error 01-12000=Modules that could not install(Mainly due to module dependency not satisfied) @@ -99,6 +100,14 @@ 01-24001=SOFABoot ReadinessCheckCallback[%s] check failed, the details is: %s 01-24002=Error occurred while doing ReadinessCheckCallback[%s] check +#Test component error +01-30000=The existing value '%s' of field '%s' is not the same as the new value '%s' +01-30001=Unable to create spy to inject target field %s when origin value is null +01-30002=Unable to find target module [%s] when resolve injector: %s +01-30003=Unable to create bean injector to bean by type [%s] expected a single matching bean to injector but no bean found +01-30004=Unable to create bean injector to bean by type [%s] expected a single matching bean to injector but found %s +01-30005=Unable to create bean injector to bean [%s] target bean not exist + diff --git a/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreatorTest.java b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreatorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..083e0aa20867b73eb3a2a2519f7b9e5cdf9070c8 --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreatorTest.java @@ -0,0 +1,102 @@ +/* + * 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.boot.aop.framework.autoproxy; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ExcludeBeanNameAutoProxyCreator}. + * + * @author huzijie + * @version ExcludeBeanNameAutoProxyCreatorTests.java, v 0.1 2024年01月04日 4:36 PM huzijie Exp $ + */ +public class ExcludeBeanNameAutoProxyCreatorTest { + + @Test + public void excludeBeanNames() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + ExcludeBeanNameAutoProxyCreatorTestConfiguration.class); + SampleInterface sampleA = context.getBean("sampleA", SampleInterface.class); + SampleInterface sampleB = context.getBean("sampleBeanB", SampleInterface.class); + SampleInterface sampleC = context.getBean("sampleBeanC", SampleInterface.class); + assertThat(sampleA.hello()).isEqualTo("hello"); + assertThat(sampleB.hello()).isEqualTo("aop"); + assertThat(sampleC.hello()).isEqualTo("hello"); + } + + @Configuration + static class ExcludeBeanNameAutoProxyCreatorTestConfiguration { + + @Bean + public SampleInterface sampleA() { + return new SampleInterfaceImpl(); + } + + @Bean + public SampleInterface sampleBeanB() { + return new SampleInterfaceImpl(); + } + + @Bean + public SampleInterface sampleBeanC() { + return new SampleInterfaceImpl(); + } + + @Bean + public ExcludeBeanNameAutoProxyCreator excludeBeanNameAutoProxyCreator() { + ExcludeBeanNameAutoProxyCreator autoProxyCreator = new ExcludeBeanNameAutoProxyCreator(); + autoProxyCreator.setBeanNames("sampleBean*"); + autoProxyCreator.setExcludeBeanNames("sampleBeanC"); + autoProxyCreator.setInterceptorNames("sampleAdvisor"); + return autoProxyCreator; + } + + @Bean + public MethodInterceptor sampleAdvisor() { + return new MethodInterceptor() { + @Nullable + @Override + public Object invoke(@Nonnull MethodInvocation invocation) { + return "aop"; + } + }; + } + } + + interface SampleInterface { + + String hello(); + } + + static class SampleInterfaceImpl implements SampleInterface { + + @Override + public String hello() { + return "hello"; + } + } +} diff --git a/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractJarVersionVerifierTest.java b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractJarVersionVerifierTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e4ffd03a0eaea9de439c0c317f31cf0e1e5fd377 --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractJarVersionVerifierTest.java @@ -0,0 +1,73 @@ +/* + * 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.boot.test.compatibility; + +import com.alipay.sofa.boot.compatibility.AbstractJarVersionVerifier; +import com.alipay.sofa.boot.compatibility.CompatibilityPredicate; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.core.env.Environment; +import org.springframework.mock.env.MockEnvironment; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author huzijie + * @version AbstractJarVersionVerifierTest.java, v 0.1 2023年08月07日 12:07 PM huzijie Exp $ + */ +public class AbstractJarVersionVerifierTest { + + private final MockEnvironment mockEnvironment = new MockEnvironment(); + + @Test + public void testJar() { + TestJarVersionVerifier verifier = new TestJarVersionVerifier(mockEnvironment); + Assert.assertTrue(verifier.verify().isNotCompatible()); + } + + public static class TestJarVersionVerifier extends AbstractJarVersionVerifier { + + public TestJarVersionVerifier(Environment environment) { + super(environment); + } + + @Override + public Collection getJarCompatibilityPredicates() { + List list = new ArrayList<>(); + list.add(() -> false); + list.add(() -> true); + return list; + } + + @Override + public String name() { + return "test jar"; + } + + @Override + public String doc() { + return "test doc"; + } + + @Override + public String enableKey() { + return "test"; + } + } +} diff --git a/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractSwitchableCompatibilityVerifierTest.java b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractSwitchableCompatibilityVerifierTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7ab83c77adef1af28c34fff703b7a140d284b125 --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractSwitchableCompatibilityVerifierTest.java @@ -0,0 +1,77 @@ +/* + * 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.boot.test.compatibility; + +import com.alipay.sofa.boot.compatibility.AbstractSwitchableCompatibilityVerifier; +import com.alipay.sofa.boot.compatibility.CompatibilityPredicate; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.core.env.Environment; +import org.springframework.mock.env.MockEnvironment; + +/** + * @author huzijie + * @version AbstractSwitchableCompatibilityVerifierTest.java, v 0.1 2023年08月07日 11:56 AM huzijie Exp $ + */ +public class AbstractSwitchableCompatibilityVerifierTest { + + private final MockEnvironment mockEnvironment = new MockEnvironment(); + + @Test + public void enableKey() { + mockEnvironment.setProperty("sofa.boot.compatibility-verifier.test.enabled", "true"); + TestSwitchableCompatibilityVerifier verifier = new TestSwitchableCompatibilityVerifier( + mockEnvironment); + Assert.assertTrue(verifier.verify().isNotCompatible()); + } + + @Test + public void disableKey() { + mockEnvironment.setProperty("sofa.boot.compatibility-verifier.test.enabled", "false"); + TestSwitchableCompatibilityVerifier verifier = new TestSwitchableCompatibilityVerifier( + mockEnvironment); + Assert.assertFalse(verifier.verify().isNotCompatible()); + } + + public static class TestSwitchableCompatibilityVerifier extends + AbstractSwitchableCompatibilityVerifier { + + public TestSwitchableCompatibilityVerifier(Environment environment) { + super(environment); + } + + @Override + public CompatibilityPredicate compatibilityPredicate() { + return () -> false; + } + + @Override + public String errorDescription() { + return "fafa"; + } + + @Override + public String action() { + return "fafa"; + } + + @Override + public String enableKey() { + return "test"; + } + } +} diff --git a/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/CompositeCompatibilityVerifierTest.java b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/CompositeCompatibilityVerifierTest.java new file mode 100644 index 0000000000000000000000000000000000000000..17694b06b8143bc0e295ef0615fcfd173919f9eb --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/CompositeCompatibilityVerifierTest.java @@ -0,0 +1,65 @@ +/* + * 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.boot.test.compatibility; + +import com.alipay.sofa.boot.compatibility.CompatibilityNotMetException; +import com.alipay.sofa.boot.compatibility.CompatibilityVerifier; +import com.alipay.sofa.boot.compatibility.CompositeCompatibilityVerifier; +import com.alipay.sofa.boot.compatibility.VerificationResult; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author huzijie + * @version CompositeCompatibilityVerifierTest.java, v 0.1 2023年08月07日 12:00 PM huzijie Exp $ + */ +public class CompositeCompatibilityVerifierTest { + + @Test + public void empty() { + CompositeCompatibilityVerifier verifier = new CompositeCompatibilityVerifier( + new ArrayList<>()); + verifier.verifyCompatibilities(); + } + + @Test + public void pass() { + CompatibilityVerifier compatibilityVerifier = VerificationResult::compatible; + List verifiers = new ArrayList<>(); + verifiers.add(compatibilityVerifier); + CompositeCompatibilityVerifier verifier = new CompositeCompatibilityVerifier(verifiers); + verifier.verifyCompatibilities(); + } + + @Test + public void notPass() { + CompatibilityVerifier compatibilityVerifier = () -> VerificationResult.notCompatible("verify error", "do action"); + List verifiers = new ArrayList<>(); + verifiers.add(compatibilityVerifier); + CompositeCompatibilityVerifier verifier = new CompositeCompatibilityVerifier(verifiers); + try { + verifier.verifyCompatibilities(); + Assert.fail(); + } catch (CompatibilityNotMetException e) { + Assert.assertTrue(e.getMessage().contains("description: verify error")); + Assert.assertTrue(e.getMessage().contains("action: do action")); + } + } +} diff --git a/sofa-boot-project/sofaboot-dependencies/pom.xml b/sofa-boot-project/sofaboot-dependencies/pom.xml index c54208700737192614279afd8bac33756235be58..e5b5e49041d8eef9f0f764fcb7a674e3d7a1d5a5 100644 --- a/sofa-boot-project/sofaboot-dependencies/pom.xml +++ b/sofa-boot-project/sofaboot-dependencies/pom.xml @@ -25,7 +25,7 @@ 5.4.2 - 3.1.0 + 3.1.3 5.8.3 @@ -38,13 +38,13 @@ 1.7.32 1.2.69 3.28.0-GA - 3.11.0 3.0 28.2-jre 2.11.3 2.7.7 2.9.10 1.0.2.Final + 3.22.2 3.6.3.Final 4.0.1 @@ -53,7 +53,7 @@ 1.0.0 2.0.0 3.11.0 - 1.28.0 + 1.53.0 1.17.0 @@ -469,6 +469,13 @@ + + com.google.protobuf + protobuf-bom + ${protobuf.version} + import + pom + com.alibaba fastjson @@ -484,11 +491,6 @@ guice-multibindings ${guice.version} - - com.google.protobuf - protobuf-java - ${protobuf-java.version} - org.javassist javassist