Постобработчик бина для аннотации Retry - PullRequest
0 голосов
/ 11 июня 2018

У меня есть устаревший проект с версией Spring 3.0 (я не могу использовать аннотацию Retriable из пакета Spring).Я хочу внедрить аннотацию Retryable, чтобы аннотировать мои методы, выполнение которых следует повторить при неудаче.

Это мой класс:

@Component
public final class RetryBpp implements BeanPostProcessor {
    private final ConcurrentMap<String, ClassDefinition> map = new ConcurrentHashMap<>();

    @Override
    public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
        final Class<?> asClass = bean.getClass();
        final Method[] methods = asClass.getMethods();
        final List<Method> collect = Stream.of(methods)
                                           .filter(method -> method.isAnnotationPresent(Repitable.class))
                                           .collect(Collectors.toList());
        if(!collect.isEmpty()){
            this.map.put(beanName,new ClassDefinition(collect,asClass));
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
        final ClassDefinition definition = this.map.get(beanName);
        if(definition != null){
            final Class beanClass = definition.asClass;
            return Proxy.newProxyInstance(beanClass.getClassLoader(), beanClass.getInterfaces(), (proxy, method, args) -> {
                if(definition.isMethodPresent(method)){
                    System.out.println("Present");
                    return this.retry(definition.originalMethod(method),bean,args);
                } else{
                    return method.invoke(bean,args);
                }
            });
        } else{
            return bean;
        }
    }

    private Object retry(final Method method,final Object originalBean,Object[] argc){
        final Repitable repitable = method.getAnnotation(Repitable.class);
        int attempts = repitable.attempts();
        Throwable exc = null;
        while(attempts!=0){
            try{
                return method.invoke(originalBean,argc);
            }catch (final Throwable throwable){
                exc = throwable;
                attempts--;
                this.sleep(repitable.delay(),repitable.timeUnit());
            }
        }
        throw new RuntimeException(exc);
    }

    @SneakyThrows(InterruptedException.class)
    private void sleep(final int time, final TimeUnit timeUnit){
        timeUnit.sleep(time);
    }

    @AllArgsConstructor
    private static final class ClassDefinition{
        private final List<Method> methods;
        private final Class asClass;
        boolean isMethodPresent(final Method method){
            return this.methods.stream().anyMatch(mthd->mthd.getName().equals(method.getName()));
        }
        Method originalMethod(final Method method){
            return this.methods.stream().filter(mthd->mthd.getName().equals(method.getName())).findFirst().orElseThrow(NullPointerException::new);

        }

    }


}

И это работает, но я хочу изменить две вещи

1) В методе повтора я хочу сохранить последнее исключение и выдать, когда repeateCount = 0, для этого мне нужно объявить null ptr в exc, но я хочу, чтобы все мои поля были окончательными.Можно ли как-то переписать мой код?

2) В ClassDefinition я сравниваю Method по имени, потому что оригинальный equals метод Method class сравнивает по классу, я не могу сделать это, потому что оригинальный классзаменены на прокси, можно ли сравнить два Method's по-разному?

1 Ответ

0 голосов
/ 11 июня 2018

Для первой части: один вариант - использовать List из Throwables.И затем бросьте им что-то вроде:

final RuntimeException theError = new RuntimeException(list.get(list.size() - 1));
for (int i = 0; i < list.size() - 1; i++) {
    theError.addSuppressed(list.get(i));
}
throw theError;

Это также дает преимущество обеспечения всех сбоев.

Для последней части это может быть сложно и дорогов зависимости от того, будут ли в некоторых методах дженерики, var-args и т. д.

Поскольку вы уже используете spring, вы можете попробовать комбинацию: AopUtils.getTargetClass и AopUtils.getMostSpecificMethod, чтобы получитьчто вы хотите (потребуется рефакторинг того, что вы передаете некоторым методам тестирования).

Что-то простое, чтобы предоставить еще несколько (обратите внимание, не непогрешимых) тестов в духе того, с чем вы уже работаете, хотя:

return methods.stream().anyMatch(mthd -> {
    if (!method.getName().equals(mthd.getName())) {
        return false;
    }
    if (!method.getReturnType().isAssignableFrom(mthd.getReturnType())) {
        return false;
    }
    final Class<?>[] parameterTypes = method.getParameterTypes();
    if (mthd.getParameterTypes().length != parameterTypes.length) {
        return false;
    }
    for (int i = 0; i < parameterTypes.length; i++) {
        if (!parameterTypes[i].equals(mthd.getParameterTypes()[i])) {
            return false;
        }
    }
    return true;
});

Я уверен, что вы можете сделать еще несколько проверок (Method.getExceptionTypes(), чтобы подтвердить, что они могут переопределить; Method.getDeclaringClass() и обработать дерево оттуда; Method.getModifiers(), чтобы определить, может ли быть переопределено и т. Д.), Но это зависит от того, насколько безопасным вы должны быть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...