Google

Jul 26, 2013

Creating Java custom annotations with Spring aspectj AOP

There are situations where you want to retry a particular method that fails. For example, retry submitting a message to an messaging queue 3 times, retry external service calls, etc. AOP (Aspect Oriented Programming) is well suited for this as this is a cross cutting concern. In this tutorial, I use aspectj, spring-aop, and Java annotation. Here are the steps.

Step 1:  The pom.xml file to outline the dependency jar files.

<!-- Spring -->
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-core</artifactId>
 <version>3.1.2.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context</artifactId>
 <version>3.1.2.RELEASE</version>

</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-aop</artifactId>
 <version>3.1.2.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context-support</artifactId>
 <version>3.1.2.RELEASE</version>
</dependency>

<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>2.2</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
 <artifactId>spring-aspects</artifactId>
 <version>3.1.2.RELEASE</version>
</dependency>


<!-- Aspectj -->
<dependency>
 <groupId>org.aspectj</groupId>
 <artifactId>aspectjrt</artifactId>
 <version>1.6.11</version>
</dependency>

<dependency>
 <groupId>org.aspectj</groupId>
 <artifactId>aspectjweaver</artifactId>
 <version>1.6.10</version>
</dependency>


Step 2:Define the annotation -- RetryOnFailure.

package com.mycompany.app9;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(
{
    ElementType.METHOD, ElementType.TYPE
})
public @interface RetryOnFailure
{
    int attempts() default 3;
    
    int delay() default 1000; //default 1 second
}

Step 3: Define the aspect point cut implementation RetryOnFailureAspect.

package com.mycompany.app9;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.NestedRuntimeException;

@Aspect
public class RetryOnFailureAspect
{
    public static final String RETRY_LIMIT_EXCEEDED_MSG = "Retry limit exceeded.";
    
    @Around("execution(* *(..)) && @annotation(retry)")
    public Object retry(ProceedingJoinPoint pjp, RetryOnFailure retry) throws Throwable
    {
        
        Object returnValue = null;
        for (int attemptCount = 1; attemptCount <= (1 + retry.attempts()); attemptCount++)
        {
            try
            {
                returnValue = pjp.proceed();
            }
            catch (Exception ex)
            {
                handleRetryException(pjp, ex, attemptCount, retry);
            }
        }
        return returnValue;
    }
    
    private void handleRetryException(ProceedingJoinPoint pjp, Throwable ex,
            int attemptCount, RetryOnFailure retry) throws Throwable
    {
        
        if (ex instanceof NestedRuntimeException)
        {
            ex = ((NestedRuntimeException) ex).getMostSpecificCause();
        }
        
        if (attemptCount == 1 + retry.attempts())
        {
            throw new RuntimeException(RETRY_LIMIT_EXCEEDED_MSG, ex);
        }
        else
        {  
            System.out.println(String
                    .format("%s: Attempt %d of %d failed with exception '%s'. Will retry immediately. %s",
                            pjp.getSignature(), attemptCount,
                            retry.attempts(),
                            ex.getClass().getCanonicalName(),
                            ex.getMessage()));
        }
    }
}

Step 4: Now wire up aspectj and the annotation via Spring xml files.

Firstly, wire up aop via spring-aop.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

 <!-- Enable the @AspectJ support -->
 <aop:aspectj-autoproxy />

 <bean id="retryOnFailureAspect" class="com.mycompany.app9.RetryOnFailureAspect" />

</beans>

Next, the application context xml file springApplicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

 <!-- Import your AspectJ config -->
 <import resource="classpath:spring-aop.xml" />

 <!-- Scan you spring services for annotations -->
 <context:component-scan base-package="com.mycompany.app9" />

</beans>


Step 5: Finally, the test class that uses this annotation. Forcefully throw an exception to see if the method is retried.

package com.mycompany.app9;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class RetryOnFailureTest
{
    
    @RetryOnFailure(attempts = 3, delay = 2000)
    public void testRetry()
    {
        System.out.println("Entered ....................");
        throw new RuntimeException("Forcing an exception");
    }
    
    public static void main(String[] args)
    {
        final ApplicationContext context = new ClassPathXmlApplicationContext("springApplicationContext.xml");
        // Get me my spring managed bean
        final RetryOnFailureTest retryFailureTest = context.getBean(RetryOnFailureTest.class);
        retryFailureTest.testRetry();
    }
}

Step 6: The output will be


Jul 24, 2013 6:41:24 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5e3974: startup date [Wed Jul 24 18:41:24 EST 2013]; root of context hierarchy
Jul 24, 2013 6:41:24 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springApplicationContext.xml]
Jul 24, 2013 6:41:24 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring-aop.xml]
Jul 24, 2013 6:41:24 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1dc423f: defining beans [org.springframework.aop.config.internalAutoProxyCreator,retryOnFailureAspect,retryOnFailureTest,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor#0]; root of factory hierarchy
Entered ....................
void com.mycompany.app9.RetryOnFailureTest.testRetry(): Attempt 1 of 3 failed with exception 'java.lang.RuntimeException'. Will retry immediately. Forcing an exception
Entered ....................
void com.mycompany.app9.RetryOnFailureTest.testRetry(): Attempt 2 of 3 failed with exception 'java.lang.RuntimeException'. Will retry immediately. Forcing an exception
Entered ....................
void com.mycompany.app9.RetryOnFailureTest.testRetry(): Attempt 3 of 3 failed with exception 'java.lang.RuntimeException'. Will retry immediately. Forcing an exception
Entered ....................
Exception in thread "main" java.lang.RuntimeException: Retry limit exceeded.
 at com.mycompany.app9.RetryOnFailureAspect.handleRetryException(RetryOnFailureAspect.java:43)
 at com.mycompany.app9.RetryOnFailureAspect.retry(RetryOnFailureAspect.java:26)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
 at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
 at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161)
 at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
 at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
 at com.mycompany.app9.RetryOnFailureTest$$EnhancerByCGLIB$$4086c6b6.testRetry()
 at com.mycompany.app9.RetryOnFailureTest.main(RetryOnFailureTest.java:23)
Caused by: java.lang.RuntimeException: Forcing an exception
 at com.mycompany.app9.RetryOnFailureTest.testRetry(RetryOnFailureTest.java:15)
 at com.mycompany.app9.RetryOnFailureTest$$FastClassByCGLIB$$e37240e1.invoke()
 at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:191)
 at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:689)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
 at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
 at com.mycompany.app9.RetryOnFailureAspect.retry(RetryOnFailureAspect.java:22)
 ... 13 more


Labels: ,

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home