Skip to content

Commit

Permalink
refactor(AutoOperate): optimize code
Browse files Browse the repository at this point in the history
  • Loading branch information
Createsequence committed Apr 25, 2024
1 parent 226f7f6 commit eefe369
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
@RequiredArgsConstructor
public class AutoOperateProxy {

private final ResultHandler resultHandler = new ResultHandler();
private final ArgumentsHandler argumentsHandler = new ArgumentsHandler();
private final ResultAndArgumentsHandler resultAndArgumentsHandler = new ResultAndArgumentsHandler();

private final MethodArgumentAutoOperateSupport methodArgumentAutoOperateSupport;
private final MethodResultAutoOperateSupport methodResultAutoOperateSupport;
private final AnnotationFinder annotationFinder;
Expand All @@ -56,22 +60,26 @@ public <T> T wrapIfNecessary(T target) {
@NonNull
private Map<Method, MethodHandler> resolveMethodHandler(Class<?> targetType) {
Map<Method, MethodHandler> methodHandles = new HashMap<>(16);
ReflectUtils.traverseTypeHierarchy(targetType, t -> {
if (!Objects.equals(Object.class, t)) {
Stream.of(ReflectUtils.getDeclaredMethods(t))
.filter(this::isHandleableMethod)
.forEach(m -> {
MethodHandler handler = determineHandler(m);
if (Objects.nonNull(handler)) {
// keep only the override method
methodHandles.putIfAbsent(m, handler);
}
});
ReflectUtils.traverseTypeHierarchy(targetType, type -> {
if (!Objects.equals(Object.class, type)) {
processHandleableMethods(type, methodHandles);
}
});
return methodHandles;
}

private void processHandleableMethods(Class<?> t, Map<Method, MethodHandler> methodHandles) {
Stream.of(ReflectUtils.getDeclaredMethods(t))
.filter(this::isHandleableMethod)
.forEach(m -> {
MethodHandler handler = determineHandler(m);
if (Objects.nonNull(handler)) {
// keep only the override method
methodHandles.putIfAbsent(m, handler);
}
});
}

private boolean isHandleableMethod(Method method) {
if (method.isSynthetic()) {
return false;
Expand All @@ -85,10 +93,10 @@ private MethodHandler determineHandler(Method method) {
AutoOperate annotation = annotationFinder.getAnnotation(method, AutoOperate.class);
if (Objects.isNull(annotation)) {
return needHandleArgs(method) ?
new ArgumentsHandler() : null;
argumentsHandler : null;
}
return needHandleArgs(method) ?
new ResultAndArgumentsHandler(annotation) : new ResultHandler(annotation);
resultAndArgumentsHandler : resultHandler;
}

@RequiredArgsConstructor
Expand Down Expand Up @@ -125,13 +133,10 @@ private interface MethodHandler {

@RequiredArgsConstructor
private class ResultHandler implements MethodHandler {
private final AutoOperate annotation;
@Override
public Object invoke(Object target, Method method, Object[] arguments) {
Object result = ReflectUtils.invoke(target, method, arguments);
methodResultAutoOperateSupport.afterMethodInvoke(
annotation, method, result, arguments
);
methodResultAutoOperateSupport.afterMethodInvoke(method, result, arguments);
return result;
}
}
Expand All @@ -147,14 +152,11 @@ public Object invoke(Object target, Method method, Object[] arguments) {

@RequiredArgsConstructor
private class ResultAndArgumentsHandler implements MethodHandler {
private final AutoOperate annotation;
@Override
public Object invoke(Object target, Method method, Object[] arguments) {
methodArgumentAutoOperateSupport.beforeMethodInvoke(method, arguments);
Object result = ReflectUtils.invoke(target, method, arguments);
methodResultAutoOperateSupport.afterMethodInvoke(
annotation, method, result, arguments
);
methodResultAutoOperateSupport.afterMethodInvoke(method, result, arguments);
return result;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import cn.crane4j.core.util.ReflectUtils;
import cn.crane4j.core.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.NonNull;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
Expand All @@ -36,7 +36,6 @@
* <p>辅助类支持通过{@link AutoOperate#condition()}设置的条件表达式,
* 每次执行时都会通过{@link MethodBasedExpressionEvaluator}来进行求值,
* 只有当表达式返回true或者 "true" 字符串时,才会执行填充。
*
* <hr/>
*
* <p>Support class for completing the operation of data
Expand Down Expand Up @@ -98,11 +97,9 @@ public final void beforeMethodInvoke(Method method, Object[] args) {
if (ArrayUtils.isEmpty(args)) {
return;
}
// cache resolved parameters
ArgAutoOperate methodLevelAnnotation = annotationFinder.findAnnotation(method, ArgAutoOperate.class);
// fix https://gitee.com/opengoofy/crane4j/issues/I82EAC
AutoOperateAnnotatedElement[] elements = CollectionUtils.computeIfAbsent(
methodParameterCaches, method, name -> resolveParameters(methodLevelAnnotation, method)
methodParameterCaches, method, this::resolveParameters
);
if (elements == EMPTY_ELEMENTS) {
return;
Expand Down Expand Up @@ -133,22 +130,17 @@ protected void processArguments(Method method, Object[] args, AutoOperateAnnotat
* Analyze the annotations on methods and method parameters
* to obtain the operation configuration of method parameters.
*
* @param argAutoOperate argAutoOperate
* @param method method
* @return operation configuration of method parameters
* @return operation configuration of method parameters,
* if not found, return {@link #EMPTY_ELEMENTS}
* @see #EMPTY_ELEMENTS
*/
protected AutoOperateAnnotatedElement[] resolveParameters(@Nullable ArgAutoOperate argAutoOperate, Method method) {
protected AutoOperateAnnotatedElement[] resolveParameters(Method method) {
if (method.getParameterCount() < 1) {
log.warn("cannot apply auto operate for method [{}], because it has no parameters", method);
return EMPTY_ELEMENTS;
}
Map<String, AutoOperate> methodLevelAnnotations = Optional.ofNullable(argAutoOperate)
.map(ArgAutoOperate::value)
.map(Arrays::stream)
.map(annotations -> annotations.map(a -> checkMethodLevelAnnotation(method, a))
.collect(Collectors.toMap(AutoOperate::value, Function.identity()))
)
.orElseGet(Collections::emptyMap);
Map<String, AutoOperate> methodLevelAnnotations = resolveMethodLevelAnnotations(method);
Map<String, Parameter> parameterMap = ReflectUtils.resolveParameterNames(parameterNameFinder, method);
AutoOperateAnnotatedElement[] results = new AutoOperateAnnotatedElement[parameterMap.size()];
int index = 0;
Expand All @@ -159,7 +151,8 @@ protected AutoOperateAnnotatedElement[] resolveParameters(@Nullable ArgAutoOpera
AutoOperate annotation = Optional
.ofNullable(annotationFinder.getAnnotation(param, AutoOperate.class))
.orElse(methodLevelAnnotations.get(paramName));
results[index++] = Objects.isNull(annotation) ? null : elementResolver.resolve(param, annotation);
results[index++] = Objects.isNull(annotation) ?
null : elementResolver.resolve(param, annotation);
}
if (Stream.of(results).allMatch(Objects::isNull)) {
log.warn("cannot apply auto operate for method [{}], because all parameters have no operation configuration", method);
Expand All @@ -168,6 +161,17 @@ protected AutoOperateAnnotatedElement[] resolveParameters(@Nullable ArgAutoOpera
return results;
}

@NonNull
private Map<String, AutoOperate> resolveMethodLevelAnnotations(Method method) {
return Optional.ofNullable(annotationFinder.findAnnotation(method, ArgAutoOperate.class))
.map(ArgAutoOperate::value)
.map(Arrays::stream)
.map(annotations -> annotations.map(a -> checkMethodLevelAnnotation(method, a))
.collect(Collectors.toMap(AutoOperate::value, Function.identity()))
)
.orElseGet(Collections::emptyMap);
}

/**
* Clear resources when destroying the bean.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package cn.crane4j.core.support.aop;

import cn.crane4j.annotation.ArgAutoOperate;
import cn.crane4j.annotation.AutoOperate;
import cn.crane4j.core.support.AnnotationFinder;
import cn.crane4j.core.support.auto.AutoOperateAnnotatedElement;
import cn.crane4j.core.support.auto.AutoOperateAnnotatedElementResolver;
import cn.crane4j.core.support.expression.MethodBasedExpressionEvaluator;
import cn.crane4j.core.util.CollectionUtils;
import cn.crane4j.core.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.NonNull;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
* <p>一个用于在方法调用后,根据{@link AutoOperate}注解的配置,对方法返回结果进行填充的辅助类。
Expand All @@ -23,7 +26,6 @@
* <p>辅助类支持通过{@link AutoOperate#condition()}设置的条件表达式,
* 每次执行时都会通过{@link MethodBasedExpressionEvaluator}来进行求值,
* 只有当表达式返回true或者 "true" 字符串时,才会执行填充。
*
* <hr/>
*
* <p>Support class for completing the operation for the result of method which annotated by {@link AutoOperate}.
Expand All @@ -41,49 +43,33 @@
* @see AutoOperateAnnotatedElementResolver
*/
@Slf4j
@RequiredArgsConstructor
public class MethodResultAutoOperateSupport {

protected final Map<Method, AutoOperateAnnotatedElement> methodCaches = CollectionUtils.newWeakConcurrentMap();
protected final AutoOperateAnnotatedElementResolver elementResolver;
protected final MethodBasedExpressionEvaluator expressionEvaluator;

/**
* Create a {@link MethodResultAutoOperateSupport} instance
*
* @param elementResolver element handler
* @param expressionEvaluator method base expression evaluator delegate
*/
public MethodResultAutoOperateSupport(
AutoOperateAnnotatedElementResolver elementResolver, MethodBasedExpressionEvaluator expressionEvaluator) {
this.elementResolver = elementResolver;
this.expressionEvaluator = expressionEvaluator;
}
protected final AnnotationFinder annotationFinder;

/**
* After the method is called, process the returning result
* of the method according to the configuration of {@link ArgAutoOperate} annotation.
* of the method according to the configuration of {@link AutoOperate} annotation.
*
* @param annotation annotation
* @param method method
* @param result result
* @param args args
*/
public void afterMethodInvoke(AutoOperate annotation, Method method, Object result, Object[] args) {
public void afterMethodInvoke(Method method, Object result, Object[] args) {
// has annotation?
if (Objects.isNull(annotation) || Objects.equals(method.getReturnType(), Void.TYPE)) {
if (Objects.isNull(result)) {
return;
}
// get and build method cache
log.debug("process result for [{}]", method);
// fix https://gitee.com/opengoofy/crane4j/issues/I82EAC
AutoOperateAnnotatedElement element = CollectionUtils.computeIfAbsent(methodCaches, method, m -> {
AutoOperateAnnotatedElement aoe = elementResolver.resolve(m, annotation);
if (Objects.isNull(aoe)) {
log.warn("cannot apply auto operate for method [{}], because return type [{}] have no operation configuration", m, m.getReturnType());
return AutoOperateAnnotatedElement.EMPTY;
}
return aoe;
});
AutoOperateAnnotatedElement element = CollectionUtils.computeIfAbsent(
methodCaches, method, this::resolveReturn
);
// fix https://github.com/opengoofy/crane4j/issues/204
if (element == AutoOperateAnnotatedElement.EMPTY) {
return;
Expand All @@ -95,6 +81,25 @@ public void afterMethodInvoke(AutoOperate annotation, Method method, Object resu
}
}

/**
* Resolve the {@link AutoOperate} annotation on the method
*
* @param method method
* @return operation configuration of method result,
* if not found, return {@link AutoOperateAnnotatedElement#EMPTY}
* @see AutoOperateAnnotatedElement#EMPTY
*/
@NonNull
protected AutoOperateAnnotatedElement resolveReturn(Method method) {
if (Objects.equals(Void.TYPE, method.getReturnType())) {
log.warn("cannot apply auto operate for method [{}], because return type is void", method);
return AutoOperateAnnotatedElement.EMPTY;
}
return Optional.ofNullable(annotationFinder.findAnnotation(method, AutoOperate.class))
.map(annotation -> elementResolver.resolve(method, annotation))
.orElse(AutoOperateAnnotatedElement.EMPTY);
}

/**
* Clear resources when destroying the bean.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public static AutoOperateProxy createAutoOperateProxy(Crane4jGlobalConfiguration
parameterNameFinder, expressionEvaluator, OgnlExpressionContext::new
);
MethodResultAutoOperateSupport resultAutoOperateSupport = new MethodResultAutoOperateSupport(
resolver, methodBasedExpressionEvaluator
resolver, methodBasedExpressionEvaluator, SimpleAnnotationFinder.INSTANCE
);
MethodArgumentAutoOperateSupport argumentAutoOperateSupport = new MethodArgumentAutoOperateSupport(
resolver, methodBasedExpressionEvaluator, parameterNameFinder, annotationFinder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import cn.crane4j.core.container.LambdaContainer;
import cn.crane4j.core.support.Crane4jGlobalConfiguration;
import cn.crane4j.core.support.ParameterNameFinder;
import cn.crane4j.core.support.SimpleAnnotationFinder;
import cn.crane4j.core.support.SimpleCrane4jGlobalConfiguration;
import cn.crane4j.core.support.SimpleParameterNameFinder;
import cn.crane4j.core.support.auto.AutoOperateAnnotatedElementResolver;
Expand Down Expand Up @@ -40,13 +41,13 @@ public class MethodResultAutoOperateSupportTest {

@Before
public void init() {
Crane4jGlobalConfiguration configuration = SimpleCrane4jGlobalConfiguration.create();
Crane4jGlobalConfiguration configuration = SimpleCrane4jGlobalConfiguration.builder().build();
ParameterNameFinder parameterNameFinder = new SimpleParameterNameFinder();
MethodBasedExpressionEvaluator expressionEvaluator = new MethodBasedExpressionEvaluator(
parameterNameFinder, new OgnlExpressionEvaluator(), method -> new OgnlExpressionContext()
);
AutoOperateAnnotatedElementResolver resolver = new MethodBasedAutoOperateAnnotatedElementResolver(configuration, configuration.getTypeResolver());
support = new MethodResultAutoOperateSupport(resolver, expressionEvaluator);
support = new MethodResultAutoOperateSupport(resolver, expressionEvaluator, SimpleAnnotationFinder.INSTANCE);

configuration.registerContainer(LambdaContainer.<Integer>forLambda(
"test", ids -> ids.stream().map(id -> new Foo(id, "name" + id))
Expand All @@ -58,34 +59,34 @@ parameterNameFinder, new OgnlExpressionEvaluator(), method -> new OgnlExpression
public void beforeMethodInvoke() {
Method method = ReflectUtils.getMethod(this.getClass(), "method", Collection.class);
Assert.assertNotNull(method);
AutoOperate annotation = method.getAnnotation(AutoOperate.class);
Assert.assertNotNull(annotation);
Result<Foo> foo = new Result<>(new Foo(1));
support.afterMethodInvoke(annotation, method, foo, new Object[]{ Arrays.asList(1, 2) });
support.afterMethodInvoke(method, foo, new Object[]{ Arrays.asList(1, 2) });
Assert.assertEquals("name1", foo.getData().getName());
support.afterMethodInvoke(null, method, foo, new Object[]{ Arrays.asList(1, 2) });

method = ReflectUtils.getMethod(this.getClass(), "notAnnotatedMethodWhenCannotResolveOperationsFromCurrentElement", Collection.class);
Assert.assertNotNull(method);
support.afterMethodInvoke(method, foo, new Object[]{ Arrays.asList(1, 2) });
}

@Test
public void beforeMethodInvokeWhenResolveOperationsFromCurrentElement() {
Method method = ReflectUtils.getMethod(this.getClass(), "methodWhenResolveOperationsFromCurrentElement", Collection.class);
Assert.assertNotNull(method);
AutoOperate annotation = method.getAnnotation(AutoOperate.class);
Assert.assertNotNull(annotation);
Result<Foo2> foo = new Result<>(new Foo2(1));
support.afterMethodInvoke(annotation, method, foo, new Object[]{ Arrays.asList(1, 2) });
support.afterMethodInvoke(method, foo, new Object[]{ Arrays.asList(1, 2) });
Assert.assertEquals("name1", foo.getData().getName());
support.afterMethodInvoke(null, method, foo, new Object[]{ Arrays.asList(1, 2) });

method = ReflectUtils.getMethod(this.getClass(), "notAnnotatedMethodWhenCannotResolveOperationsFromCurrentElement", Collection.class);
Assert.assertNotNull(method);
support.afterMethodInvoke(method, foo, new Object[]{ Arrays.asList(1, 2) });
}

@Test
public void beforeMethodInvokeWhenCannotResolveOperationsFromCurrentElement() {
Method method = ReflectUtils.getMethod(this.getClass(), "methodWhenCannotResolveOperationsFromCurrentElement", Collection.class);
Assert.assertNotNull(method);
AutoOperate annotation = method.getAnnotation(AutoOperate.class);
Assert.assertNotNull(annotation);
Result<Foo2> foo = new Result<>(new Foo2(1));
support.afterMethodInvoke(annotation, method, foo, new Object[]{ Arrays.asList(1, 2) });
support.afterMethodInvoke(method, foo, new Object[]{ Arrays.asList(1, 2) });
Assert.assertNull(foo.getData().getName());
}

Expand All @@ -104,6 +105,9 @@ private Result<Collection<Foo2>> methodWhenResolveOperationsFromCurrentElement(C
private Result<Collection<Foo2>> methodWhenCannotResolveOperationsFromCurrentElement(Collection<Integer> ids) {
return new Result<>(ids.stream().map(Foo2::new).collect(Collectors.toList()));
}
private Result<Collection<Foo2>> notAnnotatedMethodWhenCannotResolveOperationsFromCurrentElement(Collection<Integer> ids) {
return new Result<>(ids.stream().map(Foo2::new).collect(Collectors.toList()));
}

@Getter
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -504,9 +504,9 @@ public MethodBasedExpressionEvaluator methodBaseExpressionEvaluator(
@Bean
public MethodResultAutoOperateSupport methodResultAutoOperateSupport(
AutoOperateAnnotatedElementResolver autoOperateAnnotatedElementResolver,
MethodBasedExpressionEvaluator methodBasedExpressionEvaluator) {
MethodBasedExpressionEvaluator methodBasedExpressionEvaluator, AnnotationFinder annotationFinder) {
return new MethodResultAutoOperateSupport(
autoOperateAnnotatedElementResolver, methodBasedExpressionEvaluator
autoOperateAnnotatedElementResolver, methodBasedExpressionEvaluator, annotationFinder
);
}

Expand Down
Loading

0 comments on commit eefe369

Please sign in to comment.