Spring6-代理模式 / AOP切面编程

目录

1. 场景模拟

1.1. 声明接口

1.2. 创建实现类

1.3. 创建带日志功能的实现类

1.4. 提出问题

2. 代理模式

2.1. 概述

2.2. 静态代理

2.3. 动态代理

3. AOP切面编程

3.1. 概述

3.2. 相关术语

3.2.1. 横切关注点

3.2.2. 通知(增强)

3.2.3. 切面

3.2.4. 目标

3.2.5. 代理

3.2.6. 连接点

3.2.7. 切入点

3.3. 作用


1. 场景模拟

搭建子模块:spring6-aop


1.1. 声明接口

声明计算器接口Calculator,包含加减乘除的抽象方法

package com.sakurapaid.spring6.aop.demo;

/**
 * 计算器接口,定义了基本的四则运算方法。
 */
public interface Calculator {
    /**
     * 加法操作。
     * 
     * @param i 第一个操作数。
     * @param j 第二个操作数。
     * @return 两个操作数相加的结果。
     */
    int add(int i, int j);

    /**
     * 减法操作。
     * 
     * @param i 第一个操作数。
     * @param j 第二个操作数。
     * @return 两个操作数相减的结果。
     */
    int sub(int i, int j);

    /**
     * 乘法操作。
     * 
     * @param i 第一个操作数。
     * @param j 第二个操作数。
     * @return 两个操作数相乘的结果。
     */
    int mul(int i, int j);

    /**
     * 除法操作。
     * 
     * @param i 第一个操作数,作为被除数。
     * @param j 第二个操作数,作为除数。
     * @return 两个操作数相除的结果。
     */
    int div(int i, int j);
}

1.2. 创建实现类

package com.sakurapaid.spring6.aop.demo;

/**
 * 实现Calculator接口的计算类,提供基本的四则运算功能。
 */
public class CalculatorImpl implements Calculator {

    /**
     * 实现加法操作。
     * 
     * @param i 第一个操作数。
     * @param j 第二个操作数。
     * @return 两个操作数相加的结果。
     */
    @Override
    public int add(int i, int j) {

        int result = i + j;  // 计算加法结果

        System.out.println("方法内部 result = " + result);  // 打印结果

        return result;  // 返回结果
    }

    /**
     * 实现减法操作。
     * 
     * @param i 第一个操作数。
     * @param j 第二个操作数。
     * @return 两个操作数相减的结果。
     */
    @Override
    public int sub(int i, int j) {

        int result = i - j;  // 计算减法结果

        System.out.println("方法内部 result = " + result);  // 打印结果

        return result;  // 返回结果
    }

    /**
     * 实现乘法操作。
     * 
     * @param i 第一个操作数。
     * @param j 第二个操作数。
     * @return 两个操作数相乘的结果。
     */
    @Override
    public int mul(int i, int j) {

        int result = i * j;  // 计算乘法结果

        System.out.println("方法内部 result = " + result);  // 打印结果

        return result;  // 返回结果
    }

    /**
     * 实现除法操作。
     * 
     * @param i 第一个操作数。
     * @param j 第二个操作数。
     * @return 两个操作数相除的结果。
     */
    @Override
    public int div(int i, int j) {

        int result = i / j;  // 计算除法结果

        System.out.println("方法内部 result = " + result);  // 打印结果

        return result;  // 返回结果
    }
}

1.3. 创建带日志功能的实现类

package com.sakurapaid.spring6.aop.demo;

/**
 * 实现Calculator接口的计算类,提供基本的四则运算功能,并通过日志记录方法的调用过程。
 */
public class CalculatorLogImpl implements Calculator {

    @Override
    /**
     * 实现加法运算。
     * @param i 第一个加数。
     * @param j 第二个加数。
     * @return 两个数的和。
     */
    public int add(int i, int j) {

        System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);

        int result = i + j; // 计算结果

        System.out.println("方法内部 result = " + result);

        System.out.println("[日志] add 方法结束了,结果是:" + result);

