SpringAOP(控制反转)【看这一片文章就够了】

目录

一、SpringAOP

1.概述

1 什么是AOP

2 AOP的作用

3 AOP的底层

4 AOP相关概念

5 AOP开发前要明确的事情

2. AOP入门

小结:

3. AOP详解-通知类型

1 通知类型

2 通知方法里获取目标方法信息(备用)

3 小结

4. AOP详解-切入点表达式

1 execution

2 @annotation

3 抽取公用切入点表达式

4 小结

5. AOP详解-通知执行顺序

6. AOP练习

7. 小结


一、SpringAOP

1.概述

AOP是用于简化动态代理的使用

1 什么是AOP

  • AOP:Aspect Oriented Programming,面向切面编程。是通过预编译方式(aspectj)或者运行期动态代理(Spring)实现程序功能的统一维护的技术。

  • AOP是OOP(Object Oriented Programming)的技术延续,是软件开发中的一个热点,也是Spring中的一个重要内容。利用AOP可以实现对业务逻辑各个部分之间的隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时提高了开发效率。

面试问题:
Spring的两大核心是什么:IoC和AOP
分别有什么作用
	IoC:控制反转,目的用于解耦,底层使用的技术是反射+工厂模式
	AOP:面向切面编程,目的是在不修改目标对象源码的情况下,进行功能增强,底层使用的是动态代理技术

2 AOP的作用

  • 作用:不修改源码的情况下,进行功能增强,通过动态代理实现的

  • 优势:减少重复代码,提高开发效率,降低耦合度方便维护

  • 比如:给功能增加日志输出, 事务管理的功能

3 AOP的底层

实际上,Spring的AOP,底层是通过动态代理实现的。在运行期间,通过代理技术动态生成代理对象,代理对象方法执行时进行功能的增强介入,再去调用目标方法,从而完成功能增强。

  • 常用的动态代理技术有:

    • JDK的动态代理:基于接口实现的

    • cglib的动态代理:基于子类实现的

  • Spring的AOP采用了哪种代理方式?

    • 如果目标对象有接口,就采用JDK的动态代理技术

    • 如果目标对象没有接口,就采用cglib技术

4 AOP相关概念

  • 目标对象(Target):要代理的/要增强的目标对象。

  • 代理对象(Proxy):目标对象被AOP织入增强后,就得到一个代理对象

  • 连接点(JoinPoint):能够被拦截到的点,在Spring里指的是方法。能增强的方法

  • 切入点(PointCut):要对哪些连接点进行拦截的定义。要增强的方法

  • 通知/增强(Advice):拦截到连接点之后要做的事情。如何增强,额外添加上去的功能和能力

  • 切面(Aspect):是切入点和通知的结合。 告诉Spring的AOP:要对哪个方法,做什么样的增强

  • 织入(Weaving):把增强/通知 应用到 目标对象来创建代理对象的过程

5 AOP开发前要明确的事情

有哪些事情需要我们完成的:

  • 目标类及里边的方法:完成功能的目标类。

  • 编写切面类,里边配置:

    • 切入点:要对谁进行增强

    • 通知:要做什么样的增强

有哪些事情是由SpringAOP完成的:

  • 代理对象:由SpringAOP根据我们的配置,帮我们生成的

  • 织入:根据切面配置生成代理对象的过程

2. AOP入门

需求

统计Service层每个方法的执行耗时

分析

目标类:Service层的所有类

编写切面类:

  • 切入点:Service层里所有的方法

  • 通知:计算方法执行耗时,打印出来

实现

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Demo01AopAspect

  • 类上加@Component

  • 类上加@Aspect 声明一下这个类是切面类

package com.itheima.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;


@Aspect
@Component
public class Demo01AopAspect {

    @Around("execution(public * com.itheima.service.impl.*.*(..))")
    public Object executeTime(ProceedingJoinPoint pjp) throws Throwable {

        long start = System.currentTimeMillis();
        
        //调用目标方法
        Object res = pjp.proceed();
        
        long end = System.currentTimeMillis();

        System.out.println(pjp.getSignature() + "方法耗时:" + (end - start) + "毫秒");

        return res;
    }

}

测试

打开浏览器访问 http://localhost:8080/depts

可看到控制台输出了执行时间

小结:

AOP的使用步骤:

  1. 添加依赖:spring-boot-starter-aop

  2. 编写切面类:类上要加 @Component, @Aspect

    类里编写通知方法:要额外增加上去的功能

    方法上加注解 @Around("切入点表达式")

