springAop

springAop

前言:这里介绍AOP的基本使用,aop知识点比较多后续会不断补充~

1.概念介绍(通俗易懂)

(1).Join point(连接点)对哪个方法进行处理增强,这个方法就是点
程序执行过程中的一点,例如方法执行或异常处理。在Spring AOP中,连接点始终代表方法的执行

(2).Pointcut(切入点)作用范围,如在哪个包下,哪个类中等等!
切入点是与连接点匹配的表达式

(3).Advice(增强/通知)拦截后做的事情增强,如:我在执行前了写日志,做了用户访问权限校验等!
所谓的通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为目的地通知,后置通知,异常通知,最终通知和环绕通知(切面要完成的功能)

(4).Aspect(切面)你写的aop的类,就是切面,再简单一点就是你加了@Aspect的类,专业名称叫做"切面"
Aspect切面表示Pointcut(切入点)和Advice(增强/通知)的结合
如图:
在这里插入图片描述

2.常用表达式

在这里插入图片描述

3.表达式符号解释

在这里插入图片描述
1.方法的修饰符 public
2.返回值 *
3.包名 com.xxxx
4.方法,方法参数 service.*(…)
包路径: 当前包和子包就是用…
匹配无参的就是 () 括号内是空的
(Long) 那么就是匹配一个long类型的入参
(…) 那么就是任意参数
5.匹配方法抛出了哪些异常 exception
(一般加载在括号后面,这里放一个图片案例如: 后面跟的throws…)
在这里插入图片描述

4.aop通知

在这里插入图片描述

5.JoinPoint & ProceedingJoinPoint API介绍

JoinPoint
String toString(); //连接点所在位置的相关信息
String toShortString(); //连接点所在位置的简短相关信息
String toLongString(); //连接点所在位置的全部相关信息
Object getThis(); //返回AOP代理对象
Object getTarget(); //返回目标对象
Object[] getArgs(); //返回被通知方法参数列表
Signature getSignature(); //返回当前连接点方法签名
(方法签名的含义: 就是方法名加括号里面的所有参数列表,注意没有返回值!举例: print(String name) print方法加它的入参String类型的name 这就是方法的签名)
SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置
String getKind(); //连接点类型
StaticPart getStaticPart(); //返回连接点静态部分

ProceedingJoinPoint
环绕通知的入参,包含了joinPoint的所有api并且多了两个方法
Object proceed() throws Throwable (这里就连接点执行本类的方法)
通过反射执行目标对象连接点处的方法。
Object proceed(Object[] var1) throws Throwable
使用新的入参(替换掉原来的入参),通过反射执行目标对象连接点处的方法。

6.代码演示!

1.依赖
框架用的springboot

springboot父~
<parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.1.4.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
</parent>

aop测试依赖
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>
 <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
  </dependency>

下面是测试的几个类!
在这里插入图片描述

package com.aop.aspect.testaop;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

**目标执行类**
@Component
public class MyTest {
    public String add(String userId) throws Exception {

        if (StringUtils.isBlank(userId)){
            throw new Exception("我没有userId...我是个没有理想的咸鱼!");
        }
        System.out.println("我是userId..." + userId);
        return userId;
    }
}

切面类

package com.aop.aspect.testaop;

import com.aop.aspect.exception.DAOException;
import com.aop.aspect.exception.ErrorCodeConstants;
import com.aop.aspect.exception.PandoraException;
import com.aop.aspect.exception.PandoraResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.MDC;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.aspectj.lang.reflect.SourceLocation;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import static org.apache.commons.lang3.StringUtils.*;


@Aspect
@Component
@Slf4j
public class MyAop {

    /**
     *
     * 这里要写一个切点表达式,表示你要作用于哪个类,哪个方法
     * 写在方法上是因为@Pointcut()表达式要在类上才生效啊! 方法名可以自己定义但是要见名知意!方法内可以不写任何东西
     *
     * public * com.aop.aspect..*.*(..)
     * 表达式解释: public修饰的/任意返回值的/com.aop.aspect包下和子包下的/任意类/任意方法/任意参数
     *
     * 其他解释: 包后面的..表示当前包, *表示任意   (..)括号里的..表示任意参数你可以把他看成方法的括号就好理解了,传几个参数都可以什么类型都可以
     */
    @Pointcut("execution(public * com.aop.aspect..*.*(..))")
    public void cut(){
        System.out.println("我是一个切入点方法... ");
    }

    /**
     * 切点通知,你要在这里面做的方法增强逻辑就写在这里面,
     * cut()表示你要把Before这个通知作用到上面的切入点的范围内.
     * @param joinPoint
     */
    @Before("cut()")
    public void beforeAdvice(JoinPoint joinPoint){
        System.out.println(joinPoint);
        log.info("我是前置通知...我在方法执行前做处理");
        printPointAgs(joinPoint);

    }
	
	//后置通知
    @After(value = "cut()",argNames = "joinPoint")
    public void afterAdvice(JoinPoint joinPoint){
        log.info("我是后置通知,在方法执行之后做处理");
    }
	
	//正常返回通知
    @AfterReturning(value = "cut()",returning = "result")
    public void afterReturningAdvice(Object result){
        log.info("我是返回通知,可以用来做正常返回值有关的逻辑!");
    }
	