        return result;
    }

    @Override
    /**
     * 实现减法运算。
     * @param i 被减数。
     * @param j 减数。
     * @return 两个数的差。
     */
    public int sub(int i, int j) {

        System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);

        int result = i - j; // 计算结果

        System.out.println("方法内部 result = " + result);

        System.out.println("[日志] sub 方法结束了,结果是:" + result);

        return result;
    }

    @Override
    /**
     * 实现乘法运算。
     * @param i 第一个乘数。
     * @param j 第二个乘数。
     * @return 两个数的积。
     */
    public int mul(int i, int j) {

        System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);

        int result = i * j; // 计算结果

        System.out.println("方法内部 result = " + result);

        System.out.println("[日志] mul 方法结束了,结果是:" + result);

        return result;
    }

    @Override
    /**
     * 实现除法运算。
     * @param i 被除数。
     * @param j 除数。
     * @return 两个数的商。
     */
    public int div(int i, int j) {

        System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);

        int result = i / j; // 计算结果

        System.out.println("方法内部 result = " + result);

        System.out.println("[日志] div 方法结束了,结果是:" + result);

        return result;
    }
}

1.4. 提出问题

  1. 现有代码缺陷

针对带日志功能的实现类,我们发现有如下缺陷:

  • 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力
  • 附加功能分散在各个业务功能方法中,不利于统一维护
  1. 解决思路

解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。

  1. 困难

解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。


2. 代理模式

2.1. 概述

代理模式的核心思想是为某个对象(称为目标对象真实对象)创建一个替代品(称为代理对象)。当客户端请求与目标对象交互时,实际上是与代理对象打交道。代理对象可以透明地转发请求到目标对象,也可以在转发请求前或后添加额外的操作,如权限检查、日志记录、缓存、延迟加载、数据预处理等。这些附加功能通常不属于目标对象的核心业务逻辑,但对系统的整体功能或性能有重要影响。


代理模式的运作机制:

  1. 代理对象目标对象实现了相同的接口或继承自同一基类,这意味着它们对外部世界(客户端)呈现出相同的接口,即客户端可以以相同的方式与两者交互。
  2. 当客户端调用代理对象的方法时,代理对象可以选择直接转发该调用给目标对象,或者在转发前/后执行一些额外的操作。这些操作可以包括但不限于:
    • 访问控制:如检查调用者的权限,决定是否允许访问目标对象的方法。
    • 功能增强:如在方法调用前后添加日志记录、统计计数、性能监控等。
    • 资源管理:如缓存目标对象的结果以提高性能,或者延迟加载目标对象直到真正需要时。
    • 复杂逻辑封装:将与目标对象交互的复杂流程(如网络通信、事务管理)封装在代理内,简化客户端代码。

通俗例子说明:

  • 广告商找大明星拍广告需要经过经纪人:在这个场景中,大明星是目标对象,拥有实际拍摄广告的能力。经纪人则是代理对象,他代表明星与广告商进行谈判、签订合同等工作。广告商并不直接与明星交流,而是通过经纪人这个代理来间接访问明星的服务。经纪人可能还会负责处理非核心演艺事务,如协调档期、管理酬劳等,这些都是明星业务逻辑之外的辅助工作。
  • 合作伙伴找大老板谈合作要约见面时间需要经过秘书:大老板是目标对象,拥有决策权和进行商业洽谈的能力。秘书作为代理对象,负责过滤和安排合作伙伴的会面请求。合作伙伴不直接联系大老板,而是通过秘书预约时间,秘书可能会先进行初步筛选,确保会面符合大老板的日程安排和其他要求。
  • 房产中介是买卖双方的代理:卖方和买方分别是各自交易过程中的目标对象,他们拥有房源或购房需求。房产中介作为代理,帮助双方寻找合适的交易对象,处理看房、议价、签约、过户等复杂流程。买卖双方无需直接对接所有细节,而是通过中介这一代理进行沟通和交易,中介在此过程中提供了诸如市场分析、法律咨询、手续代办等附加服务。

相关术语解释:

  • 代理:在代理模式中,代理是一个类或对象,它代表或封装了对目标对象的访问。代理负责接收客户端的请求,并根据需要执行额外的操作,最终将请求转发给目标对象或返回结果。代理可以增加、改变或限制对目标对象的访问。
  • 目标(或真实对象):目标对象是代理所代表的实际对象,它包含了代理所要提供服务的核心业务逻辑。客户端对目标对象的访问通常是由代理对象间接提供的,目标对象本身并不知晓代理的存在,专注于执行其核心职责。

