Skip to content

Commit

Permalink
feature: support saga annotation (#6973)
Browse files Browse the repository at this point in the history
  • Loading branch information
wt-better authored Dec 1, 2024
1 parent 70b4fc0 commit db1567c
Show file tree
Hide file tree
Showing 34 changed files with 1,555 additions and 402 deletions.
5 changes: 5 additions & 0 deletions all/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,11 @@
<artifactId>seata-saga-spring</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.seata</groupId>
<artifactId>seata-saga-annotation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,11 @@
<artifactId>seata-saga-engine-store</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.seata</groupId>
<artifactId>seata-saga-annotation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.seata</groupId>
<artifactId>seata-sqlparser-core</artifactId>
Expand Down
1 change: 1 addition & 0 deletions changes/en-us/2.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Add changes here for all PR submitted to the 2.x branch.
- [[#6864](https://github.com/apache/incubator-seata/pull/6864)] support shentong database
- [[#6974](https://github.com/apache/incubator-seata/pull/6974)] support fastjson2 undolog parser
- [[#6992](https://github.com/apache/incubator-seata/pull/6992)] support grpc serializer
- [[#6973](https://github.com/apache/incubator-seata/pull/6973)] support saga annotation
- [[#6926](https://github.com/apache/incubator-seata/pull/6926)] support ssl communication for raft nodes


Expand Down
1 change: 1 addition & 0 deletions changes/zh-cn/2.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [[#6974](https://github.com/apache/incubator-seata/pull/6974)] 支持UndoLog的fastjson2序列化方式
- [[#6992](https://github.com/apache/incubator-seata/pull/6992)] 支持grpc序列化器
- [[#6995](https://github.com/apache/incubator-seata/pull/6995)] 升级过时的 npmjs 依赖
- [[#6973](https://github.com/apache/incubator-seata/pull/6973)] 支持saga注解化
- [[#6926](https://github.com/apache/incubator-seata/pull/6926)] 支持Raft节点间的SSL通信

### bugfix:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;

/**
* Reflection tools
Expand Down Expand Up @@ -495,6 +497,34 @@ public static Method getMethod(final Class<?> clazz, final String methodName)
return getMethod(clazz, methodName, EMPTY_CLASS_ARRAY);
}

/**
* Recursively get clazz and their interfaces match matchCondition method-class mapping
*
* @param clazz clazz
* @param matchCondition matchCondition
* @return Set
*/
public static Map<Method, Class<?>> findMatchMethodClazzMap(Class<?> clazz, Predicate<Method> matchCondition) {
Map<Method, Class<?>> methodClassMap = new HashMap<>();

for (Method method : clazz.getMethods()) {
if (matchCondition.test(method)) {
methodClassMap.put(method, clazz);
}
}

Set<Class<?>> interfaceClasses = getInterfaces(clazz);
for (Class<?> interClass : interfaceClasses) {
for (Method method : interClass.getMethods()) {
if (matchCondition.test(method)) {
methodClassMap.put(method, interClass);
}
}
}

return methodClassMap;
}

/**
* invoke Method
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@
*/
package io.seata.rm.tcc.interceptor.parser;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
import io.seata.rm.tcc.interceptor.TccActionInterceptorHandler;
import org.apache.seata.common.exception.FrameworkException;
import org.apache.seata.common.util.ReflectionUtil;
import org.apache.seata.common.util.StringUtils;
import org.apache.seata.core.model.Resource;
import org.apache.seata.integration.tx.api.interceptor.handler.ProxyInvocationHandler;
import org.apache.seata.integration.tx.api.interceptor.parser.DefaultResourceRegisterParser;
import org.apache.seata.rm.tcc.TCCResource;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
* The type Tcc action interceptor parser.
Expand All @@ -36,40 +41,72 @@ public class TccActionInterceptorParser extends org.apache.seata.rm.tcc.intercep

@Override
public ProxyInvocationHandler parserInterfaceToProxy(Object target, String objectName) {
// eliminate the bean without two phase annotation.
Set<String> methodsToProxy = this.tccProxyTargetMethod(target);
Map<Method, Class<?>> methodClassMap = ReflectionUtil.findMatchMethodClazzMap(target.getClass(), method -> method.isAnnotationPresent(getAnnotationClass()));
Set<Method> methodsToProxy = methodClassMap.keySet();
if (methodsToProxy.isEmpty()) {
return null;
}

// register resource and enhance with interceptor
DefaultResourceRegisterParser.get().registerResource(target, objectName);
return new TccActionInterceptorHandler(target, methodsToProxy);
registerResource(target, methodClassMap);

return new TccActionInterceptorHandler(target, methodsToProxy.stream().map(Method::getName).collect(Collectors.toSet()));
}

private Set<String> tccProxyTargetMethod(Object target) {
Set<String> methodsToProxy = new HashSet<>();
//check if it is TCC bean
Class<?> tccServiceClazz = target.getClass();
Set<Method> methods = new HashSet<>(Arrays.asList(tccServiceClazz.getMethods()));
Set<Class<?>> interfaceClasses = ReflectionUtil.getInterfaces(tccServiceClazz);
if (interfaceClasses != null) {
for (Class<?> interClass : interfaceClasses) {
methods.addAll(Arrays.asList(interClass.getMethods()));
}
}
@Override
protected Class<? extends Annotation> getAnnotationClass() {
return TwoPhaseBusinessAction.class;
}

TwoPhaseBusinessAction twoPhaseBusinessAction;
for (Method method : methods) {
twoPhaseBusinessAction = method.getAnnotation(TwoPhaseBusinessAction.class);
if (twoPhaseBusinessAction != null) {
methodsToProxy.add(method.getName());
}
protected Resource createResource(Object target, Class<?> targetServiceClass, Method m, Annotation annotation) throws NoSuchMethodException {
TwoPhaseBusinessAction twoPhaseBusinessAction = (TwoPhaseBusinessAction) annotation;
TCCResource tccResource = new TCCResource();
if (StringUtils.isBlank(twoPhaseBusinessAction.name())) {
throw new FrameworkException("TCC bean name cannot be null or empty");
}
tccResource.setActionName(twoPhaseBusinessAction.name());
tccResource.setTargetBean(target);
tccResource.setPrepareMethod(m);
tccResource.setCommitMethodName(twoPhaseBusinessAction.commitMethod());
tccResource.setCommitMethod(targetServiceClass.getMethod(twoPhaseBusinessAction.commitMethod(),
twoPhaseBusinessAction.commitArgsClasses()));
tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod());
tccResource.setRollbackMethod(targetServiceClass.getMethod(twoPhaseBusinessAction.rollbackMethod(),
twoPhaseBusinessAction.rollbackArgsClasses()));
// set argsClasses
tccResource.setCommitArgsClasses(twoPhaseBusinessAction.commitArgsClasses());
tccResource.setRollbackArgsClasses(twoPhaseBusinessAction.rollbackArgsClasses());
// set phase two method's keys
tccResource.setPhaseTwoCommitKeys(this.getTwoPhaseArgs(tccResource.getCommitMethod(),
twoPhaseBusinessAction.commitArgsClasses()));
tccResource.setPhaseTwoRollbackKeys(this.getTwoPhaseArgs(tccResource.getRollbackMethod(),
twoPhaseBusinessAction.rollbackArgsClasses()));
return tccResource;
}

if (methodsToProxy.isEmpty()) {
return Collections.emptySet();
protected String[] getTwoPhaseArgs(Method method, Class<?>[] argsClasses) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
String[] keys = new String[parameterAnnotations.length];
/*
* get parameter's key
* if method's parameter list is like
* (BusinessActionContext, @BusinessActionContextParameter("a") A a, @BusinessActionContextParameter("b") B b)
* the keys will be [null, a, b]
*/
for (int i = 0; i < parameterAnnotations.length; i++) {
for (int j = 0; j < parameterAnnotations[i].length; j++) {
if (parameterAnnotations[i][j] instanceof BusinessActionContextParameter) {
BusinessActionContextParameter param = (BusinessActionContextParameter) parameterAnnotations[i][j];
String key = io.seata.integration.tx.api.interceptor.ActionContextUtil.getParamNameFromAnnotation(param);
keys[i] = key;
break;
}
}
if (keys[i] == null && !(argsClasses[i].equals(BusinessActionContext.class))) {
throw new IllegalArgumentException("non-BusinessActionContext parameter should use annotation " +
"BusinessActionContextParameter");
}
}
// sofa:reference / dubbo:reference, AOP
return methodsToProxy;
return keys;
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public enum BranchType {
*/
SAGA,

/**
* The SAGA_ANNOTATION.
*/
SAGA_ANNOTATION,

/**
* The XA.
*/
Expand Down
Loading

0 comments on commit db1567c

Please sign in to comment.