	//异常通知
    @AfterThrowing(value = "cut()",throwing="e")
    public void afterThrowingAdvice(Exception e){
        log.info("我是一个异常处理通知,在抛出异常的时候执行");
    }
    
	//环绕通知
    @Around(value = "cut()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
        log.info("我是环绕通知,我最强大! 我可以做任何事情!");
        //这里给一个案例!日志打印,并抛出自定义的异常!产生traceId
        long before = System.currentTimeMillis();
        String traceId = UUID.randomUUID().toString();
        try{
			//TraceIdUtil.set(traceId);
            //this.logInfo(pjp,traceId);
            MDC.put("traceId", traceId);
            return pjp.proceed();//执行该方法
        }catch (PandoraException pandoraException) {
            this.logError(pjp, pandoraException);
            if(isNotBlank(pandoraException.getErrorMSG())){
                return PandoraResult.fail(pandoraException.getErrorCode(), pandoraException.getErrorMSG());
            }
            return PandoraResult.fail(pandoraException);
        } catch (DAOException daoException) {
            this.logError(pjp, daoException);
            return PandoraResult.fail(ErrorCodeConstants.DA0_ERROR);
        } catch (Exception e) {
            this.logError(pjp, e);
            return PandoraResult.fail(ErrorCodeConstants.SERVICE_ERROR);
        } catch (Throwable e){
            this.logError(pjp, e);
            return PandoraResult.fail(ErrorCodeConstants.SERVICE_ERROR);
        }finally{
            this.logInfoRT(pjp, traceId, before);
            MDC.remove("traceId");
        }
    }

	//参数打印!
    private void printPointAgs(JoinPoint joinPoint) {
        System.out.println("================joinPoint参数列表!===================");
        String string = joinPoint.toString(); // execution(String com.aop.aspect.testaop.MyTest.add(String))
        log.info("string {}",string);
        String shortString = joinPoint.toShortString();//execution(MyTest.add(..))
        log.info("shortString {}",shortString);
        String longString = joinPoint.toLongString();//execution(public java.lang.String com.aop.aspect.testaop.MyTest.add(java.lang.String))
        log.info("longString {}",longString);
        Object aThis = joinPoint.getThis();//com.aop.aspect.testaop.MyTest@3711c71c
        log.info("aThis {}",aThis);
        Object target = joinPoint.getTarget();//com.aop.aspect.testaop.MyTest@3711c71c
        log.info("target {}",target);
        Object[] args = joinPoint.getArgs();//闲鱼~
        log.info("args {}",args);
        Signature signature = joinPoint.getSignature();//String com.aop.aspect.testaop.MyTest.add(String)
        log.info("signature {}",signature);
        SourceLocation sourceLocation = joinPoint.getSourceLocation();//org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@30508066
        log.info("sourceLocation {}",sourceLocation);
        String kind = joinPoint.getKind();//method-execution
        log.info("kind {}",kind);
        JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();//execution(String com.aop.aspect.testaop.MyTest.add(String))
        log.info("staticPart {}",staticPart);
    }


    private Method getMethod(JoinPoint pjp) throws ClassNotFoundException, NoSuchMethodException {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Class<?> targetClass = Class.forName(pjp.getTarget().getClass().getName());
        Class[] classes = methodSignature.getMethod().getParameterTypes();
        return targetClass.getDeclaredMethod(pjp.getSignature().getName(), classes);
    }

    private void logInfoRT(JoinPoint joinPoint, String traceId, long before) {
        try {
            Method method = getMethod(joinPoint);
            log.info("Info@{}#{}-traceId={}:RT={}", method.getDeclaringClass().getName(), method.getName(), traceId, (System.currentTimeMillis() - before));
        } catch (Exception e) {
        }
    }


    private void logError(JoinPoint joinPoint, Throwable e){
        try{
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            StringBuilder sb = new StringBuilder();
            Method method = getMethod(joinPoint);
            Parameter[] parameters = method.getParameters();
            for (int i = 0; i < parameters.length; i++) {
                sb.append(parameters[i].getName()+"={},");
            }
            sb.append("errorMessage={}");
            List<Object> list = new ArrayList<Object>();
            for (Object object : arguments) {
                list.add(object);
            }
            if(e instanceof PandoraException){
                PandoraException exception = (PandoraException) e;
                list.add(exception.getErrorCode()+"-"+exception.getErrorMSG());
            }else{
                list.add(e.getMessage());
            }
            list.add(e);
            log.error("error@"+joinPoint.getTarget().getClass().getSimpleName()+"#"+methodName+":"+sb.toString(),list.toArray());
        }catch(Exception e1){}
    }
}

测试用例!

package com.aop.aspect.testaop;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@SpringBootTest
@RunWith(SpringRunner.class)
public class JUnitMyTest {
    @Resource
    private MyTest myTest;

    @Test
    public void aopTest() throws Exception {
        myTest.add("闲鱼~");
    }
}

运行的日志结果!
在这里插入图片描述

注意: 如果在切面类中写了@Around 环绕通知! 那么@Before前置通知是不会执行的! 环绕通知中是可以处理前置后置等一系列的操作!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值