This is an automated email from the ASF dual-hosted git repository.
hefengen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu-plugin-store.git
The following commit(s) were added to refs/heads/master by this push:
new 53f6918 [type: refactor] init tarts plugin (#2)
53f6918 is described below
commit 53f6918f7753468015c9308b0c9a61214598b4fc
Author: moremind <[email protected]>
AuthorDate: Mon Mar 30 22:04:22 2026 +0800
[type: refactor] init tarts plugin (#2)
* [type:init] add tars plugin
* [type:init] add tars plugin
---
pom.xml | 18 +-
shenyu-plugin/pom.xml | 4 +
shenyu-plugin/shenyu-plugin-proxy/pom.xml | 1 +
.../shenyu-plugin-proxy/shenyu-plugin-tars/pom.xml | 76 ++++
.../org/apache/shenyu/plugin/tars/TarsPlugin.java | 134 ++++++
.../plugin/tars/cache/ApplicationConfigCache.java | 450 +++++++++++++++++++++
.../tars/context/TarsShenyuContextDecorator.java | 43 ++
.../tars/exception/ShenyuTarsPluginException.java | 38 ++
.../plugin/tars/handler/TarsMetaDataHandler.java | 73 ++++
.../plugin/tars/handler/TarsPluginDataHandler.java | 72 ++++
.../shenyu/plugin/tars/proxy/TarsInvokePrx.java | 81 ++++
.../plugin/tars/proxy/TarsInvokePrxList.java | 131 ++++++
.../shenyu/plugin/tars/util/PrxInfoUtil.java | 180 +++++++++
.../plugin/tars/util/ReturnValueResolver.java | 65 +++
.../apache/shenyu/plugin/tars/TarsPluginTest.java | 167 ++++++++
.../tars/cache/ApplicationConfigCacheTest.java | 147 +++++++
.../tars/handler/TarsMetaDataHandlerTest.java | 60 +++
.../tars/handler/TarsPluginDataHandlerTest.java | 63 +++
.../shenyu/plugin/tars/util/PrxInfoUtilTest.java | 80 ++++
19 files changed, 1878 insertions(+), 5 deletions(-)
diff --git a/pom.xml b/pom.xml
index 35a2433..d186893 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,11 +21,6 @@
<artifactId>apache</artifactId>
<version>21</version>
</parent>
- <modules>
- <module>shenyu-plugin</module>
- <module>shenyu-spring-boot-starter-plugin</module>
- <module>shenyu-plugin/shenyu-plugin-proxy</module>
- </modules>
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-plugin-store</artifactId>
@@ -62,6 +57,11 @@
<url>https://github.com/apache/shenyu/issues</url>
</issueManagement>
+ <modules>
+ <module>shenyu-plugin</module>
+ <module>shenyu-spring-boot-starter-plugin</module>
+ </modules>
+
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -90,6 +90,8 @@
<!-- dependency version start -->
<motan.version>1.2.1</motan.version>
<log4j-1.2-api.vetsion>2.17.2</log4j-1.2-api.vetsion>
+ <bytebuddy.version>1.14.11</bytebuddy.version>
+ <tars.version>1.7.2</tars.version>
<!-- dependency version end -->
</properties>
@@ -118,6 +120,12 @@
<artifactId>motan-springsupport</artifactId>
<version>${motan.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>com.tencent.tars</groupId>
+ <artifactId>tars-spring-boot-starter</artifactId>
+ <version>${tars.version}</version>
+ </dependency>
</dependencies>
</dependencyManagement>
diff --git a/shenyu-plugin/pom.xml b/shenyu-plugin/pom.xml
index d44cc5b..6d6e073 100644
--- a/shenyu-plugin/pom.xml
+++ b/shenyu-plugin/pom.xml
@@ -30,5 +30,9 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
+ <modules>
+ <module>shenyu-plugin-proxy</module>
+ </modules>
+
</project>
\ No newline at end of file
diff --git a/shenyu-plugin/shenyu-plugin-proxy/pom.xml
b/shenyu-plugin/shenyu-plugin-proxy/pom.xml
index 79deb12..d0b4f4e 100644
--- a/shenyu-plugin/shenyu-plugin-proxy/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-proxy/pom.xml
@@ -32,6 +32,7 @@
</properties>
<modules>
<module>shenyu-plugin-motan</module>
+ <module>shenyu-plugin-tars</module>
</modules>
</project>
\ No newline at end of file
diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/pom.xml
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/pom.xml
new file mode 100644
index 0000000..7061ff2
--- /dev/null
+++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-plugin-proxy</artifactId>
+ <version>2.7.1-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>shenyu-plugin-tars</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>${bytebuddy.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.tencent.tars</groupId>
+ <artifactId>tars-spring-boot-starter</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-dependencies</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </exclusion>
+ </exclusions>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/TarsPlugin.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/TarsPlugin.java
new file mode 100644
index 0000000..01593e0
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/TarsPlugin.java
@@ -0,0 +1,134 @@
+/*
+ * 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 org.apache.shenyu.plugin.tars;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.enums.ResultEnum;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.common.exception.ShenyuException;
+import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.api.context.ShenyuContext;
+import org.apache.shenyu.plugin.api.result.ShenyuResultEnum;
+import org.apache.shenyu.plugin.api.result.ShenyuResultWrap;
+import org.apache.shenyu.plugin.api.utils.RequestUrlUtils;
+import org.apache.shenyu.plugin.api.utils.WebFluxResultUtils;
+import org.apache.shenyu.plugin.base.AbstractShenyuPlugin;
+import org.apache.shenyu.plugin.tars.cache.ApplicationConfigCache;
+import org.apache.shenyu.plugin.tars.proxy.TarsInvokePrxList;
+import org.apache.shenyu.plugin.tars.util.PrxInfoUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.lang.reflect.Method;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * The tars plugin.
+ */
+public class TarsPlugin extends AbstractShenyuPlugin {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(TarsPlugin.class);
+
+ @Override
+ protected String getRawPath(final ServerWebExchange exchange) {
+ return RequestUrlUtils.getRewrittenRawPath(exchange);
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ protected Mono<Void> doExecute(final ServerWebExchange exchange, final
ShenyuPluginChain chain, final SelectorData selector, final RuleData rule) {
+ String body = exchange.getAttribute(Constants.PARAM_TRANSFORM);
+ ShenyuContext shenyuContext = exchange.getAttribute(Constants.CONTEXT);
+ Objects.requireNonNull(shenyuContext);
+ MetaData metaData = exchange.getAttribute(Constants.META_DATA);
+ if (!checkMetaData(metaData)) {
+ Objects.requireNonNull(metaData);
+ LOG.error(" path is :{}, meta data have error.... {}",
shenyuContext.getPath(), metaData);
+
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
+ Object error = ShenyuResultWrap.error(exchange,
ShenyuResultEnum.META_DATA_ERROR);
+ return WebFluxResultUtils.result(exchange, error);
+ }
+ if (StringUtils.isNoneBlank(metaData.getParameterTypes()) &&
StringUtils.isBlank(body)) {
+
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
+ Object error = ShenyuResultWrap.error(exchange,
ShenyuResultEnum.TARS_HAVE_BODY_PARAM);
+ return WebFluxResultUtils.result(exchange, error);
+ }
+ TarsInvokePrxList tarsInvokePrxList =
ApplicationConfigCache.getInstance().get(metaData.getPath());
+ int index =
ThreadLocalRandom.current().nextInt(tarsInvokePrxList.getTarsInvokePrxList().size());
+ Object prx =
tarsInvokePrxList.getTarsInvokePrxList().get(index).getInvokePrx();
+ Method method = tarsInvokePrxList.getMethod();
+ CompletableFuture future;
+ try {
+ future = (CompletableFuture) method
+ .invoke(prx,
PrxInfoUtil.getParamArray(tarsInvokePrxList.getParamTypes(),
tarsInvokePrxList.getParamNames(), body));
+ } catch (Exception e) {
+ LOG.error("Invoke tars error", e);
+
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
+ Object error = ShenyuResultWrap.error(exchange,
ShenyuResultEnum.TARS_INVOKE);
+ return WebFluxResultUtils.result(exchange, error);
+ }
+ return Mono.fromFuture(future.thenApply(ret -> {
+ Object result = ret;
+ if (Objects.isNull(result)) {
+ result = Constants.TARS_RPC_RESULT_EMPTY;
+ }
+ exchange.getAttributes().put(Constants.RPC_RESULT, result);
+
exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE,
ResultEnum.SUCCESS.getName());
+ return result;
+ })).onErrorMap(m -> new ShenyuException("failed to invoke
tars")).then(chain.execute(exchange));
+ }
+
+ @Override
+ public int getOrder() {
+ return PluginEnum.TARS.getCode();
+ }
+
+ @Override
+ public String named() {
+ return PluginEnum.TARS.getName();
+ }
+
+ @Override
+ public boolean skip(final ServerWebExchange exchange) {
+ return skipExcept(exchange, RpcTypeEnum.TARS);
+ }
+
+ @Override
+ protected Mono<Void> handleSelectorIfNull(final String pluginName, final
ServerWebExchange exchange, final ShenyuPluginChain chain) {
+ return WebFluxResultUtils.noSelectorResult(pluginName, exchange);
+ }
+
+ @Override
+ protected Mono<Void> handleRuleIfNull(final String pluginName, final
ServerWebExchange exchange, final ShenyuPluginChain chain) {
+ return WebFluxResultUtils.noRuleResult(pluginName, exchange);
+ }
+
+ private boolean checkMetaData(final MetaData metaData) {
+ return Objects.nonNull(metaData) &&
!StringUtils.isBlank(metaData.getMethodName()) &&
!StringUtils.isBlank(metaData.getServiceName());
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/cache/ApplicationConfigCache.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/cache/ApplicationConfigCache.java
new file mode 100644
index 0000000..1422da5
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/cache/ApplicationConfigCache.java
@@ -0,0 +1,450 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.tars.cache;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.qq.tars.client.Communicator;
+import com.qq.tars.client.CommunicatorConfig;
+import com.qq.tars.client.CommunicatorFactory;
+import com.qq.tars.protocol.annotation.Servant;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.shenyu.common.concurrent.ShenyuThreadFactory;
+import org.apache.shenyu.common.concurrent.ShenyuThreadPoolExecutor;
+import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.dto.convert.plugin.TarsRegisterConfig;
+import org.apache.shenyu.common.dto.convert.selector.TarsUpstream;
+import org.apache.shenyu.common.exception.ShenyuException;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
+import org.apache.shenyu.plugin.tars.exception.ShenyuTarsPluginException;
+import org.apache.shenyu.plugin.tars.proxy.TarsInvokePrx;
+import org.apache.shenyu.plugin.tars.proxy.TarsInvokePrxList;
+import org.apache.shenyu.plugin.tars.util.PrxInfoUtil;
+import org.apache.shenyu.plugin.tars.util.ReturnValueResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.lang.NonNull;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+/**
+ * Tars config cache.
+ */
+public final class ApplicationConfigCache {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ApplicationConfigCache.class);
+
+ private static final ReentrantLock LOCK = new ReentrantLock();
+
+ private final LoadingCache<String, TarsInvokePrxList> cache =
CacheBuilder.newBuilder()
+ .maximumSize(Constants.CACHE_MAX_COUNT)
+ .build(new CacheLoader<>() {
+ @NonNull
+ @Override
+ public TarsInvokePrxList load(@NonNull final String key) {
+ return new TarsInvokePrxList(null, null, null);
+ }
+ });
+
+ private final ConcurrentHashMap<String, List<MetaData>> ctxPathCache = new
ConcurrentHashMap<>();
+
+ private final ConcurrentHashMap<String, Class<?>> prxClassCache = new
ConcurrentHashMap<>();
+
+ private final ConcurrentHashMap<String, TarsParamInfo> prxParamCache = new
ConcurrentHashMap<>();
+
+ private final ConcurrentHashMap<String, List<TarsUpstream>>
refreshUpstreamCache = new ConcurrentHashMap<>();
+
+ private Communicator communicator;
+
+ private final ThreadFactory factory =
ShenyuThreadFactory.create("shenyu-tars", true);
+
+ private ThreadPoolExecutor threadPool;
+
+ private ApplicationConfigCache() {
+ communicator =
CommunicatorFactory.getInstance().getCommunicator(CommunicatorConfig.getDefault());
+ }
+
+ /**
+ * Init.
+ *
+ * @param tarsRegisterConfig the tars register config
+ */
+ public void init(final TarsRegisterConfig tarsRegisterConfig) {
+ if (StringUtils.isEmpty(tarsRegisterConfig.getThreadpool())) {
+ CommunicatorConfig communicatorConfig =
CommunicatorConfig.getDefault();
+
Optional.ofNullable(tarsRegisterConfig.getCorethreads()).ifPresent(communicatorConfig::setCorePoolSize);
+
Optional.ofNullable(tarsRegisterConfig.getThreads()).ifPresent(communicatorConfig::setMaxPoolSize);
+
Optional.ofNullable(tarsRegisterConfig.getQueues()).ifPresent(communicatorConfig::setQueueSize);
+ communicator =
CommunicatorFactory.getInstance().getCommunicator(communicatorConfig);
+ } else {
+ initThreadPool(tarsRegisterConfig);
+
Optional.ofNullable(threadPool).ifPresent(this::setCommunicatorThreadPool);
+ }
+ }
+
+ /**
+ * init thread pool.
+ */
+ private void initThreadPool(final TarsRegisterConfig config) {
+ if (Objects.nonNull(threadPool)) {
+ return;
+ }
+ switch (config.getThreadpool()) {
+ case Constants.SHARED:
+ try {
+ threadPool =
SpringBeanUtils.getInstance().getBean(ShenyuThreadPoolExecutor.class);
+ return;
+ } catch (NoSuchBeanDefinitionException t) {
+ throw new ShenyuException("shared thread pool is not
enable, config ${shenyu.sharedPool.enable} in your xml/yml !", t);
+ }
+ case Constants.FIXED:
+ case Constants.EAGER:
+ case Constants.LIMITED:
+ throw new UnsupportedOperationException();
+ case Constants.CACHED:
+ int corePoolSize =
Optional.ofNullable(config.getCorethreads()).orElse(0);
+ int maximumPoolSize =
Optional.ofNullable(config.getThreads()).orElse(Integer.MAX_VALUE);
+ int queueSize =
Optional.ofNullable(config.getQueues()).orElse(0);
+ threadPool = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize, 60L, TimeUnit.SECONDS,
+ queueSize > 0 ? new LinkedBlockingQueue<>(queueSize) :
new SynchronousQueue<>(), factory);
+ return;
+ default:
+ }
+ }
+
+ /**
+ * Set communicator thread pool.
+ */
+ private void setCommunicatorThreadPool(final ThreadPoolExecutor
threadPool) {
+ Field field = ReflectionUtils.findField(Communicator.class,
"threadPoolExecutor");
+ ReflectionUtils.makeAccessible(field);
+ ReflectionUtils.setField(field, communicator, threadPool);
+ }
+
+ /**
+ * Get reference config.
+ *
+ * @param path path
+ * @return the reference config
+ */
+ public TarsInvokePrxList get(final String path) {
+ try {
+ return cache.get(path);
+ } catch (ExecutionException e) {
+ throw new ShenyuTarsPluginException(e.getCause());
+ }
+ }
+
+ /**
+ * Init prx.<br>
+ * Try to load the meta information defined by meta data to the local
cache.<br>
+ * eg: class definition, all method definition params,context path.<br>
+ *
+ * @param metaData metaData
+ */
+ public void initPrx(final MetaData metaData) {
+ while (true) {
+ Class<?> prxClass = prxClassCache.get(metaData.getPath());
+ try {
+ if (Objects.isNull(prxClass)) {
+ // Spin's Attempt to Load
+ tryLockedLoadMetaData(metaData);
+ } else {
+ if (Objects.nonNull(metaData.getContextPath()) &&
Objects.nonNull(refreshUpstreamCache.get(metaData.getContextPath()))) {
+ refreshTarsInvokePrxList(metaData,
refreshUpstreamCache.get(metaData.getContextPath()));
+ }
+ break;
+ }
+ } catch (Exception e) {
+ LOG.error("ShenyuTarsPluginInitializeException: init tars ref
ex:{}", e.getMessage());
+ break;
+ }
+ }
+ }
+
+ /**
+ * Try to load once, if it fails, it will give up.<br>
+ * add class cache to {@link #prxClassCache}.<br>
+ * add method params cache to {@link #prxParamCache}.<br>
+ * add paths cache to {@link #ctxPathCache}.<br>
+ *
+ * @param metaData metaData
+ * @throws ClassNotFoundException meta data class definition not found
+ * @see ReentrantLock
+ */
+ private void tryLockedLoadMetaData(final MetaData metaData) throws
ClassNotFoundException {
+ Objects.requireNonNull(LOCK);
+ if (LOCK.tryLock()) {
+ try {
+ if (StringUtils.isEmpty(metaData.getRpcExt())) {
+ throw new
ShenyuTarsPluginException("ShenyuTarsPluginInitializeException: can't init prx
with empty ext string");
+ }
+ Class<?> prxClazz = buildClassDefinition(metaData);
+ prxClassCache.put(metaData.getPath(), prxClazz);
+ List<MetaData> paths =
ctxPathCache.getOrDefault(metaData.getContextPath(), new ArrayList<>());
+ if (!IterableUtils.matchesAny(paths, p ->
p.getPath().equals(metaData.getPath()))) {
+ paths.add(metaData);
+ }
+ ctxPathCache.put(metaData.getContextPath(), paths);
+ } finally {
+ LOCK.unlock();
+ }
+ }
+ }
+
+ /**
+ * build target class definition.
+ *
+ * @param metaData metadata
+ * @return class definition
+ * @throws ClassNotFoundException meta data class definition not found
+ */
+ private Class<?> buildClassDefinition(final MetaData metaData) throws
ClassNotFoundException {
+ String clazzName = PrxInfoUtil.getPrxName(metaData);
+ DynamicType.Builder<?> classDefinition = new
ByteBuddy().makeInterface().name(clazzName);
+ TarsParamExtInfo tarsParamExtInfo =
GsonUtils.getInstance().fromJson(metaData.getRpcExt(), TarsParamExtInfo.class);
+ for (MethodInfo methodInfo : tarsParamExtInfo.getMethodInfo()) {
+ DynamicType.Builder.MethodDefinition.ParameterDefinition<?>
definition =
+
classDefinition.defineMethod(PrxInfoUtil.getMethodName(methodInfo.methodName),
+
ReturnValueResolver.getCallBackType(PrxInfoUtil.getParamClass(methodInfo.getReturnType())),
+ Visibility.PUBLIC);
+ if (CollectionUtils.isNotEmpty(methodInfo.getParams())) {
+ Class<?>[] paramTypes = new
Class[methodInfo.getParams().size()];
+ String[] paramNames = new
String[methodInfo.getParams().size()];
+ for (int i = 0; i < methodInfo.getParams().size(); i++) {
+ Pair<String, String> pair = methodInfo.getParams().get(i);
+ paramTypes[i] = PrxInfoUtil.getParamClass(pair.getKey());
+ paramNames[i] = pair.getValue();
+ definition = definition.withParameter(paramTypes[i],
paramNames[i]);
+ prxParamCache.put(getClassMethodKey(clazzName,
methodInfo.getMethodName()), new TarsParamInfo(paramTypes, paramNames));
+ }
+ classDefinition = definition.withoutCode();
+ }
+ }
+ return
classDefinition.annotateType(AnnotationDescription.Builder.ofType(Servant.class).build())
+ .make()
+ .load(Servant.class.getClassLoader(),
ClassLoadingStrategy.Default.INJECTION)
+ .getLoaded();
+
+ }
+
+ /**
+ * Get param info key.
+ *
+ * @param className className
+ * @param methodName methodName
+ * @return the key
+ */
+ public static String getClassMethodKey(final String className, final
String methodName) {
+ return String.join("_", className, methodName);
+ }
+
+ /**
+ * Gets instance.
+ *
+ * @return the instance
+ */
+ public static ApplicationConfigCache getInstance() {
+ return ApplicationConfigCacheInstance.INSTANCE;
+ }
+
+ /**
+ * initPrxClass.
+ *
+ * @param selectorData selectorData
+ */
+ public void initPrxClass(final SelectorData selectorData) {
+ try {
+ final List<TarsUpstream> upstreamList =
GsonUtils.getInstance().fromList(selectorData.getHandle(), TarsUpstream.class);
+ if (CollectionUtils.isEmpty(upstreamList)) {
+ invalidate(selectorData.getName());
+ return;
+ }
+ refreshUpstreamCache.put(selectorData.getName(), upstreamList);
+ List<MetaData> metaDataList =
ctxPathCache.getOrDefault(selectorData.getName(), new ArrayList<>());
+ for (MetaData metaData : metaDataList) {
+ refreshTarsInvokePrxList(metaData, upstreamList);
+ }
+ } catch (ExecutionException | NoSuchMethodException e) {
+ throw new ShenyuException(e.getCause());
+ }
+ }
+
+ /**
+ * refresh metaData path upstream url.
+ *
+ * @param metaData metaData
+ * @param upstreamList upstream list
+ */
+ private void refreshTarsInvokePrxList(final MetaData metaData, final
List<TarsUpstream> upstreamList) throws NoSuchMethodException,
ExecutionException {
+ Class<?> prxClass = prxClassCache.get(metaData.getPath());
+ if (Objects.isNull(prxClass)) {
+ return;
+ }
+ TarsInvokePrxList tarsInvokePrxList = cache.get(metaData.getPath());
+ tarsInvokePrxList.getTarsInvokePrxList().clear();
+ if (Objects.isNull(tarsInvokePrxList.getMethod())) {
+ TarsParamInfo tarsParamInfo =
prxParamCache.get(getClassMethodKey(prxClass.getName(),
metaData.getMethodName()));
+ Object prx = communicator.stringToProxy(prxClass,
PrxInfoUtil.getObjectName(upstreamList.get(0).getUpstreamUrl(),
metaData.getServiceName()));
+ Method method = prx.getClass().getDeclaredMethod(
+ PrxInfoUtil.getMethodName(metaData.getMethodName()),
tarsParamInfo.getParamTypes());
+ tarsInvokePrxList.setMethod(method);
+ tarsInvokePrxList.setParamTypes(tarsParamInfo.getParamTypes());
+ tarsInvokePrxList.setParamNames(tarsParamInfo.getParamNames());
+ }
+
tarsInvokePrxList.getTarsInvokePrxList().addAll(upstreamList.stream().map(upstream
-> {
+ Object strProxy = communicator.stringToProxy(prxClass,
PrxInfoUtil.getObjectName(upstream.getUpstreamUrl(),
metaData.getServiceName()));
+ return new TarsInvokePrx(strProxy, upstream.getUpstreamUrl());
+ }).collect(Collectors.toList()));
+ }
+
+ /**
+ * invalidate.
+ *
+ * @param contextPath context path
+ */
+ public void invalidate(final String contextPath) {
+ List<MetaData> metaDataList = ctxPathCache.getOrDefault(contextPath,
new ArrayList<>());
+ metaDataList.forEach(metaData -> cache.invalidate(metaData.getPath()));
+ }
+
+ /**
+ * The type Application config cache instance.
+ */
+ static final class ApplicationConfigCacheInstance {
+ /**
+ * The Instance.
+ */
+ static final ApplicationConfigCache INSTANCE = new
ApplicationConfigCache();
+
+ private ApplicationConfigCacheInstance() {
+
+ }
+ }
+
+ /**
+ * The type Tars param ext info.
+ */
+ static class MethodInfo {
+
+ private String methodName;
+
+ private List<Pair<String, String>> params;
+
+ private String returnType;
+
+ public String getMethodName() {
+ return methodName;
+ }
+
+ public void setMethodName(final String methodName) {
+ this.methodName = methodName;
+ }
+
+ public List<Pair<String, String>> getParams() {
+ return params;
+ }
+
+ public void setParams(final List<Pair<String, String>> params) {
+ this.params = params;
+ }
+
+ public String getReturnType() {
+ return returnType;
+ }
+
+ public void setReturnType(final String returnType) {
+ this.returnType = returnType;
+ }
+ }
+
+ /**
+ * The type Tars param ext info.
+ */
+ static class TarsParamExtInfo {
+
+ private List<MethodInfo> methodInfo;
+
+ public List<MethodInfo> getMethodInfo() {
+ return methodInfo;
+ }
+
+ public void setMethodInfo(final List<MethodInfo> methodInfo) {
+ this.methodInfo = methodInfo;
+ }
+ }
+
+ /**
+ * The type Tars param ext info.
+ */
+ static class TarsParamInfo {
+
+ private Class<?>[] paramTypes;
+
+ private String[] paramNames;
+
+ TarsParamInfo(final Class<?>[] paramTypes, final String[] paramNames) {
+ this.paramTypes = paramTypes;
+ this.paramNames = paramNames;
+ }
+
+ public Class<?>[] getParamTypes() {
+ return paramTypes;
+ }
+
+ public void setParamTypes(final Class<?>[] paramTypes) {
+ this.paramTypes = paramTypes;
+ }
+
+ public String[] getParamNames() {
+ return paramNames;
+ }
+
+ public void setParamNames(final String[] paramNames) {
+ this.paramNames = paramNames;
+ }
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/context/TarsShenyuContextDecorator.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/context/TarsShenyuContextDecorator.java
new file mode 100644
index 0000000..28efb4d
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/context/TarsShenyuContextDecorator.java
@@ -0,0 +1,43 @@
+/*
+ * 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 org.apache.shenyu.plugin.tars.context;
+
+import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.plugin.api.context.ShenyuContext;
+import org.apache.shenyu.plugin.api.context.ShenyuContextDecorator;
+
+/**
+ * The type Tars shenyu context decorator.
+ */
+public class TarsShenyuContextDecorator implements ShenyuContextDecorator {
+
+ @Override
+ public ShenyuContext decorator(final ShenyuContext shenyuContext, final
MetaData metaData) {
+ shenyuContext.setModule(metaData.getAppName());
+ shenyuContext.setMethod(metaData.getServiceName());
+ shenyuContext.setContextPath(metaData.getContextPath());
+ shenyuContext.setRpcType(RpcTypeEnum.TARS.getName());
+ return shenyuContext;
+ }
+
+ @Override
+ public String rpcType() {
+ return RpcTypeEnum.TARS.getName();
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/exception/ShenyuTarsPluginException.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/exception/ShenyuTarsPluginException.java
new file mode 100644
index 0000000..4f3bb82
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/exception/ShenyuTarsPluginException.java
@@ -0,0 +1,38 @@
+/*
+ * 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 org.apache.shenyu.plugin.tars.exception;
+
+import org.apache.shenyu.common.exception.ShenyuException;
+
+/**
+ * ShenyuTarsPluginException.
+ */
+public class ShenyuTarsPluginException extends ShenyuException {
+
+ public ShenyuTarsPluginException(final Throwable e) {
+ super(e);
+ }
+
+ public ShenyuTarsPluginException(final String message) {
+ super(message);
+ }
+
+ public ShenyuTarsPluginException(final String message, final Throwable
throwable) {
+ super(message, throwable);
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/handler/TarsMetaDataHandler.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/handler/TarsMetaDataHandler.java
new file mode 100644
index 0000000..418e0f2
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/handler/TarsMetaDataHandler.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 org.apache.shenyu.plugin.tars.handler;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.plugin.base.handler.MetaDataHandler;
+import org.apache.shenyu.plugin.tars.cache.ApplicationConfigCache;
+import org.apache.shenyu.plugin.tars.proxy.TarsInvokePrx;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+
+/**
+ * The tars metadata handler.
+ */
+public class TarsMetaDataHandler implements MetaDataHandler {
+
+ private static final ConcurrentMap<String, MetaData> META_DATA =
Maps.newConcurrentMap();
+
+ @Override
+ public void handle(final MetaData metaData) {
+ metaData.updateContextPath();
+ MetaData metaExist = META_DATA.get(metaData.getPath());
+ List<TarsInvokePrx> prxList = ApplicationConfigCache.getInstance()
+ .get(metaData.getPath()).getTarsInvokePrxList();
+ boolean exist = prxList.stream().anyMatch(tarsInvokePrx ->
tarsInvokePrx.getHost().equals(metaData.getAppName()));
+ if (!exist) {
+ ApplicationConfigCache.getInstance().initPrx(metaData);
+ }
+ if (Objects.isNull(metaExist)) {
+ META_DATA.put(metaData.getPath(), metaData);
+ }
+ }
+
+ @Override
+ public void remove(final MetaData metaData) {
+ metaData.updateContextPath();
+ List<TarsInvokePrx> prxList = ApplicationConfigCache.getInstance()
+ .get(metaData.getPath()).getTarsInvokePrxList();
+ List<TarsInvokePrx> removePrxList = prxList.stream()
+ .filter(tarsInvokePrx ->
tarsInvokePrx.getHost().equals(metaData.getAppName()))
+ .collect(Collectors.toList());
+ prxList.removeAll(removePrxList);
+ if (CollectionUtils.isEmpty(prxList)) {
+ META_DATA.remove(metaData.getPath());
+ }
+ }
+
+ @Override
+ public String rpcType() {
+ return RpcTypeEnum.TARS.getName();
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/handler/TarsPluginDataHandler.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/handler/TarsPluginDataHandler.java
new file mode 100644
index 0000000..606e717
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/handler/TarsPluginDataHandler.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 org.apache.shenyu.plugin.tars.handler;
+
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.dto.convert.plugin.TarsRegisterConfig;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.common.utils.Singleton;
+import org.apache.shenyu.plugin.base.handler.PluginDataHandler;
+import org.apache.shenyu.plugin.tars.cache.ApplicationConfigCache;
+
+import java.util.Objects;
+
+/**
+ * The type tars plugin data handler.
+ */
+public class TarsPluginDataHandler implements PluginDataHandler {
+
+ @Override
+ public void handlerPlugin(final PluginData pluginData) {
+ if (Objects.nonNull(pluginData) &&
Boolean.TRUE.equals(pluginData.getEnabled())) {
+ TarsRegisterConfig tarsRegisterConfig =
GsonUtils.getInstance().fromJson(pluginData.getConfig(),
TarsRegisterConfig.class);
+ TarsRegisterConfig exist =
Singleton.INST.get(TarsRegisterConfig.class);
+ if (Objects.isNull(tarsRegisterConfig)) {
+ return;
+ }
+ if (Objects.isNull(exist) || !tarsRegisterConfig.equals(exist)) {
+ // If it is null, cache it
+ ApplicationConfigCache.getInstance().init(tarsRegisterConfig);
+ }
+ Singleton.INST.single(TarsRegisterConfig.class,
tarsRegisterConfig);
+ }
+ }
+
+ @Override
+ public String pluginNamed() {
+ return PluginEnum.TARS.getName();
+ }
+
+ @Override
+ public void handlerSelector(final SelectorData selectorData) {
+ if (Objects.isNull(selectorData.getName())) {
+ return;
+ }
+ ApplicationConfigCache.getInstance().initPrxClass(selectorData);
+ }
+
+ @Override
+ public void removeSelector(final SelectorData selectorData) {
+ if (Objects.isNull(selectorData.getName())) {
+ return;
+ }
+
ApplicationConfigCache.getInstance().invalidate(selectorData.getName());
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/proxy/TarsInvokePrx.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/proxy/TarsInvokePrx.java
new file mode 100644
index 0000000..9a7a321
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/proxy/TarsInvokePrx.java
@@ -0,0 +1,81 @@
+/*
+ * 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 org.apache.shenyu.plugin.tars.proxy;
+
+/**
+ * Tars path invoke metadata.
+ */
+public class TarsInvokePrx {
+
+ private Object invokePrx;
+
+ private String host;
+
+ /**
+ * Instantiates a new Tars invoke prx.
+ */
+ public TarsInvokePrx() {
+ }
+
+ /**
+ * Instantiates a new Tars invoke prx.
+ *
+ * @param invokePrx the invoke prx
+ * @param host the host
+ */
+ public TarsInvokePrx(final Object invokePrx, final String host) {
+ this.invokePrx = invokePrx;
+ this.host = host;
+ }
+
+ /**
+ * Gets invoke prx.
+ *
+ * @return the invoke prx
+ */
+ public Object getInvokePrx() {
+ return invokePrx;
+ }
+
+ /**
+ * Sets invoke prx.
+ *
+ * @param invokePrx the invoke prx
+ */
+ public void setInvokePrx(final Object invokePrx) {
+ this.invokePrx = invokePrx;
+ }
+
+ /**
+ * Gets host.
+ *
+ * @return the host
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * Sets host.
+ *
+ * @param host the host
+ */
+ public void setHost(final String host) {
+ this.host = host;
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/proxy/TarsInvokePrxList.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/proxy/TarsInvokePrxList.java
new file mode 100644
index 0000000..0699cd5
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/proxy/TarsInvokePrxList.java
@@ -0,0 +1,131 @@
+/*
+ * 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 org.apache.shenyu.plugin.tars.proxy;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Tars path invoke metadata.
+ */
+public class TarsInvokePrxList {
+
+ private final List<TarsInvokePrx> tarsInvokePrxList;
+
+ private Method method;
+
+ private Class<?>[] paramTypes;
+
+ private String[] paramNames;
+
+ /**
+ * Instantiates a new Tars invoke prx list.
+ */
+ public TarsInvokePrxList() {
+ tarsInvokePrxList = new CopyOnWriteArrayList<>();
+ }
+
+ /**
+ * Instantiates a new Tars invoke prx list.
+ *
+ * @param method the method
+ * @param paramTypes the param types
+ * @param paramNames the param names
+ */
+ public TarsInvokePrxList(final Method method,
+ final Class<?>[] paramTypes,
+ final String[] paramNames) {
+ this.tarsInvokePrxList = new CopyOnWriteArrayList<>();
+ this.method = method;
+ this.paramTypes = paramTypes;
+ this.paramNames = paramNames;
+ }
+
+ /**
+ * Gets tars invoke prx list.
+ *
+ * @return the tars invoke prx list
+ */
+ public List<TarsInvokePrx> getTarsInvokePrxList() {
+ return tarsInvokePrxList;
+ }
+
+ /**
+ * Sets tars invoke prx list.
+ *
+ * @param tarsInvokePrxList the tars invoke prx list
+ */
+ public void addTarsInvokePrxList(final List<TarsInvokePrx>
tarsInvokePrxList) {
+ this.tarsInvokePrxList.addAll(tarsInvokePrxList);
+ }
+
+ /**
+ * Gets method.
+ *
+ * @return the method
+ */
+ public Method getMethod() {
+ return method;
+ }
+
+ /**
+ * Sets method.
+ *
+ * @param method the method
+ */
+ public void setMethod(final Method method) {
+ this.method = method;
+ }
+
+ /**
+ * Get param types class [ ].
+ *
+ * @return the class [ ]
+ */
+ public Class<?>[] getParamTypes() {
+ return paramTypes;
+ }
+
+ /**
+ * Sets param types.
+ *
+ * @param paramTypes the param types
+ */
+ public void setParamTypes(final Class<?>[] paramTypes) {
+ this.paramTypes = paramTypes;
+ }
+
+ /**
+ * Get param names string [ ].
+ *
+ * @return the string [ ]
+ */
+ public String[] getParamNames() {
+ return paramNames;
+ }
+
+ /**
+ * Sets param names.
+ *
+ * @param paramNames the param names
+ */
+ public void setParamNames(final String[] paramNames) {
+ this.paramNames = paramNames;
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/util/PrxInfoUtil.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/util/PrxInfoUtil.java
new file mode 100644
index 0000000..1441959
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/util/PrxInfoUtil.java
@@ -0,0 +1,180 @@
+/*
+ * 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 org.apache.shenyu.plugin.tars.util;
+
+import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.common.utils.ParamCheckUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Proxy info util.
+ */
+public final class PrxInfoUtil {
+
+ private static final Map<String, PrimitiveType> PRIMITIVE_TYPE;
+
+ static {
+ PRIMITIVE_TYPE = new HashMap<>();
+ PRIMITIVE_TYPE.put("int", new PrimitiveType(int.class, o -> {
+ if (o instanceof String) {
+ return Integer.valueOf((String) o);
+ }
+ return ((Long) o).intValue();
+ }));
+ PRIMITIVE_TYPE.put("double", new PrimitiveType(double.class, o -> {
+ if (o instanceof String) {
+ return Double.valueOf((String) o);
+ }
+ return o;
+ }));
+ PRIMITIVE_TYPE.put("long", new PrimitiveType(long.class, o -> {
+ if (o instanceof String) {
+ return Long.valueOf((String) o);
+ }
+ return o;
+ }));
+ PRIMITIVE_TYPE.put("short", new PrimitiveType(short.class, o -> {
+ if (o instanceof String) {
+ return Short.valueOf((String) o);
+ }
+ return ((Long) o).shortValue();
+ }));
+ PRIMITIVE_TYPE.put("byte", new PrimitiveType(byte.class, o -> {
+ if (o instanceof String) {
+ return Byte.valueOf((String) o);
+ }
+ return ((Long) o).byteValue();
+ }));
+ PRIMITIVE_TYPE.put("boolean", new PrimitiveType(boolean.class, o -> {
+ if (o instanceof String) {
+ return Byte.valueOf((String) o);
+ }
+ return o;
+ }));
+ PRIMITIVE_TYPE.put("char", new PrimitiveType(char.class, o -> {
+ if (o instanceof String) {
+ return String.valueOf(o).charAt(0);
+ }
+ return o;
+ }));
+ PRIMITIVE_TYPE.put("float", new PrimitiveType(float.class, o -> {
+ if (o instanceof String) {
+ return Float.valueOf((String) o);
+ }
+ return ((Double) o).floatValue();
+ }));
+ }
+
+ private PrxInfoUtil() {
+ }
+
+ /**
+ * Get class type by name.
+ *
+ * @param className className
+ * @return the type to invoke
+ * @throws ClassNotFoundException ClassNotFoundException
+ */
+ public static Class<?> getParamClass(final String className) throws
ClassNotFoundException {
+ if (PRIMITIVE_TYPE.containsKey(className)) {
+ return PRIMITIVE_TYPE.get(className).getClazz();
+ } else {
+ return Class.forName(className);
+ }
+ }
+
+ /**
+ * Get proxy class name to get tars proxy.
+ *
+ * @param metaData metaData
+ * @return className
+ */
+ public static String getPrxName(final MetaData metaData) {
+ return metaData.getPath().replace("/", "") + metaData.getMethodName()
+ "Prx";
+ }
+
+ /**
+ * Get methodName to get tars proxy.
+ *
+ * @param methodName methodName
+ * @return methodName
+ */
+ public static String getMethodName(final String methodName) {
+ return "promise_" + methodName;
+ }
+
+ /**
+ * Get objectName to get tars proxy.
+ *
+ * @param upstreamUrl upstream url
+ * @param serviceName service name
+ * @return objectName
+ */
+ public static String getObjectName(final String upstreamUrl, final String
serviceName) {
+ String[] ipAndPort = upstreamUrl.split(":");
+ return serviceName + "@tcp -h " + ipAndPort[0] + " -p " + ipAndPort[1];
+ }
+
+ /**
+ * Get param to invoke tars server.
+ *
+ * @param paramTypes paramTypes
+ * @param paramNames paramNames
+ * @param body body
+ * @return the param to invoke
+ */
+ public static Object[] getParamArray(final Class<?>[] paramTypes, final
String[] paramNames, final String body) {
+ Map<String, Object> bodyMap =
GsonUtils.getInstance().convertToMap(body);
+ ParamCheckUtils.checkParamsLength(bodyMap.size(), paramNames.length);
+ Object[] param = new Object[paramNames.length];
+ for (int i = 0; i < paramNames.length; i++) {
+ String paramName = paramNames[i];
+ Class<?> paramType = paramTypes[i];
+ if (PRIMITIVE_TYPE.containsKey(paramType.getName())) {
+ param[i] =
PRIMITIVE_TYPE.get(paramType.getName()).getFunc().apply(bodyMap.get(paramName));
+ } else {
+ param[i] = bodyMap.get(paramName);
+ }
+ }
+ return param;
+ }
+
+ static class PrimitiveType {
+
+ private final Class<?> clazz;
+
+ private final Function<Object, Object> func;
+
+ PrimitiveType(final Class<?> clazz, final Function<Object, Object>
func) {
+ this.clazz = clazz;
+ this.func = func;
+ }
+
+ public Class<?> getClazz() {
+ return clazz;
+ }
+
+ public Function<Object, Object> getFunc() {
+ return func;
+ }
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/util/ReturnValueResolver.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/util/ReturnValueResolver.java
new file mode 100644
index 0000000..983dafe
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/main/java/org/apache/shenyu/plugin/tars/util/ReturnValueResolver.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 org.apache.shenyu.plugin.tars.util;
+
+import com.google.common.reflect.TypeParameter;
+import com.google.common.reflect.TypeToken;
+
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * The tars return value type resolver.
+ */
+public final class ReturnValueResolver {
+
+ @SuppressWarnings("rawtypes")
+ private static final Map<Class, Class> WRAPPER_TYPE_MAP;
+
+ static {
+ WRAPPER_TYPE_MAP = new HashMap<>();
+ WRAPPER_TYPE_MAP.put(int.class, Integer.class);
+ WRAPPER_TYPE_MAP.put(byte.class, Byte.class);
+ WRAPPER_TYPE_MAP.put(char.class, Character.class);
+ WRAPPER_TYPE_MAP.put(boolean.class, Boolean.class);
+ WRAPPER_TYPE_MAP.put(double.class, Double.class);
+ WRAPPER_TYPE_MAP.put(float.class, Float.class);
+ WRAPPER_TYPE_MAP.put(long.class, Long.class);
+ WRAPPER_TYPE_MAP.put(short.class, Short.class);
+ }
+
+ private ReturnValueResolver() {
+ }
+
+ /**
+ * Get return type.
+ *
+ * @param <T> T
+ * @param clazz clazz
+ * @return the type
+ */
+ @SuppressWarnings("all")
+ public static <T> Type getCallBackType(final Class<T> clazz) {
+ return new TypeToken<CompletableFuture<T>>() { }
+ .where(new TypeParameter<T>() { },
TypeToken.of(WRAPPER_TYPE_MAP.getOrDefault(clazz, clazz)))
+ .getType();
+ }
+
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/TarsPluginTest.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/TarsPluginTest.java
new file mode 100644
index 0000000..33bcff2
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/TarsPluginTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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 org.apache.shenyu.plugin.tars;
+
+import org.apache.shenyu.common.concurrent.ShenyuThreadFactory;
+import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.api.context.ShenyuContext;
+import org.apache.shenyu.plugin.api.result.DefaultShenyuResult;
+import org.apache.shenyu.plugin.api.result.ShenyuResult;
+import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
+import org.apache.shenyu.plugin.tars.cache.ApplicationConfigCache;
+import org.apache.shenyu.plugin.tars.proxy.TarsInvokePrxList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
+import org.springframework.mock.web.server.MockServerWebExchange;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test case for {@link TarsPlugin}.
+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public class TarsPluginTest {
+
+ @Mock
+ private ShenyuPluginChain chain;
+
+ private MetaData metaData;
+
+ private ServerWebExchange exchange;
+
+ private TarsPlugin tarsPluginUnderTest;
+
+ @BeforeEach
+ public void setUp() {
+ ConfigurableApplicationContext applicationContext =
mock(ConfigurableApplicationContext.class);
+ when(applicationContext.getBean(ShenyuResult.class)).thenReturn(new
DefaultShenyuResult());
+ SpringBeanUtils springBeanUtils = SpringBeanUtils.getInstance();
+ springBeanUtils.setApplicationContext(applicationContext);
+ metaData = new MetaData("id", "127.0.0.1:8080", "contextPath",
+ "path", RpcTypeEnum.TARS.getName(), "serviceName", "method1",
+ "parameterTypes",
"{\"methodInfo\":[{\"methodName\":\"method1\",\"params\":"
+ +
"[{\"left\":\"java.lang.String\",\"right\":\"param1\"},{\"left\":\"java.lang.String\","
+ +
"\"right\":\"param2\"}],\"returnType\":\"java.lang.String\"}]}", false,
Constants.SYS_DEFAULT_NAMESPACE_ID);
+ ApplicationConfigCache.getInstance().initPrx(metaData);
+ exchange =
MockServerWebExchange.from(MockServerHttpRequest.get("localhost").build());
+ tarsPluginUnderTest = new TarsPlugin();
+ }
+
+ @Test
+ public void testTarsPluginWithEmptyBody() {
+ ShenyuContext context = mock(ShenyuContext.class);
+ exchange.getAttributes().put(Constants.CONTEXT, context);
+ exchange.getAttributes().put(Constants.META_DATA, metaData);
+ when(chain.execute(exchange)).thenReturn(Mono.empty());
+ RuleData data = mock(RuleData.class);
+ SelectorData selectorData = mock(SelectorData.class);
+ StepVerifier.create(tarsPluginUnderTest.doExecute(exchange, chain,
selectorData, data)).expectSubscription().verifyComplete();
+ }
+
+ @Test
+ public void testTarsPluginWithEmptyMetaData() {
+ ShenyuContext context = mock(ShenyuContext.class);
+ exchange.getAttributes().put(Constants.CONTEXT, context);
+ metaData.setServiceName("");
+ exchange.getAttributes().put(Constants.META_DATA, metaData);
+ when(chain.execute(exchange)).thenReturn(Mono.empty());
+ RuleData data = mock(RuleData.class);
+ SelectorData selectorData = mock(SelectorData.class);
+ StepVerifier.create(tarsPluginUnderTest.doExecute(exchange, chain,
selectorData, data)).expectSubscription().verifyComplete();
+ }
+
+ @Test
+ public void testTarsPluginWithArgumentTypeMissMatch() {
+ ShenyuContext context = mock(ShenyuContext.class);
+ exchange.getAttributes().put(Constants.CONTEXT, context);
+ exchange.getAttributes().put(Constants.META_DATA, metaData);
+ exchange.getAttributes().put(Constants.PARAM_TRANSFORM,
"{\"param1\":1,\"param2\":2}");
+ when(chain.execute(exchange)).thenReturn(Mono.empty());
+ RuleData data = mock(RuleData.class);
+ SelectorData selectorData = mock(SelectorData.class);
+ assertThrows(IllegalArgumentException.class, () ->
StepVerifier.create(tarsPluginUnderTest.doExecute(exchange, chain,
selectorData, data)).expectSubscription().verifyComplete());
+ }
+
+ @Test
+ public void testTarsPluginNormal() throws InvocationTargetException,
IllegalAccessException {
+ ShenyuContext context = mock(ShenyuContext.class);
+ exchange.getAttributes().put(Constants.CONTEXT, context);
+ exchange.getAttributes().put(Constants.META_DATA, metaData);
+ exchange.getAttributes().put(Constants.PARAM_TRANSFORM,
"{\"param1\":\"1\",\"param2\":\"1\"}");
+ when(chain.execute(exchange)).thenReturn(Mono.empty());
+ RuleData data = mock(RuleData.class);
+ SelectorData selectorData = mock(SelectorData.class);
+ TarsInvokePrxList tarsInvokePrxList =
ApplicationConfigCache.getInstance().get(metaData.getPath());
+ Method method = mock(Method.class);
+ ExecutorService executorService = Executors.newFixedThreadPool(1,
+ ShenyuThreadFactory.create("long-polling", true));
+ CompletableFuture<String> stringCompletableFuture =
CompletableFuture.supplyAsync(() -> "", executorService);
+ when(method.invoke(any(), any())).thenReturn(stringCompletableFuture);
+ tarsInvokePrxList.setMethod(method);
+ assertThrows(IllegalArgumentException.class, () ->
StepVerifier.create(tarsPluginUnderTest.doExecute(exchange, chain,
selectorData, data)).expectSubscription().verifyComplete());
+ }
+
+ @Test
+ public void testGetOrder() {
+ int result = tarsPluginUnderTest.getOrder();
+ assertEquals(PluginEnum.TARS.getCode(), result);
+ }
+
+ @Test
+ public void testNamed() {
+ String result = tarsPluginUnderTest.named();
+ assertEquals(PluginEnum.TARS.getName(), result);
+ }
+
+ @Test
+ public void testSkip() {
+ ShenyuContext context = mock(ShenyuContext.class);
+ when(context.getRpcType()).thenReturn(RpcTypeEnum.TARS.getName());
+ exchange.getAttributes().put(Constants.CONTEXT, context);
+ boolean result = tarsPluginUnderTest.skip(exchange);
+ assertFalse(result);
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/cache/ApplicationConfigCacheTest.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/cache/ApplicationConfigCacheTest.java
new file mode 100644
index 0000000..cbf2286
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/cache/ApplicationConfigCacheTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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 org.apache.shenyu.plugin.tars.cache;
+
+import com.qq.tars.protocol.annotation.Servant;
+import org.apache.shenyu.common.concurrent.ShenyuThreadFactory;
+import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.plugin.tars.proxy.TarsInvokePrxList;
+import org.apache.shenyu.plugin.tars.util.PrxInfoUtil;
+import org.assertj.core.util.Lists;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case for {@link ApplicationConfigCache}.
+ */
+@ExtendWith(MockitoExtension.class)
+public final class ApplicationConfigCacheTest {
+
+ private ApplicationConfigCache applicationConfigCacheUnderTest;
+
+ @BeforeEach
+ public void setUp() {
+ applicationConfigCacheUnderTest = ApplicationConfigCache.getInstance();
+ }
+
+ @Test
+ public void testGet() throws ClassNotFoundException {
+ final String rpcExt =
"{\"methodInfo\":[{\"methodName\":\"method1\",\"params\":"
+ +
"[{\"left\":\"int\",\"right\":\"param1\"},{\"left\":\"java.lang.Integer\","
+ +
"\"right\":\"param2\"}],\"returnType\":\"java.lang.String\"}]}";
+
+ final MetaData metaData = new MetaData("id", "127.0.0.1:8080",
"contextPath",
+ "path5", RpcTypeEnum.TARS.getName(), "serviceName5", "method1",
+ "parameterTypes", rpcExt, false,
Constants.SYS_DEFAULT_NAMESPACE_ID);
+
+ assertThrows(NullPointerException.class, () -> {
+ applicationConfigCacheUnderTest.initPrx(metaData);
+ final TarsInvokePrxList result =
applicationConfigCacheUnderTest.get("path5");
+ assertNotNull(result);
+ assertEquals("promise_method1", result.getMethod().getName());
+ assertEquals(2, result.getParamTypes().length);
+ assertEquals(2, result.getParamNames().length);
+ Class<?> prxClazz =
Class.forName(PrxInfoUtil.getPrxName(metaData));
+
assertTrue(Arrays.stream(prxClazz.getAnnotations()).anyMatch(annotation ->
annotation instanceof Servant));
+
+ });
+ }
+
+ @Test
+ public void testConcurrentInitPrx() {
+ final String rpcExt1 =
"{\"methodInfo\":[{\"methodName\":\"method1\",\"params\":"
+ +
"[{\"left\":\"int\",\"right\":\"param1\"},{\"left\":\"java.lang.Integer\","
+ +
"\"right\":\"param2\"}],\"returnType\":\"java.lang.String\"}]}";
+ final String rpcExt2 =
"{\"methodInfo\":[{\"methodName\":\"method2\",\"params\":"
+ +
"[{\"left\":\"int\",\"right\":\"param1\"},{\"left\":\"java.lang.Integer\","
+ +
"\"right\":\"param2\"}],\"returnType\":\"java.lang.String\"}]}";
+ final String rpcExt3 =
"{\"methodInfo\":[{\"methodName\":\"method3\",\"params\":"
+ +
"[{\"left\":\"int\",\"right\":\"param1\"},{\"left\":\"java.lang.Integer\","
+ +
"\"right\":\"param2\"}],\"returnType\":\"java.lang.String\"}]}";
+ final String rpcExt4 =
"{\"methodInfo\":[{\"methodName\":\"method4\",\"params\":"
+ +
"[{\"left\":\"int\",\"right\":\"param1\"},{\"left\":\"java.lang.Integer\","
+ +
"\"right\":\"param2\"}],\"returnType\":\"java.lang.String\"}]}";
+
+ final MetaData metaData1 = new MetaData("id", "127.0.0.1:8080",
"contextPath",
+ "path1", RpcTypeEnum.TARS.getName(), "serviceName1", "method1",
+ "parameterTypes", rpcExt1, false,
Constants.SYS_DEFAULT_NAMESPACE_ID);
+ final MetaData metaData2 = new MetaData("id", "127.0.0.1:8080",
"contextPath",
+ "path2", RpcTypeEnum.TARS.getName(), "serviceName2", "method2",
+ "parameterTypes", rpcExt2, false,
Constants.SYS_DEFAULT_NAMESPACE_ID);
+ final MetaData metaData3 = new MetaData("id", "127.0.0.1:8080",
"contextPath",
+ "path3", RpcTypeEnum.TARS.getName(), "serviceName3", "method3",
+ "parameterTypes", rpcExt3, false,
Constants.SYS_DEFAULT_NAMESPACE_ID);
+ final MetaData metaData4 = new MetaData("id", "127.0.0.1:8080",
"contextPath",
+ "path4", RpcTypeEnum.TARS.getName(), "serviceName4", "method4",
+ "parameterTypes", rpcExt4, false,
Constants.SYS_DEFAULT_NAMESPACE_ID);
+ List<MetaData> metaDataList = Lists.list(metaData1, metaData2,
metaData3, metaData4);
+ assertThrows(NullPointerException.class, () -> {
+ ExecutorService executorService = Executors.newFixedThreadPool(4,
+
ShenyuThreadFactory.create("ApplicationConfigCache-tars-initPrx", false));
+ CountDownLatch countDownLatch = new CountDownLatch(4);
+ metaDataList.forEach(metaData -> executorService.execute(() -> {
+ applicationConfigCacheUnderTest.initPrx(metaData);
+ countDownLatch.countDown();
+ }));
+ countDownLatch.await();
+ assertEquals("promise_method1",
applicationConfigCacheUnderTest.get("path1").getMethod().getName());
+ assertEquals("promise_method2",
applicationConfigCacheUnderTest.get("path2").getMethod().getName());
+ assertEquals("promise_method3",
applicationConfigCacheUnderTest.get("path3").getMethod().getName());
+ assertEquals("promise_method4",
applicationConfigCacheUnderTest.get("path4").getMethod().getName());
+ });
+ }
+
+ @Test
+ public void testInitPrx() {
+ final MetaData metaData = new MetaData("id", "127.0.0.1:8080",
"contextPath",
+ "path6", RpcTypeEnum.TARS.getName(), "serviceName6", "method1",
+ "parameterTypes",
"{\"methodInfo\":[{\"methodName\":\"method1\",\"params\":[{\"left\":\"int\",\"right\":\"param1\"},"
+ +
"{\"left\":\"java.lang.Integer\",\"right\":\"param2\"}],\"returnType\":\"java.lang.String\"}]}",
false, Constants.SYS_DEFAULT_NAMESPACE_ID);
+ assertThrows(NullPointerException.class, () -> {
+ applicationConfigCacheUnderTest.initPrx(metaData);
+ final TarsInvokePrxList result =
applicationConfigCacheUnderTest.get("path6");
+ assertEquals("promise_method1", result.getMethod().getName());
+ });
+ }
+
+ @Test
+ public void testGetClassMethodKey() {
+ assertEquals("className_methodName",
ApplicationConfigCache.getClassMethodKey("className", "methodName"));
+ }
+
+ @Test
+ public void testGetInstance() {
+ final ApplicationConfigCache result =
ApplicationConfigCache.getInstance();
+ assertNotNull(result);
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/handler/TarsMetaDataHandlerTest.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/handler/TarsMetaDataHandlerTest.java
new file mode 100644
index 0000000..3377e78
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/handler/TarsMetaDataHandlerTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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 org.apache.shenyu.plugin.tars.handler;
+
+import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * Test case for {@link
org.apache.shenyu.plugin.tars.handler.TarsMetaDataHandler}.
+ */
+@ExtendWith(MockitoExtension.class)
+public class TarsMetaDataHandlerTest {
+
+ private TarsMetaDataHandler tarsMetaDataHandler;
+
+ private MetaData metaData;
+
+ @BeforeEach
+ public void setUp() {
+ metaData = new MetaData("id", "127.0.0.1:8080", "contextPath",
+ "path", RpcTypeEnum.TARS.getName(), "serviceName", "method1",
+ "parameterTypes",
"{\"methodInfo\":[{\"methodName\":\"method1\",\"params\":[{\"left\":\"int\",\"right\":\"param1\"},"
+ +
"{\"left\":\"java.lang.Integer\",\"right\":\"param2\"}],\"returnType\":\"java.lang.String\"}]}",
false, Constants.SYS_DEFAULT_NAMESPACE_ID);
+ tarsMetaDataHandler = new TarsMetaDataHandler();
+ }
+
+ @Test
+ public void testOnSubscribe() {
+ tarsMetaDataHandler.handle(metaData);
+ /**
+ * test for cache;
+ */
+ tarsMetaDataHandler.handle(metaData);
+ }
+
+ @Test
+ public void testUnSubscribe() {
+ tarsMetaDataHandler.remove(metaData);
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/handler/TarsPluginDataHandlerTest.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/handler/TarsPluginDataHandlerTest.java
new file mode 100644
index 0000000..a9791f1
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/handler/TarsPluginDataHandlerTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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 org.apache.shenyu.plugin.tars.handler;
+
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.dto.convert.plugin.TarsRegisterConfig;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.utils.Singleton;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case for {@link TarsPluginDataHandler}.
+ */
+@ExtendWith(MockitoExtension.class)
+public final class TarsPluginDataHandlerTest {
+
+ private TarsPluginDataHandler tarsPluginDataHandlerUnderTest;
+
+ @BeforeEach
+ public void setUp() {
+ tarsPluginDataHandlerUnderTest = new TarsPluginDataHandler();
+ }
+
+ @Test
+ public void testHandlerPlugin() {
+ final PluginData pluginData = new PluginData("id", "name",
"{\"threadpool\":\"cached\",\"corethreads\":1,\"threads\":2,\"queues\":3}",
"0", true, null);
+ tarsPluginDataHandlerUnderTest.handlerPlugin(pluginData);
+ assertTrue(pluginData.getName().endsWith("name"));
+ TarsRegisterConfig config =
Singleton.INST.get(TarsRegisterConfig.class);
+ Assertions.assertEquals(config.getThreadpool(), "cached");
+ Assertions.assertEquals(config.getCorethreads(), 1);
+ Assertions.assertEquals(config.getThreads(), 2);
+ Assertions.assertEquals(config.getQueues(), 3);
+ }
+
+ @Test
+ public void testPluginNamed() {
+ final String result = tarsPluginDataHandlerUnderTest.pluginNamed();
+ assertEquals(PluginEnum.TARS.getName(), result);
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/util/PrxInfoUtilTest.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/util/PrxInfoUtilTest.java
new file mode 100644
index 0000000..431756b
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tars/src/test/java/org/apache/shenyu/plugin/tars/util/PrxInfoUtilTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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 org.apache.shenyu.plugin.tars.util;
+
+import org.apache.shenyu.common.constant.Constants;
+import org.apache.shenyu.common.dto.MetaData;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Test case for {@link PrxInfoUtil}.
+ */
+@ExtendWith(MockitoExtension.class)
+public class PrxInfoUtilTest {
+
+ @Test
+ public void testGetParamClass() throws Exception {
+ assertEquals(int.class, PrxInfoUtil.getParamClass("int"));
+ assertEquals(long.class, PrxInfoUtil.getParamClass("long"));
+ assertEquals(short.class, PrxInfoUtil.getParamClass("short"));
+ assertEquals(byte.class, PrxInfoUtil.getParamClass("byte"));
+ assertEquals(boolean.class, PrxInfoUtil.getParamClass("boolean"));
+ assertEquals(char.class, PrxInfoUtil.getParamClass("char"));
+ assertEquals(float.class, PrxInfoUtil.getParamClass("float"));
+ assertEquals(Integer.class,
PrxInfoUtil.getParamClass("java.lang.Integer"));
+ }
+
+ @Test
+ public void testGetParamClassThrowsClassNotFoundException() throws
Exception {
+ assertThrows(ClassNotFoundException.class, () ->
PrxInfoUtil.getParamClass("className"));
+ }
+
+ @Test
+ public void testGetPrxName() {
+ final MetaData metaData = new MetaData("id", "appName", "contextPath",
"/path",
+ "rpcType", "serviceName", "methodName", "parameterTypes",
+ "rpcExt", false, Constants.SYS_DEFAULT_NAMESPACE_ID);
+ final String result = PrxInfoUtil.getPrxName(metaData);
+ assertEquals("pathmethodNamePrx", result);
+ }
+
+ @Test
+ public void testGetMethodName() {
+ assertEquals("promise_methodName",
PrxInfoUtil.getMethodName("methodName"));
+ }
+
+ @Test
+ public void testGetObjectName() {
+ final String result = PrxInfoUtil.getObjectName("127.0.0.1:8080",
"serviceName");
+ assertEquals("serviceName@tcp -h 127.0.0.1 -p 8080", result);
+ }
+
+ @Test
+ public void testGetParamArray() {
+ assertArrayEquals(new Object[]{11, Double.valueOf("1.321321312"),
Long.valueOf("131231312"), Short.valueOf("11"), Byte.valueOf("0"), false, 'a',
1.321321312F},
+ PrxInfoUtil.getParamArray(new Class<?>[]{int.class,
double.class, long.class, short.class, byte.class, boolean.class, char.class,
float.class},
+ new String[]{"int", "double", "long", "short", "byte",
"boolean", "char", "float"},
+
"{\"int\":11,\"double\":1.321321312,\"long\":131231312,\"short\":11,\"byte\":0,\"boolean\":false,\"char\":'a',\"float\":1.321321312}"));
+ }
+}