3. AOP详解-通知类型

1 通知类型

Spring的AOP支持以下5种通知类型:

package com.itheima.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 当前类是一个切面配置类:
 *      类上加@Component:当前类对象必须交给Spring进行管理。不给Spring管理,Spring就不能增强
 *      类上加@Aspect:告诉Spring,这个类是切面配置类
 * 需求:
 *      对Service层的每个方法统计执行时间
 */
@Aspect
@Component
public class Demo01Aspect {

    /**
     * 方法上加的注解:@Around("切入点表达式"),用于选择对哪些方法进行增强
     * @param pjp 目标方法的信息,被增强的那个方法
     * @return
     */
    @Around("execution(public * com.itheima.service.*.*(..))")
    public Object executeTime(ProceedingJoinPoint pjp) throws Throwable {

        long start = System.currentTimeMillis();
        //调用目标方法,得到返回值
        Object res = pjp.proceed();

        long end = System.currentTimeMillis();
        System.out.println("目标方法执行耗时:" + (end - start) + "毫秒");
        return res;
    }

    /**
     * 需求:要在Service的每个方法执行之前,先输出一句话:前置通知执行了
     * 分析:
     *      切入点:要对谁增强,Service的所有方法
     *      通知:要额外增加的功能,输出“前置通知执行了”
     *      通知类型:要什么时候进行增强,在目标方法执行之前
     */
    @Before("execution(public * com.itheima.service.*.*(..))")
    public void before(){
        System.out.println("前置通知执行了");
    }

    /**
     * 需求:要在Service的每个方法执行抛出异常时,输出一句话:异常通知执行了
     * 分析:
     *      切入点:要对谁增强,Service的所有方法
     *      通知:要额外增加的功能,输出“异常通知执行了”
     *      通知类型:要什么时候进行增强,在目标方法执行抛出异常之后
     */
    @AfterThrowing("execution(public * com.itheima.service.*.*(..))")
    public void afterThrowing(){
        System.out.println("异常通知执行了");
    }

    /**
     * 需求:要在Service的每个方法正常执行完成之后,输出一句话:后置通知执行了
     * 分析:
     *      切入点:要对谁增强,Service的所有方法
     *      通知:要额外增加的功能,输出“后置通知执行了”
     *      通知类型:要什么时候进行增强,在目标方法正常执行完成之后
     */
    @AfterReturning("execution(public * com.itheima.service.*.*(..))")
    public void afterReturning(){
        System.out.println("后置通知执行了");
    }

    /**
     * 需求:要在Service每个方法执行之后,必须执行,输出一句话:最终通知执行了
     * 分析:
     *      切入点:要对谁增强,Service的所有方法
     *      通知:要额外增加的功能,输出“最终通知执行了”
     *      通知类型:要什么时候进行增强,在目标方法执行之后,必须执行
     */
    @After("execution(public * com.itheima.service.*.*(..))")
    public void after(){
        System.out.println("最终通知执行了");
    }
}

2 通知方法里获取目标方法信息(备用)

不同通知方法里,获取被调用的目标方法:

  • 可以直接给通知方法,直接加形参 JoinPoint

  • 如果是环绕通知方法,则要加形参 ProceedingJoinPoint,继承了JoinPoint

JoinPoint常用方法:

  • getArgs():获取调用方法时的实参值

  • getTarget():获取被调用的目标对象(原始目标对象)

  • getThis():获取当前代理对象

  • getSignature():获取被调用的方法信息。Signature对象的方法:

    • getName():获取被调用的方法名。比如:queryAllDepts

    • getDeclaringType():获取被调用方法所属的类Class

    • getDeclaringTypeName():获取被调用方法所属的类的全限定类名

    • toLongString():获取方法的完整名称。

      比如:public java.util.List com.itheima.service.impl.DeptServiceImpl.queryAllDepts()

    • toShortString:获取方法的简短名称。

      比如:DeptServiceImpl.queryAllDepts()

    • toString:获取方法信息。

      比如:List com.itheima.service.impl.DeptServiceImpl.queryAllDepts()

package com.itheima.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 当前类是一个切面配置类:
 *      类上加@Component:当前类对象必须交给Spring进行管理。不给Spring管理,Spring就不能增强
 *      类上加@Aspect:告诉Spring,这个类是切面配置类
 * 需求:
 *      对Service层的每个方法统计执行时间
 * @author liuyp
 * @since 2023/08/25
 */
