Sayi / sayi.github.com

个人博客已切换到公众号Evoopeed,请搜索:deepoove
44 stars 7 forks source link

面向切面编程(二)之Spring和Guice #40

Open Sayi opened 6 years ago

Sayi commented 6 years ago

AOP是依赖注入框架的一个完善,本文将会对Spring和Guice的AOP部分进行详细分析。

Spring AOP

我们直接看看AOP的使用示例。

  1. 写一个服务接口和实现
    
    public interface PersonService {
    String get();
    }

public class PersonServiceImpl implements PersonService { @Log @Override public String get() { System.out.println("execute get method"); return "Sayi"; } }

  @Log表明这个方法会被切入,使用注解标识了切入点,可以不选择注解切入,直接切入某个类的某个方法,具体配置参见Spring官方文档。
```java
package com.deepoove.diexample.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {

}
  1. 写切面拦截方法,切面类需要用注解@Aspect标识
    
    package com.deepoove.diexample.aop;

import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component;

@Aspect public class LogAspect {

@Around("@annotation(com.deepoove.diexample.annotation.Log)") public Object doLog(ProceedingJoinPoint pjp) throws Throwable { System.out.println(System.currentTimeMillis() + " Log Aspect before"); Object obj = pjp.proceed(); System.out.println(System.currentTimeMillis() + " Log Aspect after"); return obj; }

}

@Around方法会包裹这个方法的执行,同时还提供了@Before方法执行前的注解和@After方法执行后的注解。
3. 使用XML打开切面代理(`<aop:aspectj-autoproxy />`)、配置服务和切面拦截器Bean
```java
<?xml version="1.0" encoding="UTF-8"?>
<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:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop.xsd">

  <aop:aspectj-autoproxy />

  <bean id="personService" class="com.deepoove.diexample.service.PersonServiceImpl">
  </bean>

  <bean id="myAspect" class="com.deepoove.diexample.aop.LogAspect">
  </bean>

</beans>

一切完毕,可以写个单元测试检验下:

@Test
public void testXMLAOPConifg() {
  ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("person.xml");
  PersonService personService = context.getBean(PersonService.class);
  Assert.assertEquals(personService.get(), "Sayi");
  context.close();
}

// 输出结果为:
1533176773600 Log Aspect before
execute get method
1533176773600 Log Aspect after

AOP源码解析

在《依赖注入(二)Spring Dependency injection》文章中详细说明过Bean的初始化过程,我们知道AOP其实是Spring的一个扩展,而BeanPostProcessor的设计为实现这个扩展提供了便捷。关于如何扫描XML配置,如何解析@Around注解生成拦截器Advice这里不作介绍,我们直接看为Bean生成代理的源码AbstractAutoProxyCreator,处理链为先调用postProcessAfterInitialization方法再调用wrapIfNecessary方法,wrapIfNecessary方法核心代码如下:

// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
  this.advisedBeans.put(cacheKey, Boolean.TRUE);
  Object proxy = createProxy(
      bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
  this.proxyTypes.put(cacheKey, proxy.getClass());
  return proxy;
}

this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;

我们对上面代码做个简单解释:

  1. getAdvicesAndAdvisorsForBean就是获取当前Bean对应的拦截器,拦截器包含了切入点和具体拦截的方法,返回值类似为
    InstantiationModelAwarePointcutAdvisor: expression [@annotation(com.deepoove.diexample.annotation.Log)]; advice method [public java.lang.Object com.deepoove.diexample.aop.LogAspect.doLog(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable]; perClauseKind=SINGLETON
  2. 如果拦截器不为NULL,则会创建代理

    protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
    @Nullable Object[] specificInterceptors, TargetSource targetSource) {
    
    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
    AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }
    
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);
    
    if (!proxyFactory.isProxyTargetClass()) {
    if (shouldProxyTargetClass(beanClass, beanName)) {
      proxyFactory.setProxyTargetClass(true);
    }
    else {
      evaluateProxyInterfaces(beanClass, proxyFactory);
    }
    }
    
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);
    
    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
    proxyFactory.setPreFiltered(true);
    }
    
    return proxyFactory.getProxy(getProxyClassLoader());
    }

    可以从源码看出来,生成代理的核心类为ProxyFactory,接下来会详细阐述它的细节。