总结起来,代理模式通过引入代理对象来控制对目标对象的访问,实现了职责分离和功能增强,有助于提高代码的可维护性和系统的灵活性。代理对象封装了非核心逻辑,使得这些逻辑可以独立于目标对象进行管理和扩展,同时保持了对客户端的透明性,即客户端无需关心是直接与目标对象交互还是通过代理进行交互。


2.2. 静态代理

静态代理是指在编写代码时,手动创建一个代理类来实现对目标对象的代理。这个代理类通常会遵循以下特点:

  1. 明确指定代理对象:静态代理类需要提前知道它要代理的是哪个具体的目标类,因此代理类通常会与某个已知接口(或抽象类)关联,确保代理类和目标类具有相同的接口。
  2. 手动编码代理逻辑:代理类中的每个方法都会包含两部分逻辑:一部分是针对该方法的额外功能(如日志记录、权限检查等),另一部分是调用目标对象相应方法以执行核心业务逻辑。
  3. 硬编码限制:由于代理类是在编译时就已经确定的,它的行为和功能也是固定的。如果需要为不同的目标类添加类似的代理逻辑,或者调整代理逻辑,就需要为每个目标类编写对应的代理类。这种情况下,代码的重复性高,难以灵活应对变化。

创建静态代理类:

package com.sakurapaid.spring6.aop.demo;

public class CalculatorStaticProxy implements Calculator {

    // 将被代理的目标对象声明为成员变量
    private final Calculator target;

    public CalculatorStaticProxy(Calculator target) {
        this.target = target;
    }

    @Override
    public int add(int i, int j) {

        // 附加功能由代理类中的代理方法来实现
        System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);

        // 通过目标对象来实现核心业务逻辑
        int addResult = target.add(i, j);

        System.out.println("[日志] add 方法结束了,结果是:" + addResult);

        return addResult;
    }

    @Override
    public int sub(int i, int j) {
        return 0;
    }

    @Override
    public int mul(int i, int j) {
        return 0;
    }

    @Override
    public int div(int i, int j) {
        return 0;
    }
}

静态代理虽然实现了解耦,即将附加功能(如日志)与核心业务逻辑分离,但它存在明显的局限性:

  • 缺乏灵活性:对于不同的目标类或同一目标类的不同方法,如果都需要添加类似日志这样的附加功能,必须为每个类或方法分别编写代理类和代理方法。这会导致代码重复,且随着需求变化,需要不断修改或新增代理类。
  • 无法集中管理:如果需要在系统中全局地应用某种通用的代理逻辑(如日志),静态代理方式会让这些逻辑分散在各个代理类中,不易于统一管理和维护。

基于上述问题,提出了使用动态代理技术的需求。动态代理允许在运行时根据需要动态地创建代理对象,其优势在于:

  • 无需手动编写代理类:动态代理通常通过编程框架(如 Java 的 JDK 动态代理或 CGLIB 等第三方库)在运行时自动为给定的目标对象生成代理类及其代理方法,无需手动编写大量代理类代码。
  • 通用性与集中管理:动态代理可以为任意符合一定条件(如实现特定接口)的目标对象提供代理,且附加功能(如日志)可以在一个集中位置定义和配置,适用于多种场景和对象,大大减少了代码重复。
  • 易于扩展和调整:由于代理逻辑是在运行时动态生成的,可以根据运行时条件或配置动态调整代理行为,使得系统更易于适应需求变化。

举个通俗易懂的例子就是:

静态代理

想象一下,你正在经营一家小店,雇了一位员工(目标对象)专门负责算账。为了监督员工的工作,你找了一个朋友(代理对象)帮忙。朋友的任务是:

  1. 记录日志:每当员工开始计算或结束计算时,朋友都要记下时间和计算的详情。
  2. 传递工作:朋友不亲自计算,而是让员工去做实际的计算,然后把结果告诉客户。

