• Spring-AOP入门


    AOP简介

    • AOP (Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
      • OOP(Object Oriented Programming) 面向对象编程
    • 作用:在不惊动原始设计的基础上为其进行功能增强
    • Spring理念:无入侵式/无侵入式
    • 本质是代理模式

    AOP核心概念

    1. 首先,找到程序中的共性功能,把它抽出来,写一个通知类,定义一个方法叫通知
    2. 把需要执行通知的方法找出来,定义成切入点
    3. 将通知和切入点绑定得到切面
    • 连接点( JoinPoint ) : 程序执行过程中的任意位置, 粒度为执行方法、抛出异常、设置变量等
      • 在SpringAOP中,理解为方法的执行
    • 切入点( Pointcut ) :匹配连接点的式子
      • 在SpringAOP中 , 一个切入点可以只描述一个具体方法,也可以匹配多个方法
        • 一个具体方法: com. itheima. dao包下的BookDao接口中的无形参无返回值的save方法
        • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以ao结尾的接口中的任意方法,所有带有一个参数的方法
    • 通知( Advice ) : 在切入点处执行的操作,也就是共性功能
      • 在SpringAOP中 ,功能最终以方法的形式呈现
    • 通知类:定义通知的类
    • 切面( Aspect ) : 描述通知与切入点的对应关系
       

    AOP入门案例

    案例设定:测定接口执行效率

    简化设定:在接口执行前输出当前系统时间

    开发模式: XML or 注解

    目录结构:

     

    思路分析:

    1. 导入坐标( pom. xml )
      1. 其中context依赖了aop
      2. <dependency>
      3. <groupId>org.springframeworkgroupId>
      4. <artifactId>spring-contextartifactId>
      5. <version>5.2.10.RELEASEversion>
      6. dependency>
      7. <dependency>
      8. <groupId>org.aspectjgroupId>
      9. <artifactId>aspectjweaverartifactId>
      10. <version>1.9.4version>
      11. dependency>

    2. 制作连接点方法(原始操作,Dao接口与实现类)
      1. @Repository
      2. public class BookDaoImpl implements BookDao {
      3. public void save() {
      4. System.out.println(System.currentTimeMillis());
      5. System.out.println("book dao save...");
      6. }
      7. @Override
      8. public void update() {
      9. System.out.println("book dao update...");
      10. }
      11. }

    3. 制作共性功能
      1. public class MyAdvice {
      2. public void method(){
      3. System.out.println(System.currentTimeMillis());
      4. }
      5. }

    4. 定义切入点
      1. public class MyAdvice {
      2. //制作切入点
      3. @Pointcut("execution(void com.hyk.dao.BookDao.update())")
      4. private void pt(){
      5. }
      6. //制作共性功能
      7. public void method(){
      8. System.out.println(System.currentTimeMillis());
      9. }
      10. }

    5. 绑定切入点与通知的关系
      1. public class MyAdvice {
      2. //制作切入点
      3. @Pointcut("execution(void com.hyk.dao.BookDao.update())")
      4. private void pt(){
      5. }
      6. //制作共性功能
      7. @Before("pt()")
      8. public void method(){
      9. System.out.println(System.currentTimeMillis());
      10. }
      11. }

    6. 定义通知类受Spring容器管理,并且定义当前类为切面类,给MyAdvice类添加注解@Component 和@Aspect
    7. 开启Spring对AOP注解驱动支持给SpringConfig类添加注解@EnableAspectJAutoProxy

    AOP工作流程

    1. Spring容器启动
    2. 读取所有切面配置中的切入点
    3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
      1. 匹配失败,创建对象
      2. 匹配成功,创建原始对象(目标对象)的代理对象
    4. 获取bean执行方法
      1. 获取bean,调用方法并执行,完成操作
      2. 获取bean是代理对象时,根据 代理对象的运行模式运行原始方法与增强的内容,完成操作。

    目标对象( Target ) : 原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
    代理( Proxy ) :目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
     

    AOP切入点表达式

    切入点:要进行增强的方法

    切入点表达式:要进行增强的方法的描述方式

    execution(void com.hyk.dao.BookDao.update())

    描述方式一:执行com.hyk.dao包下的BookDao接口中的无参数update方法

    execution(void com.hyk.dao.impl.BookDaoImpl.update())

    描述方式一:执行com.hyk.dao.impl包下的BookDaoImpl接口中的无参数update方法

    描述实现类和描述接口的都是OK的

    切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)

    • 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
    • 访问修饰符: public , private等,可以省略
    • 返回值
    • 包名
    • 类/接口名
    • 方法名
    • 参数
    • 异常名:方法定义中抛出指定异常,可以省略
       

    可以使用通配符描述切入点,快速描述

    • *  :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现 
      1. execution(public * com.hyk.*.UserService.find*(*))
      2. 匹配com.hyk包下的任意包中的UserService类或者接口中所有find开头的带有一个参数的方法
    • .. :多个连续的任意符号, 可以独立出现,常用于简化包名与参数的书写
      1. execution(public User com..UserService.findById(..))
      2. 匹配com包下的任意包中的UserService类或者接口中所有名称为findById的方法
    • + :专用于匹配子类类型
      1. execution(* *..*Service+.*(..))

    书写技巧

    • 所有代码按照标准规范开发,否则以下技巧全部失效
    • 描述切入点通常描述接口,而不描述实现类
    • 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
    • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用通配快速描述
    • 包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配
    • 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service ,绑定业务层接口名
    • 方法名书写以动词进行精准匹配,名词采用*匹配,例如getByld书写成getBy *,selectAll书写成selectAlI
    • 参数规则较为复杂,根据业务方法灵活调整
    • 通常不使用异常作为匹配规则
       

    AOP通知类型

    1. AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
    2. AOP通知共分为5种类型
      1. 前置通知@Before
        1. @Before("pt()")
        2. public void before(){
        3. System.out.println(
        4. "before......."
        5. );
        6. }
      2. 后置通知
        1. @After("pt()")
        2. public void after(){
        3. System.out.println(
        4. "after......."
        5. );
        6. }

      3. 环绕通知(重点)
        1. @Around("pt()")
        2. public Object around(ProceedingJoinPoint pjp) throws Throwable {
        3. System.out.println(
        4. "around before......."
        5. );
        6. //表示对原始操作的调用
        7. Object proceed = pjp.proceed();
        8. System.out.println(
        9. "around after......."
        10. );
        11. return proceed;
        12. }

      4. 返回后通知(了解)
        1. @AfterReturning("pt()")
        2. public void afterReturning(){
        3. System.out.println(
        4. "afterReturning......."
        5. );
        6. }

      5. 抛出异常后通知(了解)
        1. @AfterThrowing("pt()")
        2. public void afterThrowing(){
        3. System.out.println(
        4. "afterThrowing......."
        5. );
        6. }
      6. 结果:
        1. around before.......
        2. before.......
        3. book dao update...
        4. afterReturning.......
        5. after.......
        6. around after.......
        7. Process finished with exit code 0

    @Around注意事项

    • 环绕通知必须依赖形 参ProceedingJoinPoint才能实现对原始方法的调用, 进而实现原始方法调用前后同时添加通知
    • 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
    • 对原始方法的调用可以不接收返回值 , 通知方法设置成void即可,如果接收返回值,必须设定为Object类型
    • 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void ,也可以设置成Object
    • 由于无法预知原始方法运行后是否会抛出异常 ,因此环绕通知方法必须抛出Throwable对象
       

    AOP案例-业务层接口执行效率

    测量业务层接口执行效率

     

    AOP类代码:

    1. @Component
    2. @Aspect
    3. public class ProjectAdvice {
    4. //匹配业务层的所有方法
    5. @Pointcut("execution(* com.hyk.service.*Service.*(..))")
    6. private void servicePoint(){
    7. }
    8. @Around("ProjectAdvice.servicePoint()")
    9. public void runSpeed(ProceedingJoinPoint proceedingJoinPointp) throws Throwable {
    10. //获取一次执行的签名信息
    11. Signature signature = proceedingJoinPointp.getSignature();
    12. //类型名
    13. String className = signature.getDeclaringTypeName();
    14. //方法名
    15. String methodName =signature.getName();
    16. //获取开始时间
    17. long startTime=System.currentTimeMillis();
    18. for (int i =0;i<10000;i++){
    19. Object proceed = proceedingJoinPointp.proceed();
    20. }
    21. //获取结束时间
    22. long endTime=System.currentTimeMillis();
    23. System.out.println(className+"."+methodName+"执行万次运行时间: "+(endTime-startTime)+"ms");
    24. }
    25. }

    执行结果:

    com.hyk.service.UserService.findById执行万次运行时间: 4530ms
    null

    Process finished with exit code 0

  • 相关阅读:
    并发编程面试题
    antv x6 沿边图标循环动画实现
    m基于Lorenz混沌自同步的混沌数字保密通信系统的FPGA实现,verilog编程实现,带MATLAB混沌程序
    15 OpenCV Sobel算子
    python编程从入门到实践2——列表
    [读论文] Monocular 3D Object Reconstruction with GAN inversion (ECCV2022)
    TCP的延时应答和捎带应答详解
    Java电商平台 - API 接口设计之 token、timestamp、sign 具体架构与实现
    基于 QT 实现一个 Ikun 专属桌面宠物
    化整为零优化重用,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang函数的定义和使用EP07
  • 原文地址:https://blog.csdn.net/u011572366/article/details/125911329