1

I have created a spring aspect to handle Retry mechanism. I have also created a Retry annotation. Following is the code for Retry annotation and an aspect which processes this annotation.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Retry {
    /**
     * List of exceptions for which we need to retry method invocation.
     * 
     * @return Array of classes.
     */
    Class<?>[] exceptions();

    /**
     * Number of retries. Default is 3.
     * 
     * @return Number of retires.
     */
    int retries() default 3;

    /**
     * Back of period in ms. Default is 1000 ms.
     * 
     * @return Back off Period.
     */
    int backOffPeriod() default 1000;

}

@Aspect
public class RetryInterceptor implements Ordered {

    private static final RetryInterceptor   instance    = new RetryInterceptor();

    private RetryInterceptor() {
    }

    private static final Log    logger  = LogFactory.getLog(RetryInterceptor.class);
    private int                 order   = 100;

    @Around("@annotation(retry)")
    public Object performOperation(ProceedingJoinPoint pjp, Retry retry) throws Throwable {
        Class<?>[] exceptionClasses = retry.exceptions();
        Assert.notEmpty(exceptionClasses, "Exception classes cannot be empty.");
        int retries = retry.retries();
        if (logger.isInfoEnabled()) {
            logger.info("Attempting to call " + pjp.toShortString() + " with potential for " + getExceptionClasses(exceptionClasses)
                    + " with maximum " + retries + " retries");
        }
        int numAttempts = 0;
        do {
            try {
                return pjp.proceed();
            } catch (Throwable ex) {
                // if the exception is not what we're looking for, pass it through
                boolean canThrowException = true;
                for (Class<?> exceptionClass : exceptionClasses) {
                    if (exceptionClass.isAssignableFrom(ex.getClass())) {
                        canThrowException = false;
                        break;
                    }
                }
                // A non-configured exception was found.
                if (canThrowException) {
                    throw ex;
                }
                // we caught the configured exception, retry unless we've reached the maximum
                if (++numAttempts > retries) {
                    logger.warn("Caught " + ex.getClass().getCanonicalName() + " and exceeded maximum retries (" + retries
                            + "), rethrowing.");
                    throw ex;
                }
                if (logger.isInfoEnabled()) {
                    logger.info("Caught " + ex.getClass().getCanonicalName() + " and will retry, attempts: " + numAttempts);
                }
            }
            sleep(retry.backOffPeriod());
        } while (numAttempts <= retries);
        // this will never execute - we will have either successfully returned or re-thrown an
        // exception
        return null;
    }

    @Override
    public int getOrder() {
        return order;
    }

    private String getExceptionClasses(Class<?>[] classes) {
        StringBuilder builder = new StringBuilder();
        builder.append(classes[0].getCanonicalName());
        for (int i = 1; i < classes.length; i++) {
            builder.append(", ").append(classes[i].getCanonicalName());
        }
        return builder.toString();
    }

    public static RetryInterceptor getInstance() {
        return instance;
    }

    // Better than Thread.sleep().
    public void sleep(long backOffPeriod) throws InterruptedException {
        Object mutex = new Object();
        synchronized (mutex) {
            mutex.wait(backOffPeriod);
        }
    }
}

To enable the annotation, I need to instantiate the RetryInterceptor class. I want to ensure that for a given context there is only one instance of this object. If for some reason multiple objects are created my advice is applied that many times. How can I totally ensure that there will always be 1 instance?

Vivek Kothari
  • 462
  • 6
  • 20

3 Answers3

1

I found a way to do this :) Ref: Going Beyond DI I registered a BeanDefinitionRegistryPostProcessor in my root context, this will ensure that there is only one BeanDefinition of my desired class.

package test;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;

import com.xx.xx.xx.xx.xx.RetryInterceptor;

public class TestBeanFacotryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        String[] definitionNames = registry.getBeanDefinitionNames();
        for (int i = 0, j = 0; i < definitionNames.length; i++) {
            Class<?> clazz;
            try {
                clazz = Class.forName(registry.getBeanDefinition(definitionNames[i]).getBeanClassName());
                if (RetryInterceptor.class == clazz && j++ > 0) {
                    registry.removeBeanDefinition(definitionNames[i]);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
}
Vivek Kothari
  • 462
  • 6
  • 20
0

There will ever only be one instance as its a spring managed bean with the default scope of singleton. You have some singleton type stuff at the top of your class (like where you create your new instance statically etc)... this is not needed. An aspect is just a bean like any other so code it asuch. If you want to be sure, put in a PostContruct method with some logging like 'Initiating aspect' or something and you will see it only prints to your logs once.

dectarin
  • 986
  • 5
  • 15
  • I know that, but what if in one of the xmls I instantiated this bean, with id1 and someone else instantiated it with id2, then 2 aspects will be registered for the same pointcut and that's a problem. ALso if you notice I created my aspect as a JAVA singleton, but that still doesn't help because someone can still use the same singleton instance and register it twice in the spring context. – Vivek Kothari Jul 28 '14 at 13:08
  • Ah ok, I understand you now. If you dont have full control of the contexts yourself (ie. if this will be jarred up for other teams/developers to use) then I don't think what you want is possible for the reasons you just gave there :-/ – dectarin Jul 28 '14 at 13:19
0
  1. if someone can create second bean of your type, why you are don't want to check someone creates another aspect with same logic? I think your approach is erroneous by design.
  2. you can implement ApplicationContextAware interface and check there, that only one bean of your class present in context and throw exception if it's not true, but I'm not sure that this will work if you have context hierarchy.