在这个例子中,你的朋友就是静态代理。编写了一个名为 CalculatorStaticProxy 的类,就像你朋友的角色一样。这个类实现了 Calculator 接口(就像你的朋友知道如何“算账”),并包含一个指向员工(target)的引用。代理类的方法(如 add())包含了两部分任务:记录日志(附加功能)和调用员工的对应方法(核心业务逻辑)。

局限性

现在,问题来了:

  • 不够灵活:假如你开了更多的分店,每家店都需要监督员工算账,你得为每家店找一个朋友做代理。这意味着你需要为每个分店编写一个单独的代理类(就像为每个店找不同的朋友)。如果以后要更改日志格式或添加其他监控功能(如记录计算耗时),你得逐一修改所有代理类的代码,工作量大且容易出错。
  • 难以集中管理:每个朋友(代理类)有自己的日志记录方式,没有统一的标准。如果你想查看所有分店的日志,得分别向每个朋友要,不方便且效率低。如果要更改日志处理规则,比如统一上传到云端,你需要逐个通知每个朋友,非常麻烦。

动态代理

为了解决这些问题,你决定雇佣一个专业的审计团队(动态代理)。他们承诺:

  1. 自动化代理:不论你有多少家分店,只要告诉他们哪位员工需要监督,他们会立即安排一个“虚拟朋友”去工作,无需你手动找人或编写代理类。
  2. 集中处理日志:所有的“虚拟朋友”都按照审计团队的标准流程记录日志,然后统一上报到审计团队的系统中,便于你集中查看和管理。
  3. 灵活调整:如果你需要更改日志规则或添加新功能,只需告诉审计团队一次,他们会自动更新所有“虚拟朋友”的行为,无需你一个个通知。

在编程世界里,动态代理就像这个审计团队。它通过编程框架(如 Java 的 JDK 动态代理)在运行时自动为你生成代理对象。你只需要指定目标对象(员工),框架会自动生成一个代理对象(虚拟朋友),这个代理对象能执行附加功能(日志记录)并调用目标对象的方法(算账)。这样,无论有多少个目标对象(分店员工),都可以用同一个动态代理机制来处理,日志逻辑也集中在一个地方管理,大大提高了代码的复用性和可维护性。


总结来说,静态代理就像手动找朋友帮忙监督员工,虽然实现了功能,但扩展困难、管理分散。而动态代理就像雇佣专业团队,自动、集中且灵活地处理代理任务,更适合应对复杂多变的需求。


2.3. 动态代理


生产代理对象的工厂类:

package com.sakurapaid.spring6.aop.demo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * 动态代理工厂类,用于创建目标对象的动态代理实例。
 */
public class ProxyFactory {
    private Object target;

    /**
     * 构造函数,初始化目标对象。
     * @param target 目标对象,将为此对象创建动态代理。
     */
    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * 获取目标对象的动态代理实例。
     * @return 返回代理对象,该对象实现了目标对象的所有接口。
     */
    public Object getProxy(){
        // 获取目标对象的类加载器和实现的接口数组
        ClassLoader classLoader = target.getClass().getClassLoader(); // 获取目标对象的类加载器
        Class<?>[] interfaces = target.getClass().getInterfaces(); // 获取目标对象的实现的接口数组

        // 设置代理对象的行为,即在调用任何方法时执行的逻辑
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 在方法执行前后打印日志
                Object result = null;
                try {
                    System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
                    result = method.invoke(target, args); // 调用目标对象的方法
                    System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());
                } finally {
                    System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");
                }
                return result;
            }
        };

        // 创建并返回代理实例
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

测试输出

package com.sakurapaid.spring6.aop.demo;

import org.junit.jupiter.api.Test;

/**
 * 计算器测试类,用于测试动态代理功能。
 */
public class CaculatorTest {

    /**
     * 测试使用动态代理进行方法拦截。
     * 该测试方法不接受参数,也不返回任何值。
     * 主要流程是创建一个代理工厂,针对CalculatorLogImpl实例生成一个动态代理对象,
     * 然后通过该代理对象调用add方法,以验证动态代理的拦截功能是否正常。
     */
    @Test
    public void testDynamicProxy(){
        // 创建代理工厂,并设置目标对象为CalculatorLogImpl的实例
        ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());
        // 通过代理工厂获取代理对象
        Calculator proxy = (Calculator) factory.getProxy();
        // 使用代理对象调用add方法,验证动态代理是否生效
        proxy.add(1,2);
    }
}


