AOP + 动态代理 + Proxy模式

大二学生党,理解深度广度不够,敬请谅解。。。。最近在JFinal的AOP学习中有所收获。

一:什么是AOP

AOP:Aspect-Oriented Programming,可以理解为面向切面编程,是对OOP的一种补充。
首先先看看MVC模式,Model + Controller + View,在熟悉不过,其实在JavaWeb中可以细分为下面几层

  • 最上层一层当然是View层了,没啥好说的
  • 用户对View做出交互操作后,View传递一个Action给Controller层
  • Controller层对Action继续进行分发,向下给Service层
  • Service继续向下,给DAO层(Data Access Object)

注意以下几点:

  • Service层与DAO层都是属于Model层的
  • DAO层是对数据库最基本的操作,例如插入一条数据
  • 如果把所有的业务逻辑操作(例如登陆这个操作)操作都放在Controller,就会使Controller臃肿,不易维护,所以Service就承当了一部分的任务,Controller基本就几行代码(显示最基本的逻辑),对DAO层的操作都会放在Service层中。


这时候,AOP就要登场了,它就像一个切面插在这些层面之间,进行你想进行的一系列操作。

二:动态代理(Dynamic Proxy)

首先看一段bad code

public interface Calculator {
     void calculate(int a, int b);
}
public class CalculatorImpl implements Calculator {
   @Override
    public void calculate(int a, int b) {
        before();
        System.out.println(a + b);
        after();
    }
    private void before() {
        System.out.println("Before");
    }

    private void after() {
        System.out.println("After");
    }
}

为什么说这些代码写得差呢?把before()after()方法写死在calculate()里面,如果有许多方法要进行这样的before()``after(),那每个函数都要改,显然是不可能的(例如统计一个函数运行时间)。

这样,比较简单就是动态代理。

这2个类还是不变的

public interface Calculator {
     void calculate(int a, int b);
     void calculate2(int a, int b); // 测试用啦,其实都一样
}
public class CalculatorImpl implements Calculator {
    @Override
    public void calculate(int a, int b) {
        System.out.println(a + b);
    }

    @Override
    public void calculate2(int a, int b) {
        System.out.println(a + b);
    }
}

下面就是重点:
首先看一哈JDK里面的一个接口java.lang.reflect.InvocationHandler
进入源码:

/** * {@code InvocationHandler} is the interface implemented by * the <i>invocation handler</i> of a proxy instance. * * <p>Each proxy instance has an associated invocation handler. * When a method is invoked on a proxy instance, the method * invocation is encoded and dispatched to the {@code invoke} * method of its invocation handler. * * 每一个proxy instance都会与一个invocation handler相关联。 * 当proxy instance调用一个方法时,这个方法就会被相关联的 * invocation handler的invoke()调用 */
 public interface InvocationHandler {
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
 }

我们实现这个接口:

public abstract class AroundAdvice implements InvocationHandler {
    private Object targetObject;

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

    /** * @param method 被代理的接口的方法 * @param args 被代理的接口的方法的参数列表 */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        handleBefore(targetObject, method, args);
        Object result = method.invoke(targetObject, args);
        handleAfter(targetObject, method, args);
        return result;
    }

    public Object getProxy() {
        return Proxy.newProxyInstance
                (
                        targetObject.getClass().getClassLoader(),
                        targetObject.getClass().getInterfaces(), //需要实现的接口
                        this
                );
    }

    public abstract void handleBefore(Object targetObject, Method method, Object[] args);

    public abstract void handleAfter(Object targetObject, Method method, Object[] args);
}

注意以下几点:

  • invoke(Object proxy, Method method, Object[] args)的每个参数的含义(若觉得我写的不清楚,去看源码)
  • Object result = method.invoke(targetObject, args)使用反射,调用method,关于method到底是那个方法,后文再说。
  • 对于getProxy()方法,返回一个ProxyProxy.newProxyInstance为JDK自带的方法。
public static Object newProxyInstance
(
        ClassLoader loader,  // 类加载器
        Class<?>[] interfaces, // 需要实现的接口
        InvocationHandler h // 一个InvocationHandler对象
)
        throws IllegalArgumentException

最后:

public class AroundAdviceImpl extends AroundAdvice {
    @Override
    public void handleBefore(Object targetObject, Method method, Object[] args) {
        System.out.println("Before");
    }

    @Override
    public void handleAfter(Object targetObject, Method method, Object[] args) {
        System.out.println("After");
    }
}

调用的时候这样就可以了:

public class Main {
    public static void main(String args[]) {
        AroundAdvice aop = new AroundAdviceImpl();
        aop.setTargetObject(new CalculatorImpl());

        Calculator calcImpl = (Calculator) aop.getProxy();

        calcImpl.calculate(1, 2);
        calcImpl.calculate2(1, 35);
    }
}

aop.getProxy()产生一个Proxy通过代理调用calculate(),而不是直接调用Calculatorcalculate()方法。
输出如下:

Before
3
After
Before
36
After

也就是说AroundAdviceImplCalculator的代理,对于Calculator的每个方法都进行一个封装(先输出Before,在输出After)。此时,不就是AOP的雏形吗?插入一个切片在一层模型上面或者下面(这里可不能算一层,只是一个例子)。

当然动态代理也有缺陷:newProxyInstance()函数第二个参数要是一个接口。(所以有个框架Retrofit就是用的这种技术)

顺便说一哈Jfinal里面的AOP,Jfinal使用Intercepter来对Controller层和Service层进行切面处理,@Before 与 @Clear 来标识要处理的函数。