@Aspect
@Component
public class Demo01Aspect {

    /**
     * 方法上加的注解:@Around("切入点表达式"),用于选择对哪些方法进行增强
     * @param pjp 目标方法的信息,被增强的那个方法
     * @return
     */
    @Around("execution(public * com.itheima.service.*.*(..))")
    public Object executeTime(ProceedingJoinPoint pjp) throws Throwable {

        long start = System.currentTimeMillis();
        //调用目标方法,得到返回值
        Object res = pjp.proceed();

        long end = System.currentTimeMillis();
        System.out.println("目标方法执行耗时:" + (end - start) + "毫秒");
        return res;
    }

    /**
     * 需求:要在Service的每个方法执行之前,先输出一句话:前置通知执行了
     * 分析:
     *      切入点:要对谁增强,Service的所有方法
     *      通知:要额外增加的功能,输出“前置通知执行了”
     *      通知类型:要什么时候进行增强,在目标方法执行之前
     */
    @Before("execution(public * com.itheima.service.*.*(..))")
    public void before(JoinPoint joinPoint){
        //调用目标方法时的实参值
        Object[] args = joinPoint.getArgs();
        //调用的目标类对象
        Object target = joinPoint.getTarget();
        //当前代理类对象
        Object aThis = joinPoint.getThis();
        //调用的目标方法
        Signature signature = joinPoint.getSignature();
        System.out.println("前置通知执行了, 目标类是:" + target.getClass().getName() + ", 目标方法:" + signature + ", 方法实参是:" + Arrays.toString(args));
    }

    /**
     * 需求:要在Service的每个方法执行抛出异常时,输出一句话:异常通知执行了
     * 分析:
     *      切入点:要对谁增强,Service的所有方法
     *      通知:要额外增加的功能,输出“异常通知执行了”
     *      通知类型:要什么时候进行增强,在目标方法执行抛出异常之后
     */
    @AfterThrowing("execution(public * com.itheima.service.*.*(..))")
    public void afterThrowing(JoinPoint joinPoint){
        System.out.println("异常通知执行了, joinPoint = " + joinPoint);
    }

    /**
     * 需求:要在Service的每个方法正常执行完成之后,输出一句话:后置通知执行了
     * 分析:
     *      切入点:要对谁增强,Service的所有方法
     *      通知:要额外增加的功能,输出“后置通知执行了”
     *      通知类型:要什么时候进行增强,在目标方法正常执行完成之后
     */
    @AfterReturning("execution(public * com.itheima.service.*.*(..))")
    public void afterReturning(JoinPoint joinPoint){
        System.out.println("后置通知执行了, joinPoint = " + joinPoint);
    }

    /**
     * 需求:要在Service每个方法执行之后,必须执行,输出一句话:最终通知执行了
     * 分析:
     *      切入点:要对谁增强,Service的所有方法
     *      通知:要额外增加的功能,输出“最终通知执行了”
     *      通知类型:要什么时候进行增强,在目标方法执行之后,必须执行
     */
    @After("execution(public * com.itheima.service.*.*(..))")
    public void after(JoinPoint joinPoint){
        System.out.println("最终通知执行了, joinPoint = " + joinPoint);
    }
}

3 小结

通知类型:

  • @Around:环绕通知。最灵活的通知方式,必须掌握的方式

  • @Before:前置通知。通知方法在 目标方法执行之前 先执行

  • @AfterReturning:返回通知。通知方法在 目标方法正常执行之后 再执行

  • @AfterThrowing:异常通知。通知方法在 目标方法抛出异常之后 再执行

  • @After:最终通知/后置通知。通知方法在 目标方法执行之后 一定会执行,无论是否有异常

4. AOP详解-切入点表达式

1 execution

用于根据方法的名称进行模糊匹配的

语法:execution(权限修饰符 返回值类型 全限定类名.方法名(形参列表))

  • *:可使用通配符来表示模糊匹配

  • ..:用于形参列表里,表示任意个任意形参; 用于其它地方,表示任意层级的package包

  • 权限修饰符写public,通常省略不写

