Spring AOP

519 阅读11分钟

这是我参与8月更文挑战的第30天,活动详情查看: 8月更文挑战

AOP 要实现的是在我们原来写的代码的基础上,进行一定的包装,如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者叫增强处理。

AOP 的实现并不是因为 Java 提供了什么神奇的钩子,可以把方法的几个生命周期告诉我们,而是我们要实现一个代理,实际运行的实例其实是生成的代理类的实例。

在Java平台上,对于AOP的织入,有3种方式:

  1. 编译期:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect来实现织入;
  2. 类加载器:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
  3. 运行期:目标对象和切面都是普通Java类,通过JVM的动态代理功能或者第三方库实现运行期动态织入。

最简单的方式是第三种,Spring的AOP实现就是基于JVM的动态代理。由于JVM的动态代理要求必须实现接口,如果一个普通类没有业务接口,就需要通过CGLIB或者Javassist这些第三方库实现。

AOP概念

在AOP编程中,经常会遇到以下概念:

Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点。在Spring AOP中aspect通过常规类和使用注解@Aspect的常规类。最常见的应该就是事务管理的相关配置;

Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;

Advice:增强,指特定连接点上执行的动作。不同类型的增强包括“around”,“before”和“after”增强。包括Spring在内的许多AOP框架都将增强建模为拦截器,并在连接点周围维护一系列拦截器;

Pointcut:切入点,即一组连接点的集合。增强与切入点表达式关联,并在与切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。切入点表达式匹配的连接点的概念是AOP的核心,默认情况下,Spring使用AspectJ切入点表达语言。声明切入点的方式有很多,语法规则也很多,例如下面的声明com.miracle.service包下面任意执行业务逻辑的public方法为切入点。

Introduction:引介,指为一个已有的Java对象动态地增加新的接口;

Weaving:织入,指将切面整合到程序的执行流程中;

Interceptor:拦截器,是一种实现增强的方式;

Target Object:目标对象,即真正执行业务的核心逻辑对象;

AOP Proxy:AOP代理,是客户端持有的增强后的对象引用。

使用Spring AOP

@AspectJ支持

可以使用XML或Java样式的配置来启用@AspectJ支持。在任何一种情况下,您还需要确保AspectJ的aspectjweaver.jar库位于应用程序的类路径上。

通过Java配置启用@AspectJ支持

要使用Java启用@AspectJ支持@Configuration,请添加@EnableAspectJAutoProxy 注释,如以下示例所示:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
通过XML配置启用@AspectJ支持

要使用基于XML的配置启用@AspectJ支持,请使用aop:aspectj-autoproxy 元素,如以下示例所示:

<aop:aspectj-autoproxy/>

声明一个方面

<!-- 常规bean-->
<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>
// 基于注解
@Aspect
@Component
public class MyAspect {
	
}

声明一个切入点

<!-- 语法
*:匹配任何数量字符;
..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
-->
<!-- 声明切入点 -->
<!-- 服务层中任意方法 -->
<aop:config>
    <aop:pointcut id="businessService"
        expression="execution(* com.miracle.service.*.*(..))"/>
</aop:config>
<!-- 注解方式 -->
@Pointcut("execution(* com.miracle.service.*.*(..))")
支持的切入点指示符

Spring AOP支持以下在切入点表达式中使用的AspectJ切入点指示符(PCD):

  • execution:用于匹配方法执行的连接点。这是使用Spring AOP时要使用的主要切入点指示符。
  • within:将匹配限制为某些类型内的连接点。
  • this:将指定实例限制为连接点。
  • target:在目标对象是给定类型的实例的情况下,将匹配限制为连接点。
  • args:在参数是给定类型的实例的情况下,将匹配限制为连接点。
  • @target:在执行对象的类具有给定类型的注释的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。
  • @args:限制匹配的连接点,其中传递的实际参数的运行时类型具有给定类型的注释。
  • @within:将匹配限制为具有给定注解的类型内的连接点。
  • @annotation:将匹配限制在连接点的主题具有给定注解的连接点处进行。

除了上面了的切入点指示符之外,Spring AOP提供了一个扩展,可以使用bean("beanName")来制定某个bean为切入点。

