Spring AOP面向切面编程

一、AOP面向切面编程基本概念

在软件系统中,有些行为对于绝大多数业务模块都是通用,例如日志管理、权限管理、事物管理等,在不同的业务模块中,我们很有可能会编写相同的代码来完成一个共同的功能点,这就造成了极大的代码冗余,和较高的维护成本,同时业务代码与特殊功能代码耦合交融到了一起,不利于系统的构建与开发。

AOP:Aspect Oriented Programming ,面向切面编程,就是将众多的业务模块进行横向切割,提炼抽取其中的相同功能代码进行单独编程,已达到业务代码与非业务功能代码的隔离,从而使得业务逻辑部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。

AOP最重要的功能:高内聚,低耦合

二、Spring AOP的基本概念

AOP把软件系统分成两个部分:核心关注点和横切关注点,业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点,描述横切关注点的具体内容称之为切面。

描述切面的常用概念是:通知(Advice)切点(Pointcut)连接点(JoinPoint)、目标对象(Target)、织入(Weaving)、代理(Proxy)、切面(aspect)

  • 通知:定义了切面执行的代码内容是什么,在什么时候执行。
  • 切点:一个或多个连接点的集合,通常使用具体的类名和方法名来指定这些切点,或是使用正则表达式来定义匹配的类和方法来指定这些切点。
  • 连接点:在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常时、修改一个字段时,切面代码可以利用连接点插入到应用的正常流程之中,并执行切面代码。
  • 目标对象:被代理对象。
  • 织入:将通知应用到切入点的过程。
  • 代理:将通知织入到目标对象之后,形成代理对象。
  • 切面:切面是通知和切点的结合,通知和切点共同定义了关于切面的全部内容,切面内容是什么,在什么时候执行,在什么地方执行。(切点+通知)。

Spring中实现AOP原理:

  • 动态代理:被代理对象必须实现接口才能实现代理对象,如果没有接口将不能使用动态代理技术。

  • cglib代理:第三方的代理技术,可以对任何类生成代理,代理的原理是对目标对象进行继承代理,如果目标被对象final修饰,那么该类无法被cglib代理。

  • 如果有接口优先使用动态代理。

Spring AOP中定义了五种类型的通知:

通知类型 描述
Before 再切点方法执行之前,调用通知中指定的方法。
After 再切点方法执行完成之后,调用通知中指定的方法,不管切点方法执行是否成功。
After-returning 再切点方法成功执行之后,调用通知中指定的方法。
After-throwing 再切点方法执行抛出异常后,调用通知中指定的方法。
Around 通知方法包裹切点方法执行,改变切点方法中的执行逻辑

三、Spring AOP编程

1. 通过标签方式进行切面编程

<!--切面配置标签块-->
<aop:config>
    <!--切点定义,使用正则表达式来匹配连接点集合,通过id来命令切点-->
    <aop:pointcut expression="execution(* com.yingcai.service.*.*(..))" id="pointcut"/>
    <!--通知配置标签块,ref属性定义通知执行Bean-->
    <aop:aspect  ref="adviceInfo">
        <aop:before method="beforeAdvice" pointcut-ref="pointcut"/>
        <aop:after method="afterAdvice" pointcut-ref="pointcut"/>
        <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pointcut"/>
        <aop:after-returning method="afterReturnAdvice" pointcut-ref="pointcut"/>
        <aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

环绕通知详解:

通过ProceedingJoinPoin类来获取操作连接点切入方法的相关信息

获取切入方法执行类名称:

Class executeClass = proceedingJoinPoint.getTarget().getClass();
String className = executeClass.getName();

获取切入方法名称:

String methodName = proceedingJoinPoint.getSignature().getName();

获取切入方法入参:

Object[] args = proceedingJoinPoint.getArgs();

执行切入方法内容:

proceedingJoinPoint.proceed(args);

注:环绕通知的方法格式必须与连接点方法格式相同,连接点方法有返参,环绕通知方法也必须有返参。

实例:

    /**
     * 在业务方法的前后做处理
     *
     * @param joinPoint 是具体业务方法被封装的对象
     * @return
     */
    public Object around(ProceedingJoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getName();
        String method = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        logger.debug("enter=" + className + "." + method);
        logger.debug("args=" + Arrays.toString(args));
        try {
            Result result = (Result) joinPoint.proceed(args);
            logger.debug("result=" + result);
            return result;
        } catch (Throwable throwable) {
            logger.error("occur exception:", throwable);
            return new Result(null, "proceed failure");
        }
    }

业务方法:

@Service
public class CollegeService implements IBaseService<College>{
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    public Result add(College college) {
//        int num = 1/0;
        return new Result(true,"增加成功");
    }
}

输出日志:

2. 使用注解方式进行切面编程

切面注解的开启:

<aop:aspectj-autoproxy/>
  • @Aspect:标注于类上,将一个类定义为切面类。

  • @Pointcut:标注于一个普通方法上,定义切点内容。

  • @Before:标注于前置通知方法之上。

  • @After:标注于后置方法之上。

  • @AfterThrowing:标注于后置失败通知方法之上。

  • @AfterReturning:标注于后置成功通知方法之上。

  • @Around:标注于环绕通知方法之上。

实例:

/**
 * copyright(c)2021 zbh.ALL rights Reserved
 * <p>
 * 描述:切面类
 *
 * @author zbh
 * @version 1.0
 * @date 2021/7/26
 */
@Component
@Aspect
public class LogAop {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("execution(* com.ychs.service.*.*(..))")
    public void pc() {}

    /**
     * 在业务方法执行前打印日志
     */
    // @Before(value = "execution(* com.ychs.service.*.*(..))")
    @Before("pc()")
    public void before() {
        logger.debug("service method before...");
    }

    @After("pc()")
    public void after() {
        logger.debug("service method after...");
    }

    @AfterReturning("pc()")
    public void afterReturn() {
        logger.debug("service method afterReturn");
    }

    @AfterThrowing("pc()")
    public void afterThrowing() {
        logger.debug("service method afterThrowing");
    }

    /**
     * 在业务方法的前后做处理
     *
     * @param joinPoint 是具体业务方法被封装的对象
     * @return
     */
    @Around("pc()")
    public Object around(ProceedingJoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getName();
        String method = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        logger.debug("enter=" + className + "." + method);
        logger.debug("args=" + Arrays.toString(args));
        try {
            Result result = (Result) joinPoint.proceed(args);
            logger.debug("result=" + result);
            return result;
        } catch (Throwable throwable) {
            logger.error("occur exception:", throwable);
            return new Result(null, "proceed failure");
        }
    }
}
posted @ 2020-11-25 16:10  逍遥客灬  阅读(203)  评论(0编辑  收藏  举报