Свое АОP в JDK

Заметка о том, как можно реализовать AOP без Spring и AspectJ. Для тех, кто не особо в курсе, что такое AOP смотреть суда. Итак, приступим. Создадим нашу мини программу:

public interface Calculator {
    public int calculate( int a , int b);
}

public class CalculatorImpl implements Calculator {
    @Override
    public int calculate(int a, int b) {
        System.out.println("**********Actual Method Execution**********");
        return a/b;
    }
}

Класс Calculator будет имеенно тем классом, который мы будем проксировать. В java есть такой интересный интерфейс InvocationHandler, его мы и будем использовать для нашей реализации AOP. Создадим абстрактный Handler:

public abstract class AbstractHandler implements InvocationHandler {

    private Object targetObject;

    public void setTargetObject(Object targetObject) {
        this.targetObject = targetObject;
    }

    public Object getTargetObject() {
        return targetObject;
    }
}

Создадим ProxyFactory:

public class ProxyFactory {

public static Object getProxy(Object targetObject,
        List<AbstractHandler> handlers) {
    Object proxyObject = null;
    if (handlers.size() > 0) {
        proxyObject = targetObject;
        for (int i = 0; i < handlers.size(); i++) {
            handlers.get(i).setTargetObject(proxyObject);
            proxyObject = Proxy.newProxyInstance(targetObject.getClass()
                    .getClassLoader(), targetObject.getClass()
                    .getInterfaces(), handlers.get(i));
        }
        return proxyObject;
    } else {
        return targetObject;
    }
}
}

В AOP существует несколько срезов: Before, After, AfterThrowing, AfterReturning и Around.

Так как реализации каждого среза могут быть разными, для разных случаев, создадим для них абстрактные классы:

Пример для AfterHandler и BeforeHandler.

public abstract class AfterHandler extends AbstractHandler {

    /**
     * Handles after the execution of method.
     *
     * @param proxy the proxy
     * @param method the method
     * @param args the args
     */
    public abstract void handleAfter(Object proxy, Method method, Object[] args);

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object result = method.invoke(getTargetObject(), args);
        handleAfter(proxy, method, args);
        return result;
    }
}

AbstractBeforeHandler:

public abstract class BeforeHandler extends AbstractHandler {

    /**
     * Handles before execution of actual method.
     *
     * @param proxy the proxy
     * @param method the method
     * @param args the args
     */
    public abstract void handleBefore(Object proxy, Method method, Object[] args);

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        handleBefore(proxy, method, args);
        return method.invoke(getTargetObject(), args);
    }
}

Теперь нам нужно сделать конкретные реализации для каждого из срезов:

public class AfterHandlerImpl extends AfterHandler {

    @Override
    public void handleAfter(Object proxy, Method method, Object[] args) {
        //Provide your own cross cutting concern
        System.out.println(method.getName() + Arrays.toString(args));
        System.out.println("Handling after actual method execution ........");
    }
}

public class BeforeHandlerImpl extends BeforeHandler {

    @Override
    public void handleBefore(Object proxy, Method method, Object[] args) {
        //Provide your own cross cutting concern
        System.out.println("Handling before actual method execution ........");
    }
}

Теперь мы можем легко проксировать наш Calculator класс:

public class TestAopInJDK {

    public static void main(String[] args) {
        CalculatorImpl calcImpl = new CalculatorImpl();
        BeforeHandler before = new BeforeHandlerImpl();
        AfterHandler after = new AfterHandlerImpl();
        List<AbstractHandler> handlers = new ArrayList<AbstractHandler>();
        handlers.add(before);
        handlers.add(after);
        Calculator proxy = (Calculator) ProxyFactory.getProxy(calcImpl,
                handlers);
        int result = proxy.calculate(20, 10);
        System.out.println("FInal Result :::" + result);
    }
}

После запуска вывод в консоль оказывается таким:

Handling before actual method execution ........
**********Actual Method Execution**********
calculate[20, 10]
Handling after actual method execution ........
Final Result :::2

Как вы можете заметить срабатывает Before, затем идет работа метода и затем срабатывает After. P.S. Многие кто не сильно в теме могут сказать, что такое же можно было реализовать просто написав перед вызовом и после вызова sysout. Да, можно было, но AOP предназначено немного для других целей.Я уже описывал то как мы используем логер для действий вебдрайвера здесь. В своих фреймворках, я дополнительно логирую имена методов и параметры которые он принимают используя AOP. Это позволяет хранить весь код логирования в одном месте,а не розмазывать его по всем классам.