3. AOP切面编程

3.1. 概述

AOP是什么?

想象你开了一家餐厅,餐厅里有很多事情要做:厨师负责烹饪美食(这是主业务,就像程序中的核心业务逻辑),服务员接待客人、点菜、上菜(这也是业务的一部分),还有清洁工打扫卫生、收银员结账等。除此之外,还有一些贯穿整个运营过程的“边缘任务”,比如:

  • 记账:每次卖出一道菜,都需要记录销售额和成本,以便月底算账。
  • 消毒:每接待一批客人后,都要对桌面和餐具进行消毒,保证食品安全。
  • 提醒:当某种食材快要用完时,需要及时提醒采购员补货。

这些“边缘任务”(我们称它们为“切面”)虽然不是直接做菜或服务客人,但对餐厅的正常运营至关重要。而且,它们会涉及到餐厅的各个环节,不是只针对某一特定角色或环节。

传统的做法

以前,你可能让厨师、服务员、清洁工等人在忙完自己的本职工作后,额外花时间去记账、消毒、提醒采购。这样做的问题是:

  1. 分散注意力:每个人都得操心额外的事情,可能会影响他们做好本职工作。
  2. 重复劳动:比如每道菜售出时,每个服务员都得记一笔账,很浪费时间。
  3. 难于管理:记账、消毒、提醒采购的规则可能经常变动,要通知所有人并确保他们按新规则执行,很麻烦。

AOP的做法

AOP就像是请了一个全能助手,专门负责这些“边缘任务”。助手站在一旁观察餐厅的运营情况,按照设定好的规则自动执行:

  • 记账助手:每道菜卖出时,助手自动记账,月底直接给你报表。
  • 消毒助手:客人离开后,助手立刻去清理桌面、消毒餐具,无需服务员操心。
  • 提醒助手:食材快用完时,助手直接通知采购员,无需厨师分心。

这样做的好处:

  1. 专注主业:厨师、服务员可以专心烹饪和服务,不用分心做其他事。
  2. 高效执行:记账、消毒等任务由专人专职处理,速度快、准确率高。
  3. 易于管理:修改记账规则、消毒频率、提醒条件时,只需告诉助手一个人即可,省时省力。

映射到编程领域

在软件开发中,AOP就是那个全能助手。它能在程序运行时,自动在合适的地方插入额外的代码(比如记录日志、开启事务、权限检查等),而不用修改原来的业务代码。这样:

  • 业务代码更纯粹:程序员只需关注业务逻辑本身,不用在代码中混杂各种“边缘任务”。
  • 功能复用:比如日志记录逻辑,可以统一定义一次,然后在需要的地方自动应用,无需重复编写。
  • 易于维护:如果需要调整某个“边缘任务”(如日志格式),只需改动一处,不影响业务代码。

总结来说,AOP是一种编程思想,它让程序能够自动在执行主业务逻辑的同时,处理好那些与业务逻辑交织但又相对独立的“边缘任务”,使得代码更清晰、更易维护,同时也提高了开发效率。在Spring框架中,AOP提供了具体的工具和技术实现这一思想。

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。


3.2. 相关术语

3.2.1. 横切关注点

比喻: 想象你经营一家餐厅,每天有许多日常事务要处理。其中有一些事务是贯穿整个餐厅运营的,就像一根根“红线”贯穿于各个部门之间。例如:

  • 食品安全检查:无论是厨房烹饪、服务员上菜还是收银结账,都需要确保食物安全。
  • 员工考勤:所有员工上班时都需要打卡,下班时也需要记录工时。
  • 顾客满意度调查:在顾客用餐结束后,可能需要让他们填写问卷,收集反馈。

这些贯穿于餐厅各个角落、各部门都需处理的共同问题,就像“红线”一样横跨各个业务模块,我们称之为“横切关注点”。