示例:

  • public void com.itheima.service.XxService.delete(Integer)

  • void com.itheima.service.XxService.delete(Integer)

  • * com.itheima.service.XxService.delete(Integer)

  • * com.itheima.service.*.delete(Integer)

  • * com.itheima..*.delete(Integer)

  • * com.itheima..*.*(Integer)

  • * com.itheima..*.*(Integer, String)

  • * com.itheima..*.*(Integer, *)

  • * com.itheima..*.*(..)

package com.itheima.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 切入点表达式:
 *      语法:`execution(权限修饰符 返回值类型 全限定类名.方法名(形参列表))`
 *      详解:
 *          权限修饰符:通常是`public`,可省略不写
 *          返回值类型:可以精确写,也可以用通配符*
 *             execution(String com.itheima.service.*.*(..))
 					表示要选中com.itheima.service下任意类的、返回值是String类型的方法
 *             execution(void com.itheima.service.*.*(..))
 					表示要选中com.itheima.service下任意类的、返回值是void类型的方法
 *             execution(* com.itheima.service.*.*(..)),
 					表示要选中com.itheima.service下任意类的、返回值是任意类型的方法
 *          全限定类名:
 *              可以精确写,execution(* com.itheima.service.DeptService.queryAllDepts(..))
 *                        表示选中com.itheima.service.DeptService类下名称为queryAllDepts方法
 *              可以用通配符,execution(* com.itheima.service.*.queryAllDepts(..))
 *                        表示选中com.itheima.service.包下任意类下名称为queryAllDepts方法
 *              可以使用..表示后代,execution(* com.itheima..*.*(..))
 *                        表示选中com.itheima包下所有的类(包括这个包里的类,以下所有下级包里的类)的所有方法
 *         方法名:可以精确写,也可以用通配符*
 *              如果写:execution(* com.itheima..*.queryAllDepts(..))
 *                     表示选中com.itheima包下所有类里queryAllDepts方法
 *              如果写:execution(* com.itheima..*.*(..))
 *                     表示选中com.itheima包下所有类里任意方法
 *         形参列表:
 *              可以精确写,
 *                  execution(* com.itheima..*.*(String)) 
 						表示选中com.itheima包下所有类的任意方法,但是要求方法有1个形参必须是String
 *                  execution(* com.itheima..*.*(String, Integer)) 
 						表示选中com.itheima包下所有类的任意方法,但是要求方法有2个形参按顺序必须是String、Integer
 *              也可以用通配符*
 *                  execution(* com.itheima..*.*(String, *)) 
 						表示选中com.itheima包下所有类的任意方法,但是要求方法有2个形参按顺序必须是String、任意类型
 *              任意个任意类型:..
 *                  execution(* com.itheima..*.*(..)) 
 					表示选中com.itheima包下任意类里任意方法,方法的形参个数和类型都不限制
 *     示例:execution(* *..*.*(..)) 表示选择任意包下、任意类的任意方法,形参不限,返回值不限。不要用
 * @author liuyp
 * @since 2023/08/25
 */
@Aspect
@Component
public class Demo02Aspect {

    @Before("execution(* com.itheima.service.*.*(..))")
    public void before(){
        System.out.println("=====Demo02Aspect.before执行了======");
    }


    @After("execution(* com.itheima.service.*.*(..))")
    public void after(){
        System.out.println("=====Demo02Aspect.after执行了======");
    }
}

2 @annotation

根据注解选择要增强的方法

语法:@annotation(注解的全限定类名),会选择所有加了指定注解的方法,进行增强

示例:

  1. 自定义注解


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}

2.创建切面配置类

package com.itheima.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;


@Aspect
@Component
public class Demo03Aspect {

    @Before("@annotation(com.itheima.aop.MyLog)")
    public void saveLogs(){
        System.out.println("========保存日志信息======");
    }
}

3.给DeptServiceImpl的queryAllDepts方法上加注解@MyLog

3 抽取公用切入点表达式

语法:

  • 定义公用的切入点表达式:在切面类里创建一个方法,方法上加注解@Pointcut("公用的切入点表达式")

  • 引用公用的切入点表达式:

    • 完整用法:Before("com.itheima.aop.Demo02Aspect.pc()")

    • 简写形式:Before("pc()"),仅适合于 切入点方法 和 通知方法在同一个类里

package com.itheima.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 如果要抽取公用的切入点表达式:
 *      使用注解@Pointcut("公用的切入点表达式")
 *      把注解加在方法上
 * 当需要使用切入点表达式时:
 *      完整的用法:@Before("com.itheima.aop.Demo02Aspect.pc()")
 *      如果调用的同一个类里的切入点表达式方法,可以简写@Before("方法名()")。
 * @author liuyp
 * @since 2023/08/25
 */
