AOP + 动态代理 + Proxy模式

动机

在后端编程中接触到了拦截器,用户权限的验证,统计一个函数的运行时间,这些都可以拦截器来实现,拦截器是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 + 动态代理 + Proxy模式_第1张图片
这时候,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 invocation handler of a proxy instance.
 *
 * 

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 proxy  被代理的对象(也就是targerObject)
     * @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 来标识要处理的函数。

代理模式

AOP + 动态代理 + Proxy模式_第2张图片

  • AbstractObject:目标对象与代理对象的统一接口,这样使用目标对象的地方就可以使用代理对象。(上述列子的Calculator
  • RealObject:目标对象,可以是一个接口,抽象类或具体实现类。(上述例子的CalculatorImpl
  • ProxyObject:代理对象,通过代理对象的方法调用目标对象的方法,持有一个RealObject的引用。(上述例子的AroundAdvice

这样,我们在使用RealObject里面的方法的时候,就不会去直接操作RealObject。由ProcyObject代表RealObject来进行操作。(或者说:而是将其委托给ProcyObject来进行。)(我的理解)
而动态代理,就是在运行的时候,进行代理。而不是在编译的时候就确定了。

注解实现AOP

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

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

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

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

public class CalculatorImpl implements Calculator {
    @AOP(AroundAdviceImpl.class)
    @Override
    public void calculate(int a, double 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, Object... args);
}

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

public class AroundAdviceImpl implements AroundAdvice {
    @Override
    public void handle(Class targetObject, Method method,  Object... args) {
        try {
            System.out.println("Before");
            method.invoke(targetObject.newInstance(), args);
            System.out.println("After");
        } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
            e.printStackTrace();
        }


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

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

public class Processor {
    /**
     * 调用clazz注解中的类的handler()方法
     *
     * realClass 要被代理的类,存在@AOP()注解的类
     * proxyClass 代理类,即AOP注解里面的那个类
     * targetMethod 要被代理类调用的方法,加AOP注解的方法
     * handleMethod handle方法,即AroundAdvice接口的方法
     * args targetMethod的参数
     */
    public static void process(Class realClass, Object... args) {

        Method[] methods = realClass.getMethods();
        for (Method targetMethod : methods) {
            // 得到加上了AOP注解的函数
            AOP aop = targetMethod.getAnnotation(AOP.class);
            if (aop != null) {
                Method handleMethod;
                //@AOP()中的类 即 AroundAdviceImpl.class
                Class proxyClass = aop.value();
                try {
                    handleMethod = proxyClass.getDeclaredMethod("handle", Class.class, Method.class, args.getClass());
                    handleMethod.invoke(proxyClass.newInstance(), realClass, targetMethod, args);

                    System.out.println("Processor  " + handleMethod);
                    System.out.println("Processor  " + proxyClass);
                    System.out.println("Processor  " + realClass);
                    System.out.println("Processor  " + targetMethod);

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



    // Run
    @Test
    public void test() {
        Processor.process(CalculatorImpl.class, 12, 3.0);
    }

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

15.0
aop.CalculatorImpl#calculate run in 2ms
Processor  public void aop.AroundAdviceImpl.handle(java.lang.Class,java.lang.reflect.Method,java.lang.Object[])
Processor  class aop.AroundAdviceImpl
Processor  class aop.CalculatorImpl
Processor  public void aop.CalculatorImpl.calculate(int,double)

在上面注释就已经解释了各个参数的意义,大家可以去看看。

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

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

AroundAdviceImplCalculatorImpl的代理类,Processor是注解处理器。

问题

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

  • targetMethod.invoke(targetClass.newInstance(), clazz, method, parameters);传到handle()里面。
  • 所有的method.invoke()的第一个参数,应该是method所属的一个实例对象,但是我是传递的时候使用了newInstance(),我感觉这样不是很合理,应该传被实例化过的对象。

还有一个问题:
ProxyDelegate2中模式不是很清楚。

你可能感兴趣的:(Java)