解释: 在软件开发中,横切关注点指的是那些在不同业务模块中反复出现、与业务逻辑本身关系不大,但又不可或缺的处理逻辑。比如用户验证、日志记录、事务管理、数据缓存等。它们并非某一个具体功能的核心部分,而是与整个系统的行为规范、运行环境、管理需求等相关。


3.2.2. 通知(增强)

比喻: 假设你想提升餐厅的服务质量,于是决定给餐厅添加一些“增强功能”。比如:

  • 餐前欢迎词:顾客入座后,服务员送上一句温馨的欢迎词。
  • 餐后感谢语:顾客结账离开时,收银员表达一声真诚的感谢。
  • 限时优惠提醒:在特定时段,主动告知顾客当前正在进行的优惠活动。

这些额外的互动环节就像给餐厅原有的服务流程加上了“增强功能”,让顾客体验更佳。

解释: 在AOP中,通知(增强)就是指那些在目标方法执行前后(或过程中)插入的额外操作,用来实现横切关注点所需的功能。比如:

  • 前置通知:在厨师开始炒菜(目标方法)之前,先检查食材新鲜度(附加功能)。
  • 返回通知:顾客成功付款(目标方法执行完毕)后,系统自动发送电子发票(附加功能)。
  • 异常通知:若结账时发生支付失败(目标方法异常),则触发退款流程(附加功能)。
  • 后置通知:无论结账是否成功,都会记录本次交易信息到数据库(附加功能)。
  • 环绕通知:整个点餐到结账的过程,如同包裹在try...catch...finally结构中,确保无论是否发生异常,都能执行必要的清理工作(如释放资源、关闭连接等)。


3.2.3. 切面

比喻: 为了让餐厅更好地运营,你专门设立了一个“服务质量提升小组”,这个小组负责策划并实施所有的“增强功能”,如欢迎词、感谢语、优惠提醒等。这个小组就是所有“增强功能”的集合体。

解释: 在AOP中,切面就是一个封装了多个通知(增强)的类,它定义了哪些地方需要执行哪些增强逻辑。切面就像一个“服务质量提升小组”,集中管理与特定横切关注点相关的所有通知。


3.2.4. 目标

比喻: 在餐厅里,厨师专心炒菜、服务员热情待客,他们是各自岗位上的“目标”,他们的主要任务是完成本职工作。

解释: 在AOP中,目标就是指那些被增强的对象或方法,即我们想要在其周围添加通知的原始业务逻辑。比如某个处理订单的类或方法,它们专注于处理订单的核心业务,是AOP要进行增强的“目标”。


3.2.5. 代理

比喻: 如果你聘请了一位餐厅经理,他并不直接炒菜或服务顾客,但他会指导厨师和服务员如何更好地工作,并在必要时介入处理一些特殊情况。这位经理就像是厨师和服务员的“代理”,他们对外展示的是经理协调下的综合服务能力。

解释: 在AOP中,代理就是指为目标对象生成的一个替代者,它在保留原对象功能的基础上,包含了切面中定义的通知逻辑。客户端代码实际上与代理对象交互,代理对象在调用目标方法时会适时执行相应的通知。


3.2.6. 连接点

比喻: 在餐厅运营过程中,任何一个可以插入“增强功能”的时机都可以看作一个“连接点”。比如顾客刚坐下时、点完菜后、结账时、离开时等都是可以插入欢迎词、感谢语、优惠提醒等“增强功能”的“连接点”。

解释: 在AOP中,连接点是一个抽象概念,它表示在程序执行过程中能够插入通知的所有可能位置。具体来说,通常是某个方法的执行前、执行后、抛出异常时、正常返回后等时刻。连接点就像一张餐厅运营流程的时间线图,图上标注了所有可以插入“增强功能”的时间节点。


3.2.7. 切入点

比喻: 如果你决定只在顾客入座时和结账时进行“增强功能”(欢迎词、感谢语),那么这两个特定的“连接点”就是你要瞄准的“切入点”。