ProxyFatory编程

Spring为代理模式提供了一个工厂类ProxyFatory,支持对象的DK动态代理和Cglib代理。如果目标对象至少实现了一个接口,那么优先使用JDK动态代理所有接口,否则会使用Cglib,如果需要强制使用Cglib,可以通过配置实现:

<aop:config proxy-target-class="true" />

正如上篇文章说过,cglib无法对final进行代理。

我们先来看看如何使用ProxyFatory编程:

@Test
public void testAOP() {

  ProxyFactory factory = new ProxyFactory(new MyService());
  factory.addAdvice(new MethodInterceptor() {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("AOP Spring: " + invocation.getMethod());
      return invocation.proceed();
    }
  });
  MyService tb = (MyService) factory.getProxy();

  tb.toString();

}

ProxyFactory构造器支持传入Object对象或者接口Class,通过addAdvice方法增加拦截器org.aopalliance.intercept.Interceptor的实现,AopProxy接口定义了获取代理类的方法,获取AopProxy实例的源码在DefaultAopProxyFactory中,如下:

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
  if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
    Class<?> targetClass = config.getTargetClass();
    if (targetClass == null) {
      throw new AopConfigException("TargetSource cannot determine target class: " +
          "Either an interface or a target is required for proxy creation.");
    }
    if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
      return new JdkDynamicAopProxy(config);
    }
    return new ObjenesisCglibAopProxy(config);
  }
  else {
    return new JdkDynamicAopProxy(config);
  }
}

AopProxy有两个实现,分别是JdkDynamicAopProxy和CglibAopProxy,我们可以实现接口,实现自己的代理策略。

ObjenesisCglibAopProxy继承了CglibAopProxy,使用objenesis技术实例化对象,objenesis技术参见官网http://objenesis.org/

深入JdkDynamicAopProxy的实现,我们发现Spring也是通过实现AOP盟约的MethodInvocation,完成对拦截器链的调用,具体实现类是ReflectiveMethodInvocation,构造完ReflectiveMethodInvocation后,通过其核心方法递归遍历拦截器:

public Object proceed() throws Throwable {
  //  We start with an index of -1 and increment early.
  if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
    return invokeJoinpoint();
  }

  Object interceptorOrInterceptionAdvice =
      this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
  if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
    // Evaluate dynamic method matcher here: static part will already have
    // been evaluated and found to match.
    InterceptorAndDynamicMethodMatcher dm =
        (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
    if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
      return dm.interceptor.invoke(this);
    }
    else {
      // Dynamic matching failed.
      // Skip this interceptor and invoke the next in the chain.
      return proceed();
    }
  }
  else {
    // It's an interceptor, so we just invoke it: The pointcut will have
    // been evaluated statically before this object was constructed.
    return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
  }
}

通过源码我们发现,所有JDK动态代理的对象除了代理对象接口外,还实现了接口: SpringProxy.class、Advised.class和DecoratingProxy.class,我们可以利用这一特性,获得更多的代理信息。

ProxyFatory应用之Spring-remoting

RPC框架客户端依赖服务接口,调用远程服务实现,HttpInvoker是Spring的一个基于HTTP协议和Java序列化的远程调用框架。参见《写一个极简的RPC和Hessian的设计 》

Spring Http Invoker客户端的实现原理是基于动态代理,把接口的调用代理至HTTP服务,同时利用了FactoryBean的扩展技术。

public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor implements FactoryBean<Object> {

  @Nullable
  private Object serviceProxy;

  @Override
  public void afterPropertiesSet() {
    super.afterPropertiesSet();
    Class<?> ifc = getServiceInterface();
    Assert.notNull(ifc, "Property 'serviceInterface' is required");
    this.serviceProxy = new ProxyFactory(ifc, this).getProxy(getBeanClassLoader());
  }

  @Override
  @Nullable
  public Object getObject() {
    return this.serviceProxy;
  }

  @Override
  public Class<?> getObjectType() {
    return getServiceInterface();
  }

  @Override
  public boolean isSingleton() {
    return true;
  }

}

