美文网首页从零开始学springboot
31.从零开始学springboot-再谈切面“AOP”

31.从零开始学springboot-再谈切面“AOP”

作者: 码哥说 | 来源:发表于2019-07-30 14:12 被阅读30次

    前言

    说起Java,就不得不提Spring,提到Spring,就不得不提IOC(控制反转)和AOP(切面), 本章就详细介绍一下AOP(切面)思想以及它在Spring中的应用.


    WechatIMG2.jpeg

    概念

    我们先看看百度此条对AOP的解释

    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
    式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
    热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
    的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
    了开发的效率。
    

    这样的描述还是过于空泛,我们简单的举个例子,帮助大家理解.

    案例讲解

    假设我们需要实现一个日志记录的功能.大家会怎么实现呢?
    在AOP之前无非是两种:
    方案一:
    在每个业务的节点加入日志记录的代码.

    //实例
    业务一
    public String ServiceA(){
        //记录日志的实现1
    }
    
    业务二
    public String ServiceB(){
        //记录日志的实现2
    }
    ……
    

    方案二:
    写一个通用的日志模块,在每个需要用到的业务节点调用该日志功能.

    //实例
    
    //日志模块
    public class LogUtils {
        public void log(String data){
            //记录日志
        }
    }
    
    //业务一
    public String ServiceA(){
        LogUtils.log();
    }
    
    //业务二
    public String ServiceB(){
        LogUtils.log();
    }
    

    我们仔细看方案一和二,不难看出,方案一就是面向过程的一种编程思想,而方案二就是OOP(面向对象)的一种编程思想.
    方案一的做法会使得代码非常冗余,而且后续日志调整,每处都要调整,十分不好维护.
    方法二比方法一好了一点,至少调整日志时只需调整一处,但是还是避免不了代码过于分散的问题,比如,需要在新的业务节点加上日志,就不得不添加新的调用代码.不过,方案二的这种把“公用”方法提取出来作为一个单独模块的思想,已经有了点AOP的思想了.

    那么,有没有更好的做法呢 ?

    有没有一种可以不增加新的“调用代码”(无需修改代码),就能在新的业务节点上输出日志的方法呢?

    下面,我们来仔细聊聊AOP究竟是怎么一回事.

    几个概念

    为了更好的理解AOP,我们先抛出几个概念.

    • Advice(增强)
      Advice定义了在PointCut里面定义的程序点具体要做的操作,它通过Before、After和Around来区别是在每个JoinPoint之前、之后还是代替执行的代码。
      通知定义了切面是什么以及何时调用,何时调用包含以下几种类型

      • Before 在方法被调用之前调用通知
      • After 在方法完成之后调用通知,无论方法执行是否成功
      • After-returning 在方法成功执行之后调用通知
      • After-throwing 在方法抛出异常后调用通知
      • Around 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

    总结: Advice就是你想要的功能,可以是日志/验证等等,同时Advice可以定义在PointCut的什么位置触发.

    • JoinPoint(连接点)
      JoinPoint就是程序执行的某个特定的位置,如:类开始初始化前、类初始化后、类的某个方法调用前、类的某个方法调用后、方法抛出异常后等.Spring只支持类的方法前、后、抛出异常后的连接点.

    总结: JoinPoint就是允许你使用Advice的地方.每个类中的每个方法都是一个JoinPoint,假设一个类有3个方法,那它就有3个JoinPoint.

    • PointCut(切点)
      表示一组JoinPoint,这些JoinPoint或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的Advice将要发生的地方。

    总结: PointCut是用来筛选JoinPoint的(你可以假设成mysql的查询条件). 假设你的一个类里,有增删改查4个方法,那就有4个连接点,但是你并不想在所有方法都使用Advice,你只是想让其中几个,比如只想在insert方法执行的前后进行日志记录等Advice操作,那么就用PointCut来定义这几个方法,让PointCut来筛选JoinPoint,选中那几个你想要的方法。

    • Aspect(切面)
      Aspect声明类似于Java中的类声明,在Aspect中会包含着一些PointCut以及相应的Advice。
    • Target(目标对象)
      织入Advice的目标对象。

    总结:Target就是我们需要对它进行Advice的业务类,如果没有AOP,那么该业务类就得自己实现需要的功能。

    • Weaving(织入)
      将Advice添加到Target类具体JoinPoint上的过程

    通过以上几个概念,我想大家对AOP已经有了一下感悟,下面,我们就这些概念结合实例再来归纳一下.

    实例讲解

    假设我们需要对业务上增加一个日志记录的功能.
    假设我们有两个业务类ServiceA和ServiceB

    ServiceA

    package com.aopdemo.service;
    import java.util.List;
    @Service
    public class ServiceA{
        Long insert(Order order){
    
        }
        int delete(Long id){
    
        }
        int update(Order order){
    
        }
        List<Order> query(Condition condition){
    
        }
    }
    

    ServiceB

    package com.aopdemo.service;
    import java.util.List;
    @Service
    public class ServiceB{
        Long insert(PayLog payLog){
    
        }
        int delete(Long id){
    
        }
        int update(PayLog payLog){
    
        }
        List<PayLog> query(Condition condition){
    
        }
    }
    

    我们可以看到,ServiceA和ServiceB都有对应的增删改查方法.假设,我们现在需要对每个业务的插入方法的前后做一个日志记录的功能.

    我们就需要定义一个AOP类

    
    //Spring中的声明该类为AOP的注解
    @Aspect
    @Component
    public class LogAop {
    
        //PointCut切点,用来过滤JoinPoint连接点,这句的意思即为:
        //该日志Advice只对com.aopdemo.service下的所有service类中的insert方法生效
        @Pointcut("execution(public * com.aopdemo.service.*.insert(..))")
        public void writeLog() {
            //记录日志
        }
    
        //前置通知,在某切入点@Pointcut之前的通知
        @Before("writeLog()")
        public void deBefore(JoinPoint joinPoint) throws Throwable {
            // 接收到请求,记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            // 记录下请求内容
            System.out.println("URL : " + request.getRequestURL().toString());
            System.out.println("HTTP_METHOD : " + request.getMethod());
            System.out.println("IP : " + request.getRemoteAddr());
            System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
            System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
    
        }
    
        //后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
        @After("writeLog()")
        public void after(JoinPoint jp) {
            System.out.println("方法最后执行.....");
        }
    
        //返回后通知,方法执行return之后,可以对返回的数据做加工处理
        @AfterReturning(returning = "ret", pointcut = "writeLog()")
        public void doAfterReturning(Object ret) throws Throwable {
            // 处理完请求,返回内容
            System.out.println("方法的返回值 : " + ret);
        }
    
        //抛出异常通知,程序出错跑出异常会执行该通知方法
        @AfterThrowing("writeLog()")
        public void throwss(JoinPoint jp) {
            System.out.println("方法异常时执行.....");
        }
    
        //环绕增强,在方法的调用前、后执行,相当于MethodInterceptor
        @Around("writeLog()")
        public Object arround(ProceedingJoinPoint pjp) {
            System.out.println("方法环绕start.....");
            try {
                Object o = pjp.proceed();
                System.out.println("方法环绕proceed,结果是 :" + o);
                return o;
            } catch (Throwable e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    

    总结

    AOP就是在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想。

    小结

    一句话总结IOC: Spring容器管理对象,不用自己“new”对象.

    请关注我的订阅号

    订阅号.png

    相关文章

      网友评论

        本文标题:31.从零开始学springboot-再谈切面“AOP”

        本文链接:https://www.haomeiwen.com/subject/rnzgrctx.html