解释: 在AOP中,切入点就是用来精确指定应该在哪些连接点上应用通知的表达式或规则。它就像一个过滤器,从众多连接点中选出真正需要插入通知的具体位置。比如,可能只选择所有以“save”开头的方法作为切入点,对这些方法执行事务管理的增强。切入点就像一份详细的“增强功能”实施计划,指定了在哪些具体场景(方法调用)中启用哪些“增强功能”。


3.3. 作用

  • 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
  • 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。

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

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

相关文章

【JavaSE】java刷题--数组练习

前言 本篇讲解了一些数组相关题目&#xff08;主要以代码的形式呈现&#xff09;&#xff0c;主要目的在于巩固数组相关知识。 上一篇 数组 讲解了一维数组和二维数组的基础知识~ 欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎…

大创项目推荐 深度学习 opencv python 实现中国交通标志识别_1

文章目录 0 前言1 yolov5实现中国交通标志检测2.算法原理2.1 算法简介2.2网络架构2.3 关键代码 3 数据集处理3.1 VOC格式介绍3.2 将中国交通标志检测数据集CCTSDB数据转换成VOC数据格式3.3 手动标注数据集 4 模型训练5 实现效果5.1 视频效果 6 最后 0 前言 &#x1f525; 优质…

HTX Ventures:为什么BounceBit可能成为新的BTC生态解决方案?

随着BTC现货ETF的通过&#xff0c;全球各大机构和个人都在不断加码对BTC的持仓&#xff0c;BTC价格也随之上升&#xff0c;目前已上升至全球市值排名前十的资产。在本轮市场周期中&#xff0c;BTC铭文和BTC扩容是两个被市场高度关注的细分赛道。BTC生态资产的多元化收益探索正在…

TypseScript再学习之类型别名和接口(10)

先看类型别名&#xff1a;使用关键字 type 声明,注意有等于号额 // 类型别名 使用关键字 type 声明,注意有等于号额 type Cat {name: string; }; let huahua: Cat {name: "花花", };type和interface不同之处在于&#xff1a;interface 是可以自动合并类型的&#…

Linux shell编程学习笔记43:cut命令

0 前言 在 Linux shell编程学习笔记42&#xff1a;md5sum 中&#xff0c;md5sum命令计算md5校验值后返回信息的格式是&#xff1a; md5校验值 文件名 包括两项内容&#xff0c;前一项是md5校验值 &#xff0c;后一项是文件名。 如果我们只想要前面的md5 校验值&#xff0c…

计算机网络—UDP协议详解:特性、应用

​ &#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;マリンブルーの庭園—ずっと真夜中でいいのに。 0:34━━━━━━️&#x1f49f;──────── 3:34 &#x1f504; ◀…

Rust 02.控制、引用、切片Slice、智能指针

1.控制流 //rust通过所有权机制来管理内存&#xff0c;编译器在编译就会根据所有权规则对内存的使用进行 //堆和栈 //编译的时候数据的类型大小是固定的&#xff0c;就是分配在栈上的 //编译的时候数据类型大小不固定&#xff0c;就是分配堆上的 fn main() {let x: i32 1;{le…

美术馆设计方案优化布局与设施提升观众体验!

如今&#xff0c;美术馆不仅仅是作为展示艺术作品的平台&#xff0c;也是吸引公众参与和创造独特体验的数字艺术体验空间&#xff0c;因此许多传统美术馆在进行翻修改造时&#xff0c;都会更加注重用户体验&#xff0c;并在其中使用大量的多媒体互动&#xff0c;让参观者能够在…

基于 YOLO V8 Fine-Tuning 训练自定义的目标检测模型

一、YOLO V8 YOLO V8 是由 2023 年 ultralytics 公司开源的发布&#xff0c;是结合了前几代 YOLO 的融合改进版。YOLO V8 支持全方位的视觉 AI 任务&#xff0c;包括检测、分割、姿态估计、跟踪和分类。并且在速度和准确性方面具有无与伦比的性能。能够应用在各种对速度和精度…

【重制版】在Android手机上安装kali Linux

前言 由于kali官方的Nethunter2的安装代码因为…无法访问&#xff0c;手头又没有一些受支持的机器3&#xff0c;所以做了这个脚本&#xff0c;供大家使用。 工具 搭载基于Android的手机TermuxVNC Viewer 安装必备软件(如已安装请忽略) 请到 https://www.hestudio.net/post…