组合切入点表达式

AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。 在Schema风格下,由于在XML中使用“&&”需要使用转义字符“&&”来代替之,所以很不方便,因此Spring ASP 提供了and、or、not来代替&&、||、!。

切入点表达式 详解
execution

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?) 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。

参数模式如下:

() 匹配一个不接受任何参数的方法 (..) 匹配一个接受任意数量参数的方法 () 匹配了一个接受一个任何类型的参数的方法 (,String) 匹配了一个接受两个参数的方法,其中第一个参数是任意类型,第二个参数必须是String类型

举例:

// 匹配所有目标类的public方法
execution(public * *(..))

// 匹配所有以To为后缀的方法
execution(* *To(..))

// 匹配UserService接口中的所有方法
execution(* com.miracle.service.UserService.*(..))

// 匹配UserService接口中及其实现类的方法
execution(* com.miracle.service.UserService+.*(..))

// 匹配 com.miracle.service 包下所有类的所有方法
execution(* com.miracle.service.*(..))

// 匹配 com.miracle.service 包,子孙包下所有类的所有方法
execution(* com.miracle.service..*(..))

// 匹配 包名前缀为com的任何包下类名后缀为Service的方法,方法必须以query为前缀
execution(* com..*.*Service.query*(..))

// 匹配 save(String name,int age) 函数
execution(* save(String,int))

// 匹配 save(String name,*) 函数 第二个参数为任意类型
execution(* save(String,*))

// 匹配 save(String name,..) 函数 除第一个参数固定外,接受后面有任意个入参且入参类型不限
execution(* save(String,..))

// 匹配 save(String+) 函数  String+ 表示入参类型是String的子类
execution(* save(String+))
within

within是用来指定类型的,指定类型中的所有方法将被拦截。

举例:

// 表示匹配包com.miracle以及子包的所有方法
within(com.miracle..*) 

// 匹配UserServiceImpl类的所有方法
within(com.miracle.service.UserServiceImpl)

由于execution可以匹配包、类、方法,而within只能匹配包、类,因此execution完全可以代替within的功能。

this

this就表示代理对象。

举例:

// 表示匹配了UserService接口的代理对象的所有连接点
this(com.miracle.service.UserService) 
target

target表示被代理的目标对象。使用方式和this一致。

args

args用来匹配方法参数的。

  • args()匹配任何不带参数的方法。
  • args(java.lang.String)匹配任何只带一个参数,而且这个参数的类型是String的方法。
  • args(..)带任意参数的方法。
  • args(java.lang.String,..)匹配带任意个参数,但是第一个参数的类型是String的方法。
  • args(..,java.lang.String)匹配带任意个参数,但是最后一个参数的类型是String的方法。
@target

匹配当被代理的目标对象对应的类型及其父类型上拥有指定的注解时。

示例:

// 目标对象带有@Transactional注解的任何连接点 
@target(org.springframework.transaction.annotation.Transactional)
@args

匹配被调用的方法上含有参数,且对应的参数类型上拥有指定的注解的情况。

@args(com.miracle.MyAnnotation)匹配方法参数类型上拥有MyAnnotation注解的方法调用。如我们有一个方法add(MyParam param)接收一个MyParam类型的参数,而MyParam这个类是拥有注解MyAnnotation的,则它可以被Pointcut表达式@args(com.elim.spring.support.MyAnnotation)匹配上。

@within

目标对象的声明类型具有指定注解的任何连接点。

示例:

 // 目标对象的声明类型具有@Transactional注解的任何连接点
 @within(org.springframework.transaction.annotation.Transactional)
@annotation

执行方法带有指定注解的任何连接点。

示例:

// 执行方法带有@Transactional注释的任何连接点
@annotation(org.springframework.transaction.annotation.Transactional)
bean

bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)

源码简述

调试代码

public interface UserService {

    User queryUser();

    User createUser(int id, String userName, int age);
}

@Component
public class UserServiceImpl implements UserService{

    @Override
    public User queryUser() {
        User user = new User();
        user.setId(1);
        user.setAge(10);
        user.setUserName("xiaoming");
        return user;
    }

