• AspectJ AOP的使用(@Before、@PointCut、@Around等)



    最近在看一个项目中使用到AOP的功能,现在将自己过去所学的知识梳理一下。

    AOP概念

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

    使用AspectJ面向切面编程

    切面(Aspect) :通知(advice)和切入点(pointcut)共同组成了切面(aspect)。可以从注解方式来理解,代码如下。
    @Aspect为类上面的注解——切面
    @pointcut(…)——切入点。为此类内一个空方法上面的注解。可以把拦截的地址表达式表示为方法签名,利于使用起来方便。
    @before、@after等——通知。为此类下面的方法上面的注解。
    三者在一块组成一个切面。

    package com.nowcoder.community.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    /**
     * @author cjy
     * @Package com.nowcoder.community.aspect
     * @date 2021/8/11 16:48
     */
    /*@Component
    @Aspect*/
    public class AlphaAspect {
        //定义一个切点,目标方法
        @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
        public void pointcut(){
    
        }
        //目标方法之前
        @Before("pointcut()")
        public void before(){
            System.out.println("before");
        }
    
        @After("pointcut()")
        public void after(){
            System.out.println("after");
        }
    
        //方法返回之后
        @AfterReturning("pointcut()")
        public void afterReturning(){
            System.out.println("afterReturning");
        }
    
        //再抛异常的时候植入代码
        @AfterThrowing("pointcut()")
        public void afterThrowing(){
            System.out.println("AfterThrowing");
        }
    
        //环绕,参数ProceedingJoinPoint连接点,植入的部位
        @Around("pointcut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
            System.out.println("around before");  //前
            Object obj = joinPoint.proceed();//调用目标组件的方法
            System.out.println("around after");  //后
            return obj;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    详细说明

    @Aspect

    首先,代码的类的最上面使用的是@Aspect注解,作用是把当前类标识为一个切面供容器读取,之后使用@Component将切面注入IOC容器。

    @Pointcut、execution

    进入类中,是使用@Pointcut(“execution(* com.nowcoder.community.service..(…))”)这样的一个注解修饰一个pointcut()方法。这样是为了指定切入点和一个point方法签名。execution中可以匹配如下内容:

    修饰符匹配(modifier-pattern?)
    返回值匹配(ret-type-pattern)可以为表示任何返回值,全路径的类名等
    类路径匹配(declaring-type-pattern?)
    方法名匹配(name-pattern)可以指定方法名 或者 代表所有, set 代表以set开头的所有方法
    参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“
    ”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(*,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(…)表示零个或多个任意参数
    异常类型匹配(throws-pattern?)
    例如:

    1execution(* *(..))  
    //表示匹配所有方法  
    2execution(public * com. savage.service.UserService.*(..))  
    //表示匹配com.savage.server.UserService中所有的公有方法  
    3execution(* com.savage.server..*.*(..))  
    //表示匹配com.savage.server包及其子包下的所有方法 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后要使用所定义的Pointcut时,可以指定Pointcut签名。例如:

    @Before("pointCut")
    
    • 1

    这种使用方式等同于以下方式,直接定义execution表达式使用:

    @Before("execution(* com.savage.aop.MessageSender.*(..))")
    
    • 1

    Pointcut定义时,还可以使用&&、||、! 这三个运算,例如:

    @Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
    private void logSender(){}
    
    @Pointcut("execution(* com.savage.aop.MessageReceiver.*(..))")
    private void logReceiver(){}
    
    @Pointcut("logSender() || logReceiver()")
    private void logMessage(){}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    笔者更推荐这一种,将一些公用的Pointcut放到一个类中,以供整个应用程序使用。类似于常量存储,便于修改和扩展。

    package com.savage.aop;
    
    import org.aspectj.lang.annotation.*;
    
    public class Pointcuts {
    @Pointcut("execution(* *Message(..))")
    public void logMessage(){}
    
    @Pointcut("execution(* *Attachment(..))")
    public void logAttachment(){}
    
    @Pointcut("execution(* *Service.*(..))")
    public void auth(){}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    使用如下:

    @Aspect
    public class LogBeforeAdvice {
    		@Before("com.sagage.aop.Pointcuts.logMessage()")
    		public void before(JoinPoint joinPoint) {
    		System.out.println("Logging before " + joinPoint.getSignature().getName());
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    各种通知

    最后,就是整个aop的核心代码了,使用了@Before、@Around等注解修饰的方法。
    @Before 前置通知(Before advice) :在某连接点(JoinPoint)——核心代码(类或者方法)之前执行的通知,但这个通知不能阻止连接点前的执行。
    @After 后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
    @AfterReturning 返回后通知(After return advice) :在某连接点正常完成后执行的通知,不包括抛出异常的情况。
    @Around 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。这时aop的最重要的,最常用的注解。用这个注解的方法入参传的是ProceedingJionPoint pjp,可以决定当前线程能否进入核心方法中——通过调用pjp.proceed();
    @AfterThrowing 抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。

    有如下的一个例子:某一个系统需要一个日志组件,用来记录用户在什么时间访问了哪个资源。正常情况下来讲,需要在每一个controller层接口部分返回一个用户操作的log。这样需要修改的代码很多,如果使用面向切面思想,如果直接在controller层方法后,或者service方法前指定一个切面,就可以对其进行扩展。代码如下:

    package com.nowcoder.community.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * @author cjy
     * @Package com.nowcoder.community.aspect
     * @date 2021/8/11 17:00
     */
    @Component
    @Aspect
    public class ServiceLogAspect {
        //日志组件
        private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
        //指定切入点,类路径匹配,匹配所有com.nowcoder.community.service中所有的方法
        @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
        //point签名
        public void pointCut(){
    
        }
    
        //在方法执行前执行
        @Before("pointCut()")
        public void before(JoinPoint joinPoint) {
            //记录内容
            //用户【1,2,3(ip)】,在【时间】访问量【com....功能】的方法
            //转换为子类型
            ServletRequestAttributes attr = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
            if (attr == null){
                logger.info("使用kafka消费");
                return;
            }
            HttpServletRequest request = attr.getRequest();
    
            String ip = request.getRemoteHost();
            String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            //类名和方法名获取
            String target = joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName();
            logger.info(String.format("用户[%s],在[%s]访问了[%s]",ip,now,target));
        }
        /*
        * 也可以写为controller方法,使用@AfterReturning注解。当这个方法(连接点)正常返回后执行通知,异常的话不执行通知
        * 或者使用@Around环绕通知,自定义方法完成前后执行。而且还可以决定当前线程是否进入到核心方法中
        * 主要是ProceedingJoinPoint.proceed()方法可以真正的调用目标方法,必须有返回值返回
        * pjp.getArgs()可以获取参数,可以对参数进行校验
        * */
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    完成以后,在每个service方法旁边,都会有一个->m的标识,也就是切入点在此。
    在这里插入图片描述
    这样的切入点范围市比较广的,有时候只对其中的一些功能切入,我们可以配合自定义注解方式使用AOP编程。

  • 相关阅读:
    abp9 .net8 升级错误记录
    Docker的运用场景 103.53.124.2
    如果你还没玩过Docker Stack管理服务,你已经out了,(送Portainer集群管理教程)
    纯正体验,极致商务 | 丽亭酒店聚焦未来赛道,实现共赢发展
    2023华为杯研究生数学建模竞赛选题建议+初步分析
    2.Tornado的优势
    微星迫击炮b660m使用intel arc a750/770显卡功耗优化方法
    Java版本+企业电子招投标系统源代码+支持二开+招投标系统+中小型企业采购供应商招投标平台
    在小程序中对flex布局的理解
    排序算法-----快速排序(递归)
  • 原文地址:https://blog.csdn.net/qq_40454136/article/details/126098759