sayi.github.com icon indicating copy to clipboard operation
sayi.github.com copied to clipboard

面向切面编程(一)之AOP和代理模式

Open Sayi opened this issue 7 years ago • 0 comments

什么是AOP

AOP全称为Aspect Oriented Programming。

AOP的关注点是在切面,它的核心单元是Aspect,面向对象编程中核心单元是类Class。

AOP为我们从切面角度去解决一类问题,比如数据库事务、日志、安全性等。

AOP Alliance

Java许多通用技术都有一套规范,AOP也不例外。AOP Alliance定义了一组AOP技术的基础API,所有实现AOP功能的框架都应该遵守这个约定。

org.aopalliance.aop包定义了一个最通用的接口Advice,拦截器都集成于它。

package org.aopalliance.aop;
public interface Advice {
}

org.aopalliance.intercept包定义了拦截机制所需要的一些列接口,主要分为两类,一类是拦截器Interceptor,一类是连接点Joinpoint,下表是官方的javadoc:

拦截器接口 功能
Interceptor This interface represents a generic interceptor.
MethodInterceptor Intercepts calls on an interface on its way to the target.
ConstructorInterceptor Intercepts the construction of a new object.
FieldInterceptor Intercepts field access on a target object.
连接点接口 功能
Joinpoint This interface represents a generic runtime joinpoint (in the AOP terminology).
Invocation This interface represents an invocation in the program.
MethodInvocation Description of an invocation to a method, given to an interceptor upon method-call.
ConstructorInvocation Description of an invocation to a constuctor, given to an interceptor upon construtor-call.
FieldAccess This interface represents a field access in the program.

连接点可以获取到切入点的信息,比如具体切入的方法等。拦截器是用户自己定义,通过Joinpoint的procced方法执行原先的代码或者指向执行下一个拦截器链。一个可能的拦截器代码如下:

 class DebuggingInterceptor implements MethodInterceptor, 
     ConstructorInterceptor, FieldInterceptor {

   Object invoke(MethodInvocation i) throws Throwable {
     debug(i.getMethod(), i.getThis(), i.getArgs());
     return i.proceed();
   }

   Object construct(ConstructorInvocation i) throws Throwable {
     debug(i.getConstructor(), i.getThis(), i.getArgs());
     return i.proceed();
   }
 
   Object get(FieldAccess fa) throws Throwable {
     debug(fa.getField(), fa.getThis(), null);
     return fa.proceed();
   }

   Object set(FieldAccess fa) throws Throwable {
     debug(fa.getField(), fa.getThis(), fa.getValueToSet());
     return fa.proceed();
   }

   void debug(AccessibleObject ao, Object this, Object value) {
     ...
   }
 }

代理模式

看完AOP规范,我们考虑如何来实现AOP,答案是代理模式:代理模式通过代理类对真实对象进行代理,这样我们就可以在真实对象的调用前后,执行自己的代码,这就是切面。

image

JDK动态代理

Java提供了java.lang.reflect.Proxy.newProxyInstance静态方法实现代理模式,这种代理叫做动态代理。

ApectJ提供了语言方面的支持,它是Java语言的一个扩展。静态代理模式是在编码过程中就已明确了代理结构,这里不作讨论。

public static Object newProxyInstance(ClassLoader loader,
  Class<?>[] interfaces,
  InvocationHandler h)
throws IllegalArgumentException
  • 参数ClassLoader定义了类加载器,可以通过Thread.currentThread().getContextClassLoader()指定为当前加载器
  • 参数interfaces限定了代理接口,所以JDK动态代理只支持接口,不支持没有类
  • 参数InvocationHandler则是具体需要实现的调用处理器,他的定义如下:
public interface InvocationHandler {
 public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
}

其中proxy是代理对象,它的名称可能是$Proxy1,method则是具体要拦截的方法,args是方法的参数。

Cglib动态代理

如果需要对类代理,我们可以使用字节码工具,比如cglib,Spring的AOP也是利用了cglib对类进行代理。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new net.sf.cglib.proxy.MethodInterceptor() {
   
   @Override
   public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
       throws Throwable {
     
   }
});
Object instance = enhancer.create();
  • 参数Obj表示代理对象,它的名称可能是这样的:XXXService$$EnhancerByCglib$9e453df32
  • method是被拦截的方法
  • args是参数
  • MethodProxy可以调用未拦截的方法

注意:cglib是基于继承特性,所以无法对final的类或者final的方法进行代理

javassist动态代理

javassit是另一个简单的字节码框架,它生产动态代理也很简单,下面直接给出代码:

ProxyFactory f = new ProxyFactory();
f.setSuperclass(key.getType());
Class<?> c = f.createClass();

