Plugins are based on extensions, consisting of multiple extensions to enhance a specific framework, implementing business logic such as multi-active traffic governance.
A plugin is packaged into a directory, as shown below:
.
└── plugin
├── dubbo
│ ├── joylive-registry-dubbo2.6-1.0.0.jar
│ ├── joylive-registry-dubbo2.7-1.0.0.jar
│ ├── joylive-registry-dubbo3-1.0.0.jar
│ ├── joylive-router-dubbo2.6-1.0.0.jar
│ ├── joylive-router-dubbo2.7-1.0.0.jar
│ ├── joylive-router-dubbo3-1.0.0.jar
│ ├── joylive-transmission-dubbo2.6-1.0.0.jar
│ ├── joylive-transmission-dubbo2.7-1.0.0.jar
│ └── joylive-transmission-dubbo3-1.0.0.jar
This Dubbo plugin supports 3 versions, enhancing the capabilities of the registry, routing, and link transmission.
This interface describes the matching type and provides method interceptor definitions.
@Extensible("PluginDefinition")
public interface PluginDefinition {
ElementMatcher<TypeDesc> getMatcher();
InterceptorDefinition[] getInterceptors();
}This interface describes the matching methods and interceptors.
public interface InterceptorDefinition {
ElementMatcher<MethodDesc> getMatcher();
Interceptor getInterceptor();
}This interface describes method interception points.
public interface Interceptor {
void onEnter(ExecutableContext ctx);
void onSuccess(ExecutableContext ctx);
void onError(ExecutableContext ctx);
void onExit(ExecutableContext ctx);
}ExecutableContext has different implementations for intercepting member methods and constructors, namely MethodContext and ConstructorContext.
classDiagram
direction BT
class Attributes {
+ getAttribute(String) T
+ setAttribute(String, Object) void
}
class ConstructorContext {
+ getConstructor() Constructor~?~
}
class ExecutableContext {
+ getId() long
+ getType() Class~?~
+ getArguments() Object[]
+ getArgumentCount() int
+ getArgument(int) T
+ getDescription() String
+ getTarget() Object
+ getThrowable() Throwable
+ setArgument(int, Object) void
+ setThrowable(Throwable) void
+ isSuccess() boolean
+ isSkip() boolean
+ tryLock(LockContext) boolean
+ unlock() boolean
+ isLocked() boolean
}
class MethodContext {
+ getMethod() Method
+ getResult() Object
+ setResult(Object) void
+ skip() void
+ skipWithResult(Object) void
+ skipWithThrowable(Throwable) void
+ setSkip(boolean) void
+ success(Object) void
+ toString() String
+ invokeOrigin() Object
+ invokeOrigin(Object) Object
+ invokeOrigin(Object,Method,Object...)$ Object
}
ExecutableContext --> Attributes
ConstructorContext --> ExecutableContext
MethodContext --> ExecutableContext
Taking Dubbo3 as an example, let's see how to implement a business plugin.
.
├── com
│ └── jd
│ └── live
│ └── agent
│ └── plugin
│ └── router
│ └── dubbo
│ └── v3
│ ├── definition
│ │ ├── ClassLoaderFilterDefinition.java
│ │ ├── ClusterDefinition.java
│ │ └── LoadBalanceDefinition.java
│ ├── instance
│ │ └── DubboEndpoint.java
│ ├── interceptor
│ │ ├── ClassLoaderFilterInterceptor.java
│ │ ├── ClusterInterceptor.java
│ │ └── LoadBalanceInterceptor.java
│ ├── request
│ │ ├── DubboRequest.java
│ │ └── invoke
│ │ └── DubboInvocation.java
│ └── response
│ └── DubboResponse.java
└── org
└── apache
└── dubbo
└── rpc
└── cluster
└── support
└── DubboCluster3.java
definitionis used to store plugin definitions.interceptoris used to store interceptors.requestis used to store request objects.responseis used to store response objects.invokeis used to store invocation objects.instanceis used to store backend instance objects.- The
DubboCluster3cluster object is placed underorg.apache.dubbo.rpc.cluster.supportfor easy access to protected methods.
@Injectable
@Extension(value = "ClusterDefinition_v3")
@ConditionalOnProperty(name = GovernanceConfig.CONFIG_FLOW_CONTROL_ENABLED, matchIfMissing = true)
@ConditionalOnProperty(name = GovernanceConfig.CONFIG_LIVE_DUBBO_ENABLED, matchIfMissing = true)
@ConditionalOnClass(ClusterDefinition.TYPE_ABSTRACT_CLUSTER)
@ConditionalOnClass(ClassLoaderFilterDefinition.TYPE_CONSUMER_CLASSLOADER_FILTER)
public class ClusterDefinition extends PluginDefinitionAdapter {
protected static final String TYPE_ABSTRACT_CLUSTER = "org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker";
private static final String METHOD_DO_INVOKE = "doInvoke";
private static final String[] ARGUMENT_DO_INVOKE = new String[]{
"org.apache.dubbo.rpc.Invocation",
"java.util.List",
"org.apache.dubbo.rpc.cluster.LoadBalance"
};
@Inject(InvocationContext.COMPONENT_INVOCATION_CONTEXT)
private InvocationContext context;
public ClusterDefinition() {
this.matcher = () -> MatcherBuilder.isSubTypeOf(TYPE_ABSTRACT_CLUSTER)
.and(MatcherBuilder.not(MatcherBuilder.isAbstract()));
this.interceptors = new InterceptorDefinition[]{
new InterceptorDefinitionAdapter(
MatcherBuilder.named(METHOD_DO_INVOKE)
.and(MatcherBuilder.arguments(ARGUMENT_DO_INVOKE)),
() -> new ClusterInterceptor(context)
)
};
}
}- The plugin definition inherits from
PluginDefinitionAdapter, implementing thePluginDefinitioninterface. - The plugin declares the extension implementation with
@Extension. - The plugin declares multiple extension enabling conditions, describing that it is enabled when flow control is enabled, Dubbo is enabled, and it is running in the Dubbo3 environment.
- The plugin injects the invocation context
InvocationContextto facilitate multi-active traffic control. - In the constructor, the plugin uses
MatcherBuilderto describe the classes and methods to be intercepted.
public class ClusterInterceptor extends InterceptorAdaptor {
private final InvocationContext context;
private final Map<AbstractClusterInvoker<?>, DubboCluster3> clusters = new ConcurrentHashMap<>();
public ClusterInterceptor(InvocationContext context) {
this.context = context;
}
@Override
public void onEnter(ExecutableContext ctx) {
MethodContext mc = (MethodContext) ctx;
Object[] arguments = ctx.getArguments();
DubboCluster3 cluster = clusters.computeIfAbsent((AbstractClusterInvoker<?>) ctx.getTarget(), DubboCluster3::new);
List<Invoker<?>> invokers = (List<Invoker<?>>) arguments[1];
List<DubboEndpoint<?>> instances = invokers.stream().map(DubboEndpoint::of).collect(Collectors.toList());
DubboOutboundRequest request = new DubboOutboundRequest((Invocation) arguments[0]);
DubboOutboundInvocation invocation = new DubboOutboundInvocation(request, context);
DubboOutboundResponse response = cluster.request(context, invocation, instances);
if (response.getThrowable() != null) {
mc.setThrowable(response.getThrowable());
} else {
mc.setResult(response.getResponse());
}
mc.setSkip(true);
}
}- The interceptor declares interception on method entry using
onEnter. - It retrieves the method parameters through the context.
- It creates a cluster object, backend instance list, request object, and invocation object based on the parameters.
- It synchronously calls to get a response based on the cluster's strategy.
- It sets the response based on the invocation interface and skips the original method's processing.