Wednesday, October 27, 2010

Logging and Spring AOP

Problem

Normally, we log down the values of parameters of a method and the value of return of a method. For example,

public class MyClass {

      public YYY getSomething(int id, Object obj) {
             log.debug ("para[id]="+id+",para[obj]="+obj);
             // do something, then return object YYY
             log.debug("return value : "+YYY);
             return YYY;
     }

}

We can log down the relative information with spring AOP. It is not necessary to log down something in each methods.

Configuration

prepare the spring configuration as follows.

<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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-3.0.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    <context:component-scan base-package="com.spring.test" />

    <aop:aspectj-autoproxy />

    <bean id="personAopImpl" class="com.spring.test.aop.PersonAopImpl" />
    <bean id="personAopProxy" class="com.spring.test.aop.PersonAopProxy" />
  
</beans>

Reference Library

add the following reference library files to your project. you can get them from spring v3.0.2 dist package and its dependency package.

com.springsource.javax.annotation-1.0.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.apache.commons.logging-1.1.1.jar
com.springsource.org.aspectj.tools-1.6.6.RELEASE.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
org.springframework.aop-3.0.2.RELEASE.jar
org.springframework.asm-3.0.2.RELEASE.jar
org.springframework.beans-3.0.2.RELEASE.jar
org.springframework.context-3.0.2.RELEASE.jar

org.springframework.context.support-3.0.2.RELEASE.jar
org.springframework.core-3.0.2.RELEASE.jar
org.springframework.expression-3.0.2.RELEASE.jar

Source Code

// there are two POJO classes
package com.spring.test.aop;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.Map;

public class Address {

    private static final long serialVersionUID = -872400794860227364L;
    private int floor;
    private String unit;

    public Address() {
        super();
    }

    public Address(int floor, String unit) {
        super();
        this.floor = floor;
        this.unit = unit;
    }

    public int getFloor() {
        return floor;
    }

    public void setFloor(int floor) {
        this.floor = floor;
    }

    public String getUnit() {
        return unit;
    }

    public void setUnit(String unit) {
        this.unit = unit;
    }

    public String toString() {
        Map<String, Object> map = new LinkedHashMap<String, Object>();
        try {
            Field[] fields = this.getClass().getDeclaredFields();
            if (fields != null) {
                for (Field f : fields) {
                    if (Modifier.isPrivate(f.getModifiers())
                            || Modifier.isProtected(f.getModifiers())) {
                        f.setAccessible(true);
                    } else {
                        continue;
                    }

                    map.put(f.getName(), f.get(this));
                }
            }
        } catch (Exception e) {
        }
        return map.toString();
    }
}
=====================================================
package com.spring.test.aop;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class PersonInfo {

    private static final long serialVersionUID = 1552674486660927122L;
    private int id;
    private String name;
    private List<String> mobilePhone;
    private List<Address> addressList;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getMobilePhone() {
        return mobilePhone;
    }

    public void setMobilePhone(List<String> mobilePhone) {
        this.mobilePhone = mobilePhone;
    }

    public List<Address> getAddressList() {
        return addressList;
    }

    public void setAddressList(List<Address> addressList) {
        this.addressList = addressList;
    }

    public String toString() {
        Map<String, Object> map = new LinkedHashMap<String, Object>();
        try {
            Field[] fields = this.getClass().getDeclaredFields();
            if (fields != null) {
                for (Field f : fields) {
                    if (Modifier.isPrivate(f.getModifiers())
                            || Modifier.isProtected(f.getModifiers())) {
                        f.setAccessible(true);
                    } else {
                        continue;
                    }

                    map.put(f.getName(), f.get(this));
                }
            }
        } catch (Exception e) {
        }
        return map.toString();
    }
}
=======================================================
// interface class
package com.spring.test.aop;

public interface IPersonAop {
    public void sayHello() throws Throwable;
    public void sayHello2() throws Throwable;
    public boolean createPersonInfo(PersonInfo info, int id);
    public PersonInfo getPersonInfo(int id);
}
=======================================================
// implement class
package com.spring.test.aop;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

@Service
public class PersonAopImpl implements IPersonAop {

    @Override
    public void sayHello() throws Throwable {
        System.out.println("Hello !!");
        throw new Exception("throw out exception in sayHello().");
    }
   
    @Override
    public void sayHello2() throws Throwable {
        System.out.println("Hello2 !!");
    }

    @Override
    public boolean createPersonInfo(PersonInfo info, int id) {
        info.setId(id);
        System.out.println("creating person information " + info);
        return true;
    }

    @Override
    public PersonInfo getPersonInfo(int id) {
        Address addr1 = new Address(15, "-101");
        Address addr2 = new Address(19, "-204");
        List<Address> addr = new ArrayList<Address>();
        addr.add(addr1);
        addr.add(addr2);
        PersonInfo info = new PersonInfo();
        info.setAddressList(addr);
        info.setName("testname2");
        List<String> mobile = new ArrayList<String>();
        mobile.add("999999");
        mobile.add("888888");
        info.setMobilePhone(mobile);
        info.setId(id);
        return info;
    }
}
========================================================
// Junit class
package com.spring.test.junit;

import java.util.ArrayList;
import java.util.List;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.test.aop.Address;
import com.spring.test.aop.IPersonAop;
import com.spring.test.aop.PersonInfo;