Object object = c.newInstance();
((javassist.util.proxy.Proxy)object).setHandler(new MethodHandler() {
  
  @Override
  public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args)
      throws Throwable {
    
  }
});
return object;

手动实现一个AOP框架

理解了代理模式后,我们就可以实现一个自己的AOP框架了。

准备设计一个代理工厂用来生成代理对象,封装JDK动态代理和Cglib动态代理,拦截器链的调用则是通过连接点JoinPoint实现的,因为我们主要实现对方法的拦截,所以我们会实现接口MethodInvocation,通过proceed方法实现拦截器的逐一调用。

实现ProxyFactory

直接看源码,提供了两个构造器,一个是对接口的代理,一个对对象的代理,然后提供一个getProxy方法获得代理对象,具体代理策略参见getProxy方法。

package com.deepoove.liteinject.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;

import org.aopalliance.intercept.MethodInterceptor;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;

public class ProxyFactory {
  
  private Object target;
  private Class<?> interfaceClass;
  
  private List<MethodInterceptor> advisor;
  
  public ProxyFactory(Class<?> clazz, MethodInterceptor... interceptors) {
    this.interfaceClass = clazz;
    if (null != interceptors) this.advisor = Arrays.asList(interceptors);
  }
  
  
  public ProxyFactory(Object obj, MethodInterceptor... interceptors) {
    this.target = obj;
    if (null != interceptors) this.advisor = Arrays.asList(interceptors);
  }
  
  
  public Object getProxy(){
     if (null != interfaceClass && interfaceClass.isInterface()) {
       //jdk dynamic proxy
       return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{interfaceClass}, new InvocationHandler() {
         @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           ReflectMethodInvocation methodInvocation = new ReflectMethodInvocation(method, args,  null, new Pointcut(), advisor);
           return methodInvocation.proceed();
         }
       });
     }else{
       //bytecode dynamic proxy
       Enhancer enhancer = new Enhancer();
       enhancer.setSuperclass(target.getClass());
       enhancer.setCallback(new net.sf.cglib.proxy.MethodInterceptor() {
         
         @Override
         public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
             throws Throwable {
           CglibMethodInvocation  methodInvocation = new CglibMethodInvocation(method, args, target, proxy,  new Pointcut(), advisor);
           return methodInvocation.proceed();
         }
       });
       return enhancer.create();
       
     }
  }
}

实现ReflectMethodInvocation和CglibMethodInvocation

ReflectMethodInvocation和CglibMethodInvocation都是对接口MethodInvocation的实现,他们的代码大同小异,这里看下ReflectMethodInvocation的代码。

package com.deepoove.liteinject.aop;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.List;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class ReflectMethodInvocation implements MethodInvocation {

  private Object obj;
  private Pointcut pointcut;
  private List<MethodInterceptor> interceptors;

  private Method method;
  private Object[] args;
  
  private int i = 0;

  public ReflectMethodInvocation(Method method, Object[] args, Object newInstance,
      Pointcut pointcut, List<MethodInterceptor> interceptors) {
    this.method = method;
    this.args = args;
    this.obj = newInstance;
    this.pointcut = pointcut;
    this.interceptors = interceptors;
  }

  @Override
  public Object[] getArguments() {
    return args;
  }

  @Override
  public Object proceed() throws Throwable {
    if (!pointcut.matcher(method)) return method.invoke(obj, args);
    
    if (i == interceptors.size()) return method.invoke(obj, args);
    
    return interceptors.get(i++).invoke(this);
  }

  @Override
  public Object getThis() {
    return obj;
  }

  @Override
  public AccessibleObject getStaticPart() {
    return null;
  }

  @Override
  public Method getMethod() {
    return null;
  }

}

它的核心方法就是proceed,实现了拦截器链的调用,在方法的执行前后,依次执行拦截器前置方法代码,再执行具体方法,再反过来依次执行拦截器后置方法代码。

关于Pointcut类这里无需关注,它代表一个切入点,表示是否需要拦截,我们姑且认为pointcut.matcher(method)会返回True,即拦截所有方法。

接下来,我们可以写个单元测试来验证下,先对一个接口动态代理,然后对一个对象进行代理:

@Test
public void testProxyFactory() {
  
  UserService proxy = (UserService)new ProxyFactory(UserService.class, new MethodInterceptor() {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("jdk AOP .");
      return null;
    }
  }).getProxy();
  
  System.out.println(proxy.get(""));
  
  UserService userService = new LiteService();
  UserService proxy2 = (UserService)new ProxyFactory(userService, new MethodInterceptor() {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("cglib AOP Before.");
      Object proceed = invocation.proceed();
      System.out.println("cglib AOP After.");
      return proceed;
    }
  }).getProxy();
  
  User user = proxy2.get("id");
  System.out.println(user.getName());
}