AOP应用

Spring中AOP的应用还有很多,比如@Transcation、@Cache等,后面有时间会作为一个主题单独分析。

Guice AOP

Guice AOP的设计相对比较简单:切入点匹配和拦截器绑定。 我们先看一个示例,首先在Module中指定切入点和拦截器(这里采用了官方文档的代码,Matchers类用来生成匹配逻辑或者不匹配逻辑),所有使用了注解NotOnWeekends的方法都将会被WeekendBlocker拦截。

public class NotOnWeekendsModule extends AbstractModule {
  protected void configure() {
    bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), 
        new WeekendBlocker());
  }
}
```java
接着就可以实现拦截器了,实现AOP盟约的接口MethodInterceptor:
```java
public class WeekendBlocker implements MethodInterceptor {
  public Object invoke(MethodInvocation invocation) throws Throwable {
    Calendar today = new GregorianCalendar();
    if (today.getDisplayName(DAY_OF_WEEK, LONG, ENGLISH).startsWith("S")) {
      throw new IllegalStateException(
          invocation.getMethod().getName() + " not allowed on weekends!");
    }
    return invocation.proceed();
  }
}

如果有某个Bean的方法加上了注解NotOnWeekends,那么在周末执行的时候就会抛错。

源码解析

我们直接看ConstructorBindingImpl代码来分析如何实现Bean的生成,Guice是在构造Bean的时候直接生成代理的,com.google.inject.internal.ConstructorBindingImpl.initialize方法初始化了ConstructorInjector对象,这个对象包含了ConstructionProxy对象,而ConstructionProxy对象是由内部隐藏的一个代理工厂com.google.inject.internal.ProxyFactory<T>类生成的。

public ConstructionProxy<T> create() throws ErrorsException {
  if (interceptors.isEmpty()) {
    return new DefaultConstructionProxyFactory<T>(injectionPoint).create();
  }

  @SuppressWarnings("unchecked")
  Class<? extends Callback>[] callbackTypes = new Class[callbacks.length];
  for (int i = 0; i < callbacks.length; i++) {
    if (callbacks[i] == net.sf.cglib.proxy.NoOp.INSTANCE) {
    callbackTypes[i] = net.sf.cglib.proxy.NoOp.class;
    } else {
    callbackTypes[i] = net.sf.cglib.proxy.MethodInterceptor.class;
    }
  }

  // Create the proxied class. We're careful to ensure that all enhancer state is not-specific
  // to this injector. Otherwise, the proxies for each injector will waste PermGen memory
  try {
    Enhancer enhancer = BytecodeGen.newEnhancer(declaringClass, visibility);
    enhancer.setCallbackFilter(new IndicesCallbackFilter(methods));
    enhancer.setCallbackTypes(callbackTypes);
    return new ProxyConstructor<T>(enhancer, injectionPoint, callbacks, interceptors);
  } catch (Throwable e) {
    throw new Errors().errorEnhancingClass(declaringClass, e).toException();
  }
  }

ConstructionProxy有三个实现FastClassProxy、ReflectiveProxy和ProxyConstructor,在有拦截器时,采用ProxyConstructor实例化对象。

//com.google.inject.internal.ProxyFactory.ProxyConstructor.newInstance(Object...)
public T newInstance(Object... arguments) throws InvocationTargetException {
  Enhancer.registerCallbacks(enhanced, callbacks);
  try {
    return (T) fastClass.newInstance(constructorIndex, arguments);
  } finally {
    Enhancer.registerCallbacks(enhanced, null);
  }
}

总结

Spring很好的利用扩展机制实现了AOP,Guice采用了简单优雅的方式使用AOP。

在研究实现代理模式的源码,我们发现基本上所有的框架都会有一个类ProxyFactory,它隐藏了JDK动态代理和Cglib动态代理的实现,对外提供一个代理对象。