三:代理模式

  • AbstractObject:目标对象与代理对象的统一接口,这样使用目标对象的地方就可以使用代理对象
  • RealObject:目标对象,可以是一个接口,抽象类或具体实现类
  • ProxyObject:代理对象,通过代理对象的方法调用目标对象的方法
    具体实现就是上文中实现啦。

四:注解+AOP

Jfinal里面是使用@Before + Intercepter来实现AOP的,下面我们也来简单实现。

AOP的注解声明(对于注解不熟悉的看注解基本)

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AOP { Class<? extends AroundAdvice> value(); }
  • 由于技术水平限制,只考虑ElementType.METHOD
  • 该注解是在运行时起作用的
  • 注解需要的参数在后面在介绍

首先我们看看怎么使用这个注解:

public class CalculatorImpl implements Calculator {
    @AOP(AroundAdviceImpl.class)
    @Override
    public void calculate(int a, int b) {
        System.out.println(a + b);
    }
}

只需要在calculate方法上面加上@AOP(AroundAdviceImpl.class)。参数AroundAdviceImpl.class就是当调用calculate方法之前或者之后,先去调用AroundAdviceImpl.class里面的方法。

前文说到,注解需要的参数是一个AroundAdvice接口或者其子类。对于AroundAdvice

//handler每个参数后面再解释
public interface AroundAdvice {
    void handle(Class<?> targetObject, Method method, Parameter[] args);
}

对于AroundAdvice的一个实现类AroundAdviceImpl,也就是CalculatorImpl注解具体传入的参数:

public class AroundAdviceImpl implements AroundAdvice {
    @Override
    public void handle(Class<?> targetObject, Method method, Parameter[] args) {
        try {
            System.out.println("Before");
            //Todo :具体的参数并不是在这里传进来的
            //method.invoke(targetObject.newInstance(), args[0], args[1]);
            method.invoke(targetObject.newInstance(), 1, 3);
            System.out.println("After");
        } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }


    }
}
  • 首先要注意,method.invoke(targetObject.newInstance(), 1, 3);就是在调用calculate,具体为什么,后面再说。
  • AroundAdviceImpl就是一个Intercepter,在对calculate之前调用System.out.println("Before");,在对calculate之后调用System.out.println("After");

对注解有所了解朋友,明白注解实则是Java反射机制,下面我们就看看注解处理器(Annotation Processor)。

public class Processor {
    /** * 调用clazz注解中类的handler()方法 * * @param clazz 存在@AOP()注解的类 */
    public static void process(Class<?> clazz) {
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            AOP aop = method.getAnnotation(AOP.class);
            if (aop != null) {
                Method targetMethod;
                //@AOP()中的类 即 AroundAdviceImpl.class
                Class<? extends AroundAdvice> targetClass = aop.value();
                Parameter[] parameters = method.getParameters();
                try {
                    targetMethod = targetClass.getDeclaredMethod("handle", Class.class, Method.class, Parameter[].class);
                    targetMethod.invoke(targetClass.newInstance(), clazz, method, parameters);

                    System.out.println("Processor " + targetMethod);
                    System.out.println("Processor " + targetClass);
                    System.out.println("Processor " + clazz);
                    System.out.println("Processor " + method);
                    System.out.println("Processor " + Arrays.toString(parameters));

                } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException e) {
                    e.printStackTrace();
                }
            }

        }

    }
}

里面变量这些反射,光说的话是不好明白的。等哈在说,这段代码,先看最终的调用:

public class Main {
    public static void main(String args[]) {
        Processor.process(CalculatorImpl.class);
    }
}

那好,我们运行一哈,看看Processer里面的参数是啥子意思:

Processor  public void aop.AroundAdviceImpl.handle(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Parameter[])
Processor  class aop.AroundAdviceImpl
Processor  class aop.CalculatorImpl
Processor  public void aop.CalculatorImpl.calculate(int,int)
Processor  [int arg0, int arg1]
  • targetMethod:就是指拦截器的handler方法
  • targetClass:就是我们所谓的拦截器AroundAdviceImpl
  • clazzCalculatorImpl,也就是原始的类,我们希望进行AOP的方法所属的对象
  • methodCalculatorImpl.calculate(int,int),我们希望进行AOP的方法
  • parametersCalculatorImpl.calculate(int,int)的参数

所以,在handle(Class<?> targetObject, Method method, Parameter[] args)方法里面三个参数都是从上面5个参数中传进来的。其类型是:

  • targetObject:一个CalculatorImpl对象,就是我们希望进行AOP的方法所属的对象
  • method:CalculatorImpl.calculate(int,int),我们希望进行AOP的方法
  • argsCalculatorImpl.calculate(int,int)的参数

这样,一个依靠注解的AOP就简单实现了。有下面几个问题,我觉得不是很妥:

  • method.invoke(targetObject.newInstance(), 1, 3);其实真正的参数不是在这里实现的,可以在Processor.process()这个函数里面传值,然后再通过targetMethod.invoke(targetClass.newInstance(), clazz, method, parameters);传到handle()里面。
  • 所有的method.invoke()的第一个参数,应该是method所属的一个实例对象,但是我是传递的时候使用了newInstance(),我感觉这样不是很合理,应该传被实例化过的对象。
  • Processor估计是CalculatorImpl的代理,感觉是,其实我也不是很清楚。。。。。建议大家去看看jFinal的InterceptorManager + @Before 的源码,他写的确实好。而且本文写的程序并没有实际的用途。

你可能感兴趣的:(AOP,mvc)