输出结果如下:

jdk AOP .
null
cglib AOP Before.
cglib AOP After.
Sayi

至此,我们已经完成了一个符合AOP规范的AOP框架。

为DI框架增加AOP功能

AOP作为依赖注入框架的完善,很多DI框架都提供了AOP的功能,回到以前的一篇文章《依赖注入(一)实现一个简单的DI框架》,我们为lite-inject框架实现AOP功能。

在DI实现AOP,主要需要做到两步,指定哪些类,哪些方法(切入点)需要切面操作,并且绑定到具体的拦截器,其次就是在Bean生成的时候,可以生成代理类。

切入点的绑定

首先我们定义一个切入点类,表示哪些类,哪些用了注解的类,哪些用了注解的方法可以被切入,类名是Pointcut。

package com.deepoove.liteinject.aop;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Pointcut {

  private List<Class<?>> clazzes = new ArrayList<>();
  private List<Class<? extends Annotation>> clazzAnnotationes = new ArrayList<>();;

  private List<Class<? extends Annotation>> methodAnnotationes = new ArrayList<>();;

  public Pointcut() {}

  public Pointcut clazz(Class<?> clazz) {
    clazzes.add(clazz);
    return this;
  }

  public Pointcut clazzOrAnnotatedWith(Class<? extends Annotation> anno) {
    clazzAnnotationes.add(anno);
    return this;
  }

  public Pointcut methodAnnotatedWith(Class<? extends Annotation> clazz) {
    methodAnnotationes.add(clazz);
    return this;
  }

  public boolean matcher(Class<?> clazz) {
    if (null != clazzes && -1 != clazzes.indexOf(clazz)) return true;
    if (null == clazzAnnotationes || clazzAnnotationes.isEmpty()) return false;
    Annotation[] annotations = clazz.getAnnotations();
    if (null == annotations || annotations.length == 0) return false;
    return new ArrayList<>(clazzAnnotationes).retainAll(Arrays.asList(annotations));
  }

  public boolean matcher(Method method) {
    if (null == methodAnnotationes || methodAnnotationes.isEmpty()) return true;
    Annotation[] annotations = method.getAnnotations();
    if (null == annotations || annotations.length == 0) return false;
    return new ArrayList<>(methodAnnotationes).retainAll(Arrays.asList(annotations));
  }

}

定义好切入点,我们就可以在InjectModule中绑定切入点和拦截器了:

bindInterceptor(new Pointcut().clazz(LoginService.class).clazzOrAnnotatedWith(Log.class).methodAnnotatedWith(Log.class), new MethodInterceptor() {
  
  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    System.out.println("AOP Before.");
    Object proceed = invocation.proceed();
    System.out.println("AOP After.");
    return proceed;
  }
});

bindInterceptor(new Pointcut().clazz(LoginServiceImpl.class).methodAnnotatedWith(Log.class), new MethodInterceptor() {
  @Override
  public Object invoke(MethodInvocation invocation) throws Throwable {
    System.out.println("cgLib AOP Before.");
    Object proceed = invocation.proceed();
    System.out.println("cgLib AOP After.");
    return proceed;
  }
});

第一步代码的切入点是接口LoginService或者使用了注解@Log的类,方法是使用了注解@Log的方法; 第二步代码的切入点是类LoginServiceImpl,方法是使用了注解@Log的方法。

为Bean生成代理

在生成Bean的时候,我们就可以对先前写的AOP框架进行改造,生成代理了。

Object createAopProxy(Key<?> key, Object newInstance, Map<Pointcut, List<MethodInterceptor>> advisor) {
  for (Entry<Pointcut, List<MethodInterceptor>> entry : advisor.entrySet()) {
    Pointcut pointcut = entry.getKey();
    List<MethodInterceptor> interceptors = entry.getValue();
    if (pointcut.matcher(key.getType())){
      if (key.getType().isInterface()) {
        // jdk proxy
        return getJdkProxyInstance(key, newInstance, pointcut, interceptors);
      } else {
        //cglib proxy
        return getCgLibProxyInstance(key, newInstance, pointcut, interceptors);  
      }
    }
  }
  return newInstance;
}

从现在开始lite-inject也是一个专注于DI和AOP的小型框架了。

总结

技术的实现是百花齐放,所以一定要有约定,AOP盟约统一了AOP实现的规范,理解这个规范对理解AOP很有好处。

下篇文章将会对Spring AOP和Guice AOP的源码进行解读,待续。

Sayi avatar Jul 27 '18 02:07 Sayi