Spring 拾遗 - AOP

概述

  AOP(Aspect-Oriented Progarmming),面向切面编程 / 面向方面编程,是Spring框架提供的关键特性之一;使用AOP技术,可以将一些系统层面的问题分离出来独立编程实现,并在适当的时机织入系统,以增强目标实现,对于广大开发者而言,日常应用中主要用来解决的问题包括事务控制、权限控制、日志管理等;使用AOP特性,可以避免在业务逻辑中过多的掺杂公共问题的解决方案,故此,它也是OOP(Object-Oriented Programming)的有效补充;AOP思想在其它开源软件中也有广泛的应用,大家熟悉且比较经典的,莫过于Struts2的拦截器了。

术语定义

  • Aspect:切面,通常是一个类,包含切入点和通知;
  • Pointcut:切入点,在程序中,通常体现为切入点表达式;
  • Advice:通知,在特定的切入点上执行的增强处理,通常是一个方法,定义增强实现的逻辑;
  • Join Point:连接点,程序执行过程中一个明确的点,通常为方法的调用; 
  • Target Object:目标对象,即被通知的对象 / 被增强的对象 / 被代理的对象。

通知(Advice)类型

  • @Before:前置通知,在目标方法执行之前做增强处理;
  • @Around:环绕通知,在目标方法执行前后做增强处理,注意,编程核心为ProceedingJoinPoint类;
  • @After:后置通知,在目标方法完成之后做增强处理(无论目标方法是正常完成,还是异常退出);
  • @AfterReturning:返回后通知,在目标方法正常完成后做增强处理,区别于@After;注解可传入returning形参,用以表示目标方法的返回值;
  • @AfterThrowing:异常时通知,在目标方法抛出异常时做增强处理;注解可传入throwing形参,用以表示目标方法抛出的异常。

实现原理

  如果被代理对象实现了接口,使用JDK动态代理实现,如果被代理对象未实现接口,则使用CGLIB实现。通过阅读Spring实现AOP的源码可知,Spring默认使用JDK动态代理技术实现AOP。

@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    @Override
    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.");
            }
       // 有以下逻辑可知,Spring默认使用JDK动态代理技术实现AOP
            if (targetClass.isInterface()) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

配置示例

定义示例所需的基础类,源码清单如下:

UserServiceI.java

public interface UserServiceI {

    public void addUser();
}

UserServiceImpl.java

@Service("userService")
public class UserServiceImpl implements UserServiceI {

    public void addUser() {        
        System.out.println("UserServiceImpl.addUser()");
    }
}

UserServiceAspect.java

/**
 * 切面类,定义对目标方法的增强处理
 * 
 * @author 
 */
@Component("userServiceAspect")
public class UserServiceAspect {

    public void before() {
        System.out.println("Before Advice");
    }
    
    public void around(ProceedingJoinPoint proceed) throws Throwable {
        System.out.println("Around Advice before");
        proceed.proceed();
        System.out.println("Around Advice after");
    }
    
    public void after() {
        System.out.println("After Advice");
    }
    
    public void afterReturning() {
        System.out.println("AfterReturning Advice");
    }
    
    public void afterThrowing() {
        System.out.println("AfterThrowing Advice");
    }
}

基于xml文件的方式配置aop

<aop:config>
        
        <!-- 切入点定义,此处为UserServiceImpl类中的所有方法 -->
        <aop:pointcut expression="execution(* mysource.demo.spring_aop_xml.*Impl.*(..))" id="userPointcut"/>
        
        <!-- 切面配置,对 UserService 增强处理 -->
        <aop:aspect ref="userServiceAspect" id="userServiceAspect">
            <aop:before method="before" pointcut-ref="userPointcut" />            
            <aop:around method="around" pointcut-ref="userPointcut" />
            <aop:after method="after" pointcut-ref="userPointcut" />
            <aop:after-returning method="afterReturning" pointcut-ref="userPointcut" />
            <aop:after-throwing method="afterThrowing" pointcut-ref="userPointcut" />
        </aop:aspect>
        
    </aop:config>

基于注解的形式配置aop

修改UserServiceAspect.java文件,添加@Aspect注解,使其具有AOP切面的特性;使用@Pointcut定义切入点;并分别使用注解定义通知方式,如下代码所示: 

package mysource.busi.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 切面类,定义对目标方法的增强处理
 * 
 * @author 
 */
@Component
@Aspect
public class UserServiceAspect {

    /**
     * 定义切入点,此处匹配 UserServiceI 类中的所有方法
     */
    @Pointcut("execution(* mysource..*.*(..))")
    public void pointcut() {}
    
    @Before("pointcut()")
    public void before() {
        System.out.println("Before Advice");
    }
    
    @Around("pointcut()")
    public void around(ProceedingJoinPoint proceed) throws Throwable {
        System.out.println("Around Advice before");
        // around 通知中,调用 proceed(),才会执行目标方法
        proceed.proceed();
        System.out.println("Around Advice after");
    }
    
    @After("pointcut()")
    public void after() {
        System.out.println("After Advice");
    }
    
    @AfterReturning(pointcut="pointcut()", returning="returning")
    public void afterReturning(Object returning) {
        System.out.println("AfterReturning Advice");
    }
    
    @AfterThrowing(pointcut="pointcut()", throwing="error")
    public void afterThrowing(Throwable error) {
        System.out.println("AfterThrowing Advice");
    }
}

示例运行结果

调用UserServiceI.addUser()方法,输出如下,可以看出切面类对业务方法进行了织入增强:

通知执行优先级

有执行结果可知,先织入@Around通知,接着执行@Before通知,目标方法退出时,先织入@Around通知,接着执行@After通知,最后执行@AfterReturning通知。  

AspectJ切点函数

切点表达式语法说明:* 表示任意返回类型,任意类名、方法名,任意参数类型;.. 连续两个点,表示0个或多个包路径,0个或多个参数;

execution,匹配满足模式定义的目标类方法;示例表达式,匹配mysource包及其子包中所有类方法:

@Pointcut("execution(* mysource..*.*(..))")

within,匹配指定包路径(类名也可以看作包名)的所有方法;示例表达式,匹配mysource.aop包中所有的类方法:

@Pointcut("execution(* mysource.aop.*)")
posted @ 2016-08-15 16:04  Dawn、  阅读(274)  评论(0编辑  收藏  举报