@Aspect
@Component
public class Demo02Aspect {

    /**这个方法仅仅作为切入点表达式的载体*/
    @Pointcut("execution(* com.itheima.service.*.*(..))")
    public void pc(){}

    // @Before("execution(* com.itheima.service.*.*(..))")
    // @Before("com.itheima.aop.Demo02Aspect.pc()")
    @Before("pc()")
    public void before(){
        System.out.println("=====Demo02Aspect.before执行了======");
    }


    // @After("execution(* com.itheima.service.*.*(..))")
    @After("pc()")
    public void after(){
        System.out.println("=====Demo02Aspect.after执行了======");
    }
}

4 小结

切入点表达式:用于 选中要增强的方法

语法:

  • 根据方法名选择:execution(权限修饰符 返回值类型 全限定类名.方法名(形参列表))

  • 根据注解选择:@annotation(注解的全限定类名)

如何抽取公用切入点表达式

  • 在切面类里定义一个方法,方法上加@Pointcut("切入点表达式")

  • 需要使用切入点表达式时

    • 简写:这个方法和当前通知方法在 同一类里,可以简写 @Before("方法名()")

    • 完整:这个方法和当前通知方法不在同一类,完整写@Before("com.itheima.aspect.Demo01Aspect.pc()")

5. AOP详解-通知执行顺序

package com.itheima.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Aspect
//@Order(150)
@Component
public class Demo04Aspect {
    @Pointcut("execution(* com.itheima.service.*.*(..))")
    public void pc(){}

    @Before("pc()")
    public void before(){
        System.out.println("===Demo04Aspect.before===");
    }

    @AfterReturning("pc()")
    public void afterReturning(){
        System.out.println("===Demo04Aspect.afterReturning===");
    }

    @AfterThrowing("pc()")
    public void afterThrowing(){
        System.out.println("===Demo04Aspect.afterThrowing===");
    }

    @After("pc()")
    public void after(){
        System.out.println("===Demo04Aspect.after===");
    }

    @Around("pc()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        System.out.println("===Demo04Aspect.around前===");

        //调用目标方法
        Object res = pjp.proceed();

        System.out.println("===Demo04Aspect.around后===");

        return res;
    }
}
package com.itheima.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Aspect
//@Order(100)
@Component
public class Demo05Aspect {
    @Pointcut("execution(* com.itheima.service.*.*(..))")
    public void pc(){}

    @Before("pc()")
    public void before(){
        System.out.println("===Demo05Aspect.before===");
    }

    @AfterReturning("pc()")
    public void afterReturning(){
        System.out.println("===Demo05Aspect.afterReturning===");
    }

    @AfterThrowing("pc()")
    public void afterThrowing(){
        System.out.println("===Demo05Aspect.afterThrowing===");
    }

    @After("pc()")
    public void after(){
        System.out.println("===Demo05Aspect.after===");
    }

    @Around("pc()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        System.out.println("===Demo05Aspect.around前===");

        //调用目标方法
        Object res = pjp.proceed();

        System.out.println("===Demo05Aspect.around后===");

        return res;
    }
}

6. AOP练习

需求

将tlias案例中增、删、改相关接口的操作日志记录到数据库表中。

  • 就是当访问部门管理和员工管理当中的增、删、改相关功能接口时,需要详细的操作日志,并保存在数据表中,便于后期数据追踪。

操作日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长

准备

把《资料\AOP-日志案例\tlias-web-management2》拷贝到一个不含中文、空格、特殊字符的目录里,使用idea打开项目,启动引导类

再启动tlias的nginx

打开浏览器访问 http://localhost:90

SQL脚本

use tlias;
-- 操作日志表
create table operate_log(
    id int unsigned primary key auto_increment comment 'ID',
    operate_user int unsigned comment '操作人ID',
    operate_time datetime comment '操作时间',
    class_name varchar(100) comment '操作的类名',
    method_name varchar(100) comment '操作的方法名',
    method_params varchar(1000) comment '方法参数',
    return_value varchar(2000) comment '返回值',
    cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';

实体类

package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
    private Integer id; //ID
    private Integer operateUser; //操作人ID
    private LocalDateTime operateTime; //操作时间
    private String className; //操作类名
    private String methodName; //操作方法名
    private String methodParams; //操作方法参数
    private String returnValue; //操作方法返回值
    private Long costTime; //操作耗时
}

