Spring高级技术AOP

AOP

  • AOP概述:

(1)AOP是什么:

是面向切面编程、面向方面编程,其实就是面向特定方法编程。

面向一个或者是多个方法编程就叫做面向切面编程。

  1. 场景:

对业务方法进行功能的扩展,或者是更改些许功能的情形的时候;由于一个业务的功能有很多,方法也有很多,当我们在每个业务功能里面都扩展功能的话会造成代码的冗余,降低效率。

这个时候我们就需要抽取出一个模版,将这么多的功能放在模版当中。这个过程就可以使用AOP的技术。

注意:当我们运行业务时候,程序就不会去调用业务程序功能了,而是直接调用AOP模版了。

例子:比如统计部门和员工当中所有方法的执行耗时。

  1. 动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。
  • AOP快速入门
  1. 导入依赖:在pom.xml中导入AOP的依赖

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-aop</artifactId>

</dependency>

  1. 编写AOP程序:针对于特定方法根据业务需要进行编程

我们就拿统计方法的执行时间来举例:

@Component
@Aspect
@Slf4j
public class TimeAspect {
    //切入点表达式,用来指定如下的方法是作用在哪些路径的方法里
    @Around("execution(* com.itheima.servise.impl.DeptServiceImpl.*(..))")
    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        //1.获取方法运行的开始时间
        long begin = System.currentTimeMillis();

        //2.调用运行原始方法
        //那我们怎么调用原始方法呢,我们就需要调用AOP给我们提供的ProceedingJoinPoint这个API
        //result就代表原始方法的返回值
        //其实在JoinPoint中就封装了原始方法的相关信息,然后调用proceed就将元素信息展示了出来
        Object result = joinPoint.proceed();

        //3.获取方法运行结束时间,计算执行耗时
        long end = System.currentTimeMillis();
        //可以使用joinPoint中的getSignature方法获取签名,
        // 那么我们就会知道是哪个原始方法被调用了
        log.info(joinPoint.getSignature()+"执行耗时 : {} ms", (end-begin));
        return result;
    }
}

  • AOP核心概念
  1. 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)那么衍生出基本上所有的方法都是AOP的连接点
  2. 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
  3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用,就是那个被注解Around修饰的那个东西
  4. 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)

  • AOP的执行流程

当我们使用AOP之后,我们就不会去执行原始对象,而是去执行基于目标对象生成的代理对象去执行了。

当目标对象(此处为 DeptserviceImpl )功能需要被增强时,并且我们使用AOP方式定义了增强逻(在Aspect类中)

Spring会为目标对象自动生成一个代理对象,并在代理对象对应方法中,结合我们定义的AOP增强逻辑完成功能增强。

  • AOP进阶
  1. 通知类型
  1. Before:在目标方法之前执行
  2. Around:在目标方法之前之后都执行
  3. After:在目标方法之后执行,有异常则不执行
  4. AfterReturning:在目标方法返回结果后执行
  5. AfterThrowing:在目标方法抛出异常后执行,不抛异常不执行

例子:

切面类:

@Aspect//Aspect注解修饰的类就是AOP类了
@Component
@Slf4j
public class TestAspect {

    //before在目标方法前被执行
    @Before("execution(* com.itheima.servise.impl.DeptServiceImpl.*(..))")//切入点
    public void before() {
        log.info("before...执行了");
    }

    //Arrount在目标方法前、后都被执行
    @Around("execution(* com.itheima.servise.impl.DeptServiceImpl.*(..))")//切入点
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("around...执行1");

        Object result = joinPoint.proceed();

        log.info("around...执行2");
        return result;
    }

    //after在目标方法后被执行,无论是否有异常
    @After("execution(* com.itheima.servise.impl.DeptServiceImpl.*(..))")//切入点
    public void after() {
        log.info("after...执行了");
    }

    //afterReturning在目标方法后被执行,有异常不会执行
    @AfterReturning("execution(* com.itheima.servise.impl.DeptServiceImpl.*(..))")//切入点
    public void AfterReturning() {
        log.info("afterreturning...方法执行了!");
    }

    //afterThrowing方法发生异常后执行
    @AfterThrowing("execution(* com.itheima.servise.impl.DeptServiceImpl.*(..))")//切入点
    public void afterThrowing() {
        log.info("afterThrowing...执行了!");
    }
}

目标类:但是我们只使用PostMan 对目标类当中的一个方法进行测试(任选一个)