public class AopTest {

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
    }

    @Test
    public void test() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "spring.xml");
        IPersonAop aop = (IPersonAop) ctx.getBean("personAopImpl");
        try {
            aop.sayHello2();
            aop.sayHello();
        } catch (Throwable e) {
        }

        Address addr1 = new Address(12, "-01");
        Address addr2 = new Address(13, "-04");
        List<Address> addr = new ArrayList<Address>();
        addr.add(addr1);
        addr.add(addr2);
        PersonInfo info = new PersonInfo();
        info.setAddressList(addr);
        info.setName("testname");
        List<String> mobile = new ArrayList<String>();
        mobile.add("222222");
        mobile.add("333333");
        info.setMobilePhone(mobile);
        aop.createPersonInfo(info, 40);

        aop.getPersonInfo(18);
    }
}
=================================================================
// this is proxy class, it is important
package com.spring.test.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class PersonAopProxy {

    @Before("execution (* com.spring.test.aop..*.*(..))")
    public void logBeforeMethodRunning(JoinPoint joinPoint) {
        StringBuilder sb = new StringBuilder();
        sb.append("--- Begin to call "
                + joinPoint.getTarget().getClass().getName() + "."
                + joinPoint.getSignature().getName() + "() ");

        Object[] objs = joinPoint.getArgs();
        if (objs == null || objs.length == 0)
            sb.append("Without Parameter");
        else {
            int i = 0;
            while (i < objs.length) {
                sb.append(" para" + (i + 1) + "["
                        + objs[i].getClass().getSimpleName() + "]=" + objs[i]);
                i++;
            }
        }

        System.out.println(sb.toString());
    }

    @AfterReturning(pointcut = "execution (* com.spring.test.aop..*.*(..))", returning = "result")
    public void logAfterMethodRunning(JoinPoint joinPoint, Object result)
            throws Throwable {

        StringBuilder sb = new StringBuilder();

        sb.append("--- End to call "
                + joinPoint.getTarget().getClass().getName() + "."
                + joinPoint.getSignature().getName() + "() ");

        Object[] objs = joinPoint.getArgs();
        if (objs == null || objs.length == 0)
            sb.append("Without Parameter");
        else {
            int i = 0;
            while (i < objs.length) {
                sb.append(" para" + (i + 1) + "["
                        + objs[i].getClass().getSimpleName() + "]=" + objs[i]);
                i++;
            }
        }
        sb.append(" <return> " + result);
        System.out.println(sb.toString());
    }

    @AfterThrowing(pointcut = "execution (* com.spring.test.aop..*.*(..))", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        StringBuilder sb = new StringBuilder();

        sb.append("--- End to call "
                + joinPoint.getTarget().getClass().getName() + "."
                + joinPoint.getSignature().getName() + "() ");

        Object[] objs = joinPoint.getArgs();
        if (objs == null || objs.length == 0)
            sb.append("Without Parameter");
        else {
            int i = 0;
            while (i < objs.length) {
                sb.append(" para" + (i + 1) + "["
                        + objs[i].getClass().getSimpleName() + "]=" + objs[i]);
                i++;
            }
        }
        sb.append(" <Exception> " + e);
        System.out.println(sb.toString());
    }
}


Before Advices
To create a before advice to handle crosscutting concerns before particular program execution points,you use the @Before annotation and include the pointcut expression as the annotation value.
@Before("execution(* *.*(..))")
 
This pointcut expression (* com.spring.test.aop..*.*(..) matches the any method execution under package com.spring.test.aop and its sub-package.The preceding wildcard in this expression matches any modifier (public, protected, and private) and any return type. The two dots in the argument list match any number of arguments.

To register this aspect, you just declare a bean instance of it in the IoC container. The aspect bean may even be anonymous if there’s no reference from other beans.
<bean id="personAopProxy" class="com.spring.test.aop.PersonAopProxy" />

After Advices
An after advice is executed after a join point finishes, whenever it returns a result or throws an exception
abnormally.
@After("execution(* *.*(..))")


After Returning Advices
An after advice is executed regardless of whether a join point returns normally. If you want to perform logging only when a join point returns, you should replace the after advice with an after returning advice.
@AfterReturning("execution(* *.*(..))")
or
@AfterReturning(pointcut = "execution(* *.*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result)

After Throwing Advices
An after throwing advice is executed only when an exception is thrown by a join point.
@AfterThrowing("execution(* *.*(..))")
or
@AfterThrowing(pointcut = "execution(* *.*(..))", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Throwable e)

Around Advices
It is the most powerful of all the advice types. It gains full control of a join point, so you can combine all the actions of the preceding advices into one single advice. You can even control when, and whether, to proceed with the original join point execution.
The following around advice is the combination of the before, after returning, and after throwing advices you created before. Note that for an around advice, the argument type of the join point must be ProceedingJoinPoint. It’s a subinterface of JoinPoint that allows you to control when to proceed with the original join point.

@Around("execution(* *.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
       log.info("The method " + joinPoint.getSignature().getName()
                   + "() begins with " + Arrays.toString(joinPoint.getArgs()));
       try {
                Object result = joinPoint.proceed();
                log.info("The method " + joinPoint.getSignature().getName()+ "() ends with " + result);
                return result;
        } catch (IllegalArgumentException e) {
                log.error("Illegal argument " + Arrays.toString(joinPoint.getArgs())
                              + " in "+joinPoint.getSignature().getName() + "()");
            throw e;
        }
}


Reference Book
Apress.Spring.Enterprise.Recipes.A.Problem.Solution.Approach, written by Gary Mak and Josh Long

No comments:

Post a Comment