Mapper接口

package com.itheima.mapper;

import com.itheima.pojo.OperateLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OperateLogMapper {

    //插入日志数据
    @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
            "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
    void insert(OperateLog log);

}

分析:

选择AOP技术实现:

  • 如果不使用AOP,就需要修改源码,给大量的方法增加保存日志的代码。代码重复,日志和业务功能耦合到了一起

  • 希望:不修改源码的情况下,给Service层的方法进行增强,就使用AOP

要使用AOP技术:

  • 切入点:对哪些方法增强

    哪个方法需要保存日志,就在哪个方法上加一个自定义注解

    使用注解切入点表达式@annotation(自定义注解)

  • 通知:

    通知类型:使用@Around环绕通知

    通知方法:获取当前用户、当前时间、当前方法的类名和方法名、方法的实参、返回值,运行耗时,把这些信息保存到日志表

package com.itheima.aspect;

import com.itheima.mapper.OperateLogMapper;
import com.itheima.pojo.OperateLog;
import com.itheima.util.JwtUtils;
import io.jsonwebtoken.Claims;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;


@Aspect
@Component
public class OperateLogAspect {

    @Autowired
    private OperateLogMapper logMapper;

    @Autowired
    private HttpServletRequest request;

    @Around("execution(* com.itheima.service.*.*(..))")
    public Object logSave(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        //调用目标方法,得到返回值
        Object res = pjp.proceed();
        long end = System.currentTimeMillis();

        //保存日志信息
        OperateLog ol = new OperateLog();
        //填充日志数据-操作人id,当前登录的用户。客户端每次请求都会携带token到服务端,只要从请求里获取token就能得到当前用户
        String token = request.getHeader("token");
        if (token != null && !"".equals(token)) {
            Claims claims = JwtUtils.parseJWT(token);
            Integer userId = claims.get("id", Integer.class);
            ol.setOperateUser(userId);
        }
        //填充日志数据-操作时间
        ol.setOperateTime(LocalDateTime.now());
        //填充日志数据-目标类名
        Signature signature = pjp.getSignature();
        String className = signature.getDeclaringTypeName();
        ol.setClassName(className);
        //填充日志数据-目标方法名
        ol.setMethodName(signature.getName());
        //填充日志数据-方法的实参
        Object[] args = pjp.getArgs();
        ol.setMethodParams(Arrays.toString(args));
        //填充日志数据-方法的返回值
        ol.setReturnValue(res.toString());
        //填充日志数据-操作耗时
        ol.setCostTime(end-start);
        //保存到数据库里
        logMapper.insert(ol);

        String remoteAddr = request.getRemoteAddr();
        System.out.println("客户端ip:" + remoteAddr);
        return res;
    }
}

7. 小结

AOP是什么:面向切面编程,底层是动态代理

AOP的作用:用于在不修改目标对象源码的情况下,进行功能增强

AOP的底层:

  • JDK的动态代理:如果有接口,就使用JDK的

  • cglib动态代理:如果没有接口,就使用cglib

AOP的几个概念:

  • 切入点PointCut:要增强的方法

  • 通知Advice:要如何增强

  • 切面Aspect:切入点 + 通知。表示 要对哪些方法 做什么样的增强

AOP的使用:

  1. 添加依赖坐标 spring-boot-starter-aop

  2. 创建切面类:类上加@Component @Aspect;类里写通知方法,通知方法加注解配置切入点表达式

@Aspect
@Order(整数) //整数是排序号,值越小,执行的越早
@Component
public class DemoAspect{
    
    @PointCut("切入点表达式")
    public void pc(){}
    
    //环绕通知方法:固定写法
    @Around("pc()")
    public Object method(ProceedingJoinPoint pjp){
        //前边写的前置增强
        
        //调用目标方法
        Object res = pjp.proceed();
        
        //后边写的后置增强
        
        return res;
    }
    
    //其它通知方法:返回值void,可以无参,方法名随便。 如果需要用到目标方法,就加形参JoinPoint
    @Before("切入点表达式")
    public void before(){
    }
    @AfterThrowing("切入点表达式")
    public void before(){
    }
    @AfterReturning("切入点表达式")
    public void before(){
    }
    @After("切入点表达式")
    public void before(){
    }
}