@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;
    @Autowired
    private EmpMapper empMapper;
    @Override
    public List<Dept> getAllDeptInfo() {
        List<Dept> selectAllDeptInfo = deptMapper.selectAllDeptInfo();
        return selectAllDeptInfo;
    }

    //启用事务回滚完成对应部门的相关删除
    @Override
    @Transactional//将这个方法交由事务进行管理
    public void deleteDeptById(Integer id) {
        //删除部门
        deptMapper.deleteDept(id);
        //int i = 1 / 0;
        //删除员工
        empMapper.deleteBydept_id(id);
    }

    @Override
    public void saveDept(Dept dept) {
        dept.setUpdateTime(LocalDateTime.now());
        dept.setCreateTime(LocalDateTime.now());
        deptMapper.insertDept(dept);
    }

    //回显部门信息
    @Override
    public Dept getById(Integer id) {
       Dept dept= deptMapper.getById(id);
        return dept;
    }

    @Override
    public void update(Dept dept) {
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.update(dept);
    }
}

注意事项:

@Around环绕通知需要自己调用ProCeedingJoinPoint.proceed()来让原始类执行,其他通知不需要考虑目标方法执行

@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。

引申:

   我们不难发现,每一个通知后面都跟上了相同的切面表达式,造成了代码的冗余,也不方便后期的更改,那么我们应该怎么样去去重呢

   我们可以创建一个方法空的方法,在方法上面用切入点构建一个切面表达式,之后就可以直接在其他的通知上面引入:例如

@Aspect//Aspect注解修饰的类就是AOP类了
@Component
@Slf4j
public class TestAspect {
    @Pointcut("execution(* com.itheima.servise.impl.DeptServiceImpl.*(..))")
    public void pt() {
    }
    //before在目标方法前被执行
    @Before("pt()")//切入点
    public void before() {
        log.info("before...执行了");
    }
    //Arrount在目标方法前、后都被执行
    @Around("pt()")//切入点
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("around...执行1");

        Object result = joinPoint.proceed();

        log.info("around...执行2");
        return result;
    }

    //after在目标方法后被执行,无论是否有异常
    @After("pt()")//切入点
    public void after() {
        log.info("after...执行了");
    }

    //afterReturning在目标方法后被执行,有异常不会执行
    @AfterReturning("pt()")//切入点
    public void AfterReturning() {
        log.info("afterreturning...方法执行了!");
    }

    //afterThrowing方法发生异常后执行
    @AfterThrowing("pt()")//切入点
    public void afterThrowing() {
        log.info("afterThrowing...执行了!");
    }
}

注:我们使用的这个用@PointCut注解使用的切入点,也可以被其他的切面类使用,但是需要注意的是,我们需要将这个连接点对应的类设置成public才可以使用

该注解的作用是将公共的切点表达式抽取出来,需要用到时引用改切点表达式即可。

  1. 通知的执行顺序

1.不同切面类中,默认按照切面类的类名字母排序

目标方法前的通知方法:字母排名靠前的先执行

目标方法后的通知方法:字母排名靠前的后执行

2. 用 @Order(数字) 加在切面类上来控制顺序

目标方法前的通知方法:数字小的先执行

目标方法后的通知方法:数字小的后执行

  1. 切入点表达式

切入点表达式是描述切入点方法的一种表达式

作用:主要用来决定项目中的哪些方法需要加入通知

常见形式:

Execution(...):根据方法的签名来匹配

@annotation(...):根据注解匹配

  1. Execution

   主要是根据方法的返回值、包名、类名、方法名、参数等信息来匹配,语法:

Execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数)throws异常?)

其中:带?的表示可以省略的部分

访问修饰符:可省略(比如:public、protected)

包名.类名:可省略

Throws 异常:可省略(注意是方法上声明的异常,不是实际抛出的异常)

@Before(“execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))”)

Public void before(JoinPoint joinPoint)

 

 

 

  1. @annotation

自定义一个注解,想使用哪个方法就在该方法上面的通知后加上@annotation(com.itheima.anno.Log)

将自定义注解加入到该方法上面,在通知后加上自定义注解的全路径

 

 

@Retention用来注解这个注解在什么时候生效,设置为RUNTIME意思是在运行时生效

@Target用来注解这个注解作用在什么地方,设置为METHOD意思是注解在方法上有效。

  1. 连接点

  可以被AOP控制的方法暗含其延伸的对象

在spring 中用 JinPoint 抽象了连接点,用它可以获得方法执行时的相关信息,如方法名、方法参数类型、方法实际参数等等。

对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint

对于其他四种通知,获取连接点信息只能使用 JoinPoint,它是 ProceedingJoinPoint 的父类型

@Slf4j
@Aspect
@Component
public class TestAspect2 {
    @Pointcut("execution(* com.itheima.servise.impl.DeptServiceImpl.*(..))")
    public void pt() {
    }
    @Around("pt()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //1.获取 目标对象的类名
        String name = joinPoint.getTarget().getClass().getName();
        //2.获取目标方法的方法名
        String methodName = joinPoint.getSignature().getName();
        //3.获取目标方法运行时传入的参数
        Object[] args = joinPoint.getArgs();
        //4.放行 目标方法执行
        Object result = joinPoint.proceed();
        //5.获取目标对象运行的返回值
        log.info("目标对象运行的返回值:{}",result);
        return null;
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值