    @Override
    public User createUser(int id, String userName, int age) {
        User user = new User();
        user.setId(id);
        user.setAge(age);
        user.setUserName(userName);
        return user;
    }
}

public interface MailService {

    Mail createMail(int id, String address, String name);
}

@Component
public class MailServiceImpl implements MailService {

    @Override
    public Mail createMail(int id, String address, String name) {
        Mail mail = new Mail();
        mail.setAddress(address);
        mail.setId(id);
        mail.setName(name);
        return mail;
    }
}

// 启动AOP
@EnableAspectJAutoProxy
@Component
public class AppConfig {
}

@Aspect
@Component
public class LogAop {

    // 在执行UserService的每个方法前执行
    @Before("execution(public * com.miracle.aop.UserService.*(..))")
    public void doAccessCheck() {
        System.err.println("[Before] do access check...");
    }

    // 在执行MailService的每个方法前后执行
    @Around("execution(public * com.miracle.aop.MailService.*(..))")
    public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
        System.err.println("[Around] start " + pjp.getSignature());

        Object retVal = pjp.proceed();
        System.err.println("[Around] done " + pjp.getSignature());
        return retVal;
    }

}

// 启动类
public class SpringAopDemoApplication {

    public static void main(String[] args) {

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "application.xml");

        UserService userService = context.getBean(UserService.class);
        MailService mailService = context.getBean(MailService.class);

        userService.createUser(1, "xiaoming", 12);
        userService.queryUser();

        mailService.createMail(1, "test@123.com", "miracle");
    }

}

// 执行结果
[Before] do access check...
[Before] do access check...
[Around] start Mail com.miracle.aop.MailService.createMail(int,String,String)
[Around] done Mail com.miracle.aop.MailService.createMail(int,String,String)

@EnableAspectJAutoProxy

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	boolean proxyTargetClass() default false;

	boolean exposeProxy() default false;

}

这个注解中@Import引入了一个配置类AspectJAutoProxyRegistrar。往下跟代码就到了关键代码AnnotationAwareAspectJAutoProxyCreator类。

AnnotationAwareAspectJAutoProxyCreator

先看下AnnotationAwareAspectJAutoProxyCreator类的继承结构:

AnnotationAwareAspectJAutoProxyCreator

我们可以发现,AnnotationAwareAspectJAutoProxyCreator最后居然是一个 BeanPostProcessor,在 Spring IoC 源码分析的时候说过,BeanPostProcessor 的两个方法,分别在 init-method 的前后得到执行。

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object var1, String var2) throws BeansException;

    Object postProcessAfterInitialization(Object var1, String var2) throws BeansException;
}

回忆一下Spring IoC的源码。

protected Object doCreateBean(final String beanName, 
                              final RootBeanDefinition mbd, final Object[] args) {
   // ...
   Object exposedObject = bean;
   try {
      // 装载属性
      populateBean(beanName, mbd, instanceWrapper);
      if (exposedObject != null) {
         // 初始化
         exposedObject = initializeBean(beanName, exposedObject, mbd);
      }
   }
   // ...

   return exposedObject;
}
protected Object initializeBean(final String beanName, 
                                final Object bean, RootBeanDefinition mbd) {
   // ...
   Object wrappedBean = bean;
   if (mbd == null || !mbd.isSynthetic()) {
      // 执行每一个 BeanPostProcessor 的 postProcessBeforeInitialization 方法
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
   }

   // ...

   if (mbd == null || !mbd.isSynthetic()) {
      // 执行每一个 BeanPostProcessor 的 postProcessAfterInitialization 方法
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
   }
   return wrappedBean;
}

我们重点关注初始化之后执行postProcessAfterInitialization()方法,Spring AOP 会在 IOC 容器创建 bean 实例的最后对 bean 进行处理。其实就是在这一步进行代理增强。

回到AnnotationAwareAspectJAutoProxyCreator,发现postProcessAfterInitialization()方法在其父类AbstractAutoProxyCreator中被覆写。

public Object postProcessAfterInitialization(Object bean, 
                                             String beanName) throws BeansException {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (!this.earlyProxyReferences.contains(cacheKey)) {
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

继续看wrapIfNecessary()方法。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
   if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
      return bean;
   }
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
   }
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }

   // 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;
}