通知类型:

  • @Before:通知方法在 目标方法执行之前 先执行

  • @AfterReturning:通知方法在 目标方法正常执行之后 再执行

  • @AfterThrowing:通知方法在 目标方法抛出异常之后 再执行

  • @After:通知方法在 目标方法执行之后,必定会执行,无论有没有异常

切入点表达式:这种表达式是用于选择 要增强的方法

  • 根据方法名选择:execution(权限修饰符 返回值类型 全限定类名.方法名(形参类型列表))

  • 根据注解选择:@annotation(注解的全限定类名)

多个切面的执行顺序:

  • 默认按 切面类的全限定类名 顺序执行

  • 如果要设置执行顺序,就在切面类上加 @Order(整数),整数越小,执行的越早

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/470445.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

opencv 傅里叶变换(低通滤波 + 高通滤波)

文章目录 1、傅里叶变换2、通过numpy实现3、高通滤波器5、通过opencv实现傅里叶变换6、低通滤波器 1、傅里叶变换 时域分析&#xff1a;以时间作为参照物&#xff0c;世间万物都是随着时间变化而变化&#xff0c;并且不会停止 频域分析&#xff1a;认为世间万物都是静止的&…

将html网页展示的图表,下载到PPT文档内,以图片的形式展示在PPT内

使用到的工具有&#xff1a; 开发工具&#xff1a;IDEA 报表开发工具&#xff1a;帆软10.0.19 1、针对帆软报表[普通报表]的设置 1.1首先选中在帆软里制作好的报表&#xff0c;选择模板web属性 1.2.选择数据分析模式&#xff0c;添加一个事件设置&#xff0c;该事件应该设置“…

使用甘特图实现高效时间规划

甘特图虽然看似简单,却蕴含着规划时间的奥秘。它将复杂的工序分解成逻辑严密的任务链条,每个短小的条形图块都清晰地道出一个任务的起始、持续和终止。就像指挥家挥舞手中的棒,每个动作都精确拍着节奏,确保各个乐手分工合作、行云流水。择一个好用的甘特图制作工具,会让你事半功…

HCIP作业

实验要求&#xff1a; 1、R6为ISP&#xff0c;接口IP地址均为公有地址&#xff0c;该设备只能配置IP地址&#xff0c;之后不能再对其进行任何配置&#xff1b; 2、R1-R5为局域网&#xff0c;私有IP地址192.168.1.0/24&#xff0c;请合理分配&#xff1b; 3、R1、R2、R4&#x…

空间解析几何之直线与平面:推导直线与直线、直线与平面交点

空间解析几何——直线与平面 三维空间中的直线和平面与二维空间中的性质有一定的类似之处&#xff0c;但是其相交关系的求解方式有所差异。本文回顾了三维空间中直线和平面的解析表达&#xff0c;然后推导线-线、线-面交点。 平面 空间平面的表达式为&#xff1a; A x B y…

React状态管理库快速上手-Redux(一)

基本使用 安装 pnpm install reduxjs/toolkit react-redux创建一个仓库 定义state createSlice相当于创建了一个模块仓库&#xff0c;initialState存放状态&#xff0c;reducers存放改变状态的方法。 import { createSlice } from reduxjs/toolkitexport const counterSli…

MATLAB环境下基于改进最大相关峭度解卷积的滚动轴承故障诊断

相关峭度解卷积MCKD是一种新的解卷积方法&#xff0c;其设计了一个新的目标函数—相关峭度&#xff0c;并以此为优化目标设计一系列的FIR滤波器&#xff0c;为实现最好的效果&#xff0c;需要从中找到最优滤波器并最终实现对信号中噪声的抑制和对信号中冲击成分的突出的目的。M…

产品推荐 | 基于XC7K325T的FMC接口万兆光纤网络验证平台

01、产品概述 TES307是一款基于XC7K325T FPGA的万兆光纤网络验证平台&#xff0c;板卡具有1个FMC&#xff08;HPC&#xff09;接口&#xff0c;4路SFP万兆光纤接口、4路SATA接口、1路USB3.0接口。 板载高性能的FPGA处理器可以实现光纤协议、SATA总线控制器、以及USB3.0高速串…

长安链Docker Java智能合约引擎的架构、应用与规划

#功能发布 长安链3.0正式版发布了多个重点功能&#xff0c;包括共识算法切换、支持java智能合约引擎、支持后量子密码、web3生态兼容等。我们接下来为大家详细介绍新功能的设计、应用与规划。 在《2022年度长安链开源社区开发者调研报告》中&#xff0c;对Java合约语言支持是开…