制造出海,灵途科技助力割草机器人、泳池清洁机器人全方位感知

近年来&#xff0c;越来越多的中国企业开始对外开拓&#xff0c;走向海外市场、挖掘和满足全球消费者的需求。在消费机器人领域&#xff0c;中国企业出海成绩亮眼&#xff01;在2024 ces 和上海AWE展会上&#xff0c;多家机器人公司展示了家用智能割草机器人、泳池清洁机器人的…

C#基础知识总结

C语言、C和C#的区别 ✔ 面向对象编程&#xff08;OOP&#xff09;&#xff1a; C 是一种过程化的编程语言&#xff0c;它不直接支持面向对象编程。然而&#xff0c;C 是一种支持 OOP 的 C 的超集&#xff0c;它引入了类、对象、继承、多态等概念。C# 是完全面向对象的&#xff…

【C++】string类(常用接口)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;http://t.csdnimg.cn/eCa5z 目录 修改操作 push_back append operator assign insert erase replace c_str find string类非成…

淘宝商品详情数据(商品分析,竞品分析,代购商城建站与跨境电商,ERP系统商品数据选品)

淘宝商品详情数据在多个业务场景中发挥着关键作用&#xff0c;以下是一些主要的应用场景&#xff1a; 请求示例&#xff0c;API接口接入Anzexi58 商品分析&#xff1a;通过对淘宝商品详情的全面分析&#xff0c;商家可以深入了解商品的属性、价格、销售量、评价等信息。这些数…

手写简易操作系统(十八)--实现用户进程

一、TSS TSS是Task State Segment的缩写&#xff0c;即任务状态段&#xff0c;早在简述特权级的时候我们就讲过了一点 手写简易操作系统(八)&#xff0c;现在我们讲一下这些保存的寄存器是干嘛的。 这一部分需要讲点历史&#xff0c;硬件与软件的关系是相互促进的&#xff0c…

基于SpringBoot + Vue实现的中国陕西民俗网设计与实现+毕业论文

介绍 本系统包含管理员、用户两个角色。 管理员角色&#xff1a;登录、用户管理功能、民俗介绍管理功能(发布和管理民俗文化的介绍文章)、公告信息管理功能(发布网站的重要通知和活动信息)、商品管理功能(对商家发布的商品进行监管)、商品评价管理功能(监管商品评价内容&#…

乐理通识

2023 年搞了台雅马哈 61 键的电子琴&#xff0c;顺手看了下啊 B 的上的课程 《零基础自学音乐学乐理合集-第一季》&#xff0c;这里是部分笔记&#xff08;给博客加点不一样的东西&#x1f440;&#xff09;。 简谱各部分一览 C 表示音名竖线为小节线 音名 完整钢琴键盘 88 键…

leetcode:392. 判断子序列

题目&#xff1a; class Solution { public:bool isSubsequence(string s, string t) {} }; 题解&#xff1a; 很巧妙的题解&#xff1a;循环遍历两个字符串&#xff0c;两个字符串都没遍完就继续遍历&#xff0c;字符串s先遍历完结果为true&#xff0c;字符串t先遍历完结果为…

项目管理【环境】概述

系列文章目录 【引论一】项目管理的意义 【引论二】项目管理的逻辑 【环境】概述 一、组织运行环境 1.1 事业环境因素EEFs 1.2 组织过程资产OPA 1.3 二者差异 二、组织结构类型 2.1 组织架构 2.2 职能型组织 2.3 项目型组织 2.4 矩阵型组织 2.5 项目管理者在不同组织中的特…

NSSCTF Round#20 Basic 真亦假,假亦真 CSDN_To_PDF V1.2 出题笔记 (附wp+源码)

真亦假&#xff0c;假亦真 简介&#xff1a;java伪造php一句话马。实则信息泄露一扫就出&#xff0c;flag在/flag里面。 题目描述&#xff1a;开开心心签个到吧&#xff0c;祝各位师傅们好运~ 静态flag&#xff1a;NSS{Checkin_h4v3_4_g00D_tINNe!} /路由显示 <?php e…