代码相对于Spring IoC的源码简单了很多,不过其中有一个点,getAdvicesAndAdvisorsForBean()方法返回所有可用于拦截当前bean的Advice、Advisor和Intercepter。

protected Object createProxy(Class<?> beanClass, String beanName, 
                             Object[] specificInterceptors, TargetSource targetSource) {

   if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
      AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this
                                       .beanFactory, beanName, beanClass);
   }

   // 创建 ProxyFactory 实例
   ProxyFactory proxyFactory = new ProxyFactory();
   proxyFactory.copyFrom(this);

   // 在xml中配置<aop:aspectj-autoproxy proxy-target-class="true"/>
   // 或者注解@EnableAspectJAutoProxy(proxyTargetClass = true)
   // 强制使用cglib生成代理
   if (!proxyFactory.isProxyTargetClass()) {
      if (shouldProxyTargetClass(beanClass, beanName)) {
         proxyFactory.setProxyTargetClass(true);
      }
      else {
         // 有接口的:proxyFactory.addInterface(ifc);
         // 没有接口的:proxyFactory.setProxyTargetClass(true);
         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实例,并添加一些属性,最后调用getProxy()方法生成代理。

public Object getProxy(ClassLoader classLoader) {
   return createAopProxy().getProxy(classLoader);
}
protected final synchronized AopProxy createAopProxy() {
   if (!this.active) {
      activate();
   }
   return getAopProxyFactory().createAopProxy(this);
}
// DefaultAopProxyFactory.java line 50
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实例,根据是否有接口生成JdkDynamicAopProxyObjenesisCglibAopProxy

最后根据生成的AopProxy实例执行getProxy(ClassLoader classLoader)方法。

JdkDynamicAopProxy.getProxy(ClassLoader classLoader)
public Object getProxy(ClassLoader classLoader) {
   if (logger.isDebugEnabled()) {
      logger.debug("Creating JDK dynamic proxy: target source is " + 
                   this.advised.getTargetSource());
   }
   Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(
       this.advised, true);
   findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
   return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
CglibAopProxy.getProxy(ClassLoader classLader)
public Object getProxy(ClassLoader classLoader) {
   if (logger.isDebugEnabled()) {
      logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource());
   }

   try {
      Class<?> rootClass = this.advised.getTargetClass();
      Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

      Class<?> proxySuperClass = rootClass;
      if (ClassUtils.isCglibProxyClass(rootClass)) {
         proxySuperClass = rootClass.getSuperclass();
         Class<?>[] additionalInterfaces = rootClass.getInterfaces();
         for (Class<?> additionalInterface : additionalInterfaces) {
            this.advised.addInterface(additionalInterface);
         }
      }

      // Validate the class, writing log messages as necessary.
      validateClassIfNecessary(proxySuperClass, classLoader);

      // Configure CGLIB Enhancer...
      Enhancer enhancer = createEnhancer();
      if (classLoader != null) {
         enhancer.setClassLoader(classLoader);
         if (classLoader instanceof SmartClassLoader &&
               ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
            enhancer.setUseCache(false);
         }
      }
      enhancer.setSuperclass(proxySuperClass);
      enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
      enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
      enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));

      Callback[] callbacks = getCallbacks(rootClass);
      Class<?>[] types = new Class<?>[callbacks.length];
      for (int x = 0; x < types.length; x++) {
         types[x] = callbacks[x].getClass();
      }
      // fixedInterceptorMap only populated at this point, after getCallbacks call above
      enhancer.setCallbackFilter(new ProxyCallbackFilter(
          this.advised.getConfigurationOnlyCopy(), 
          this.fixedInterceptorMap, this.fixedInterceptorOffset));
       
      enhancer.setCallbackTypes(types);

      // Generate the proxy class and create a proxy instance.
      return createProxyClassAndInstance(enhancer, callbacks);
   }
   // ...
}

整体Spring Aop的过程就是这样,本文没有像Spring IoC那样具体说,本身Spring AOP的源码不算复杂,其依赖于Spring IoC,是对IoC的一种扩展支持。