华为配置HTTPS服务器实验

配置HTTPS服务器示例 组网图形 图1 配置HTTPS服务器组网图 组网需求配置思路配置注意事项操作步骤配置文件 组网需求 如图1所示&#xff0c;用户通过Web方式访问网关设备AP。 为了防止传输的数据不被窃听和篡改&#xff0c;实现对设备的安全管理&#xff0c;网络管理员要…

Makefile编译make complie时报错的心路历程

本次报错是在Makefile文件里面找错 Makefile文件找错的方法很复杂&#xff0c;必须要有一双慧眼&#xff0c;一层一层剥离分析 第一个错误&#xff08;还有一个错在全志 76节 23&#xff1a;35的时候连接wiringPi库&#xff09; 第二个错误&#xff1a; undefined reference…

Java:类和对象

目录 1.面对对象的初步认识1.1 什么是面向对象&#xff1f;&#xff08;Java当中一切皆为对象&#xff09;1.2 面对对象与面对过程 2.类的定义和使用2.1简单认识类2.2 类的定义格式 3.类的实例化3.1 什么是实例化3.2类和对象的说明 4.this引用4.1为什么要使用this引用4.2 什么是…

docker小白第十四天之Portainer与CIG

Portainer简介 Portainer是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便地管理Docker环境&#xff0c;包括单机环境和集群环境。 Portainer命令安装 # 一个容器可以同时起多个-p端口&#xff0c;restartalways表示随时在线&#xff0c;重启机器后也…

O2OA红头文件流转与O2OA版式公文编辑器基本使用

O2OA开发平台在流程管理中&#xff0c;提供了符合国家党政机关公文格式标准&#xff08;GB/T 9704—2012&#xff09;的公文编辑组件&#xff0c;可以让用户在包含公文管理的项目实施过程中&#xff0c;轻松地实现标准化公文格式的在线编辑、痕迹保留、手写签批等功能。并且可以…

[uni-app] uni.createAnimation动画在APP端无效问题记录

文章目录 uni.createAnimation动画描述动画代码templatejs部分 问题原因改进方案template js部分改动git 改进截图 uni.createAnimation 动画描述 实现一个以左上角为锚点,以Z轴做平面抬起及落下的动画效果 动画代码 template <image v-if"showHot(item.cname)&quo…

锂电极片生产中机器视觉系统的多元检测能力

随着新能源行业的快速发展&#xff0c;锂电池作为核心组件&#xff0c;其生产质量受到了前所未有的关注。在锂电极片的生产过程中&#xff0c;机器视觉系统以其高精度、高效率的特点&#xff0c;成为了保障产品质量的关键工具。本文将探讨机器视觉系统在锂电极片生产中可以检测…

WanAndroid(鸿蒙版)开发的第六篇

前言 DevEco Studio版本&#xff1a;4.0.0.600 WanAndroid的API链接&#xff1a;玩Android 开放API-玩Android - wanandroid.com 其他篇文章参考&#xff1a; 1、WanAndroid(鸿蒙版)开发的第一篇 2、WanAndroid(鸿蒙版)开发的第二篇 3、WanAndroid(鸿蒙版)开发的第三篇 …

GO语言:函数、方法、面向对象

本文分享函数的定义、特性、defer陷阱、异常处理、单元测试、基准测试等以及方法和接口相关内容 1 函数 函数的定义 func 函数名(参数列表) (返回值列表) { // 函数体&#xff08;实现函数功能的代码&#xff09; } 匿名函数的定义就是没有函数名&#xff0c;可以当做一个函…

第十九章 linux部署scrapyd

文章目录 1. linux部署python环境1. 部署python源文件环境2. 下载python3. 解压安装包4. 安装5. 配置环境变量6. 检查是否安装成功7. 准备python使用的包8. 安装scrapyd 1. linux部署python环境 1. 部署python源文件环境 yum install gcc patch libffi-devel python-devel zl…

【源码阅读】EVMⅢ

参考[link](https://blog.csdn.net/weixin_43563956/article/details/127725385 大致流程如下&#xff1a; 编写合约 > 生成abi > 解析abi得出指令集 > 指令通过opcode来映射成操作码集 > 生成一个operation 以太坊虚拟机的工作流程&#xff1a; 由solidity语言编…
最新文章