Java中的设计模式——单例模式、代理模式、适配器模式

news2024/11/8 15:53:07

1. 单例模式

模式概述

  • 定义:单例模式是一种设计模式,目的是确保一个类只有一个实例,并提供一个全局访问点。
  • 实现方式:常见的单例模式实现方式有懒汉式和饿汉式。懒汉式是在第一次需要用到实例时才创建对象,而饿汉式是在类加载时就创建好实例。

Java 内存模型(JMM)和线程工作内存

Java 内存模型规定了线程与主内存之间的交互规则,具体体现在以下几个方面:

  1. 主内存:所有 Java 对象实例和静态变量都存储在主内存中。主内存可以看作是一个共享的内存区域,所有线程都可以访问。

  2. 工作内存(线程缓存):每个线程有自己的工作内存(也称为线程缓存)。线程的工作内存中存储着该线程所需要的数据(变量副本)。每个线程从主内存中读取所需变量值后,会将这些值暂存到自己的工作内存中,线程在计算或操作这些变量时,是在工作内存中进行的,而非直接在主内存中操作。

  3. 变量读取与写入:线程的工作内存会把从主内存加载的变量值保存在自己的缓存中,执行计算时直接对缓存中的数据进行操作。修改后的结果也会先写入线程的工作内存中,最后再同步回主内存中。

这种设计可以提高访问速度,但也会带来一个问题——可见性问题

可见性问题

因为每个线程对变量的操作都发生在自己的工作内存中,当多个线程操作同一个变量时,工作内存和主内存之间的更新不同步,就会导致一个线程对变量的修改对其他线程不可见。

例如,假设有一个变量 flag,被线程 A 和线程 B 同时使用:

  • 步骤 1flag 初始值为 true,存储在主内存中。
  • 步骤 2:线程 A 将 flag 的值加载到自己的工作内存中。
  • 步骤 3:线程 B 也将 flag 的值加载到自己的工作内存中。
  • 步骤 4:线程 A 将 flag 的值修改为 false 并写回到主内存中。
  • 步骤 5:线程 B 继续使用自己的工作内存中缓存的 flag 值,而不会意识到主内存中的值已经被修改为 false

        结果就是,线程 B 继续操作的是一个过期的值,导致了可见性问题

volatile 如何解决可见性问题

在 Java 中,volatile 关键字是一种轻量级的同步机制,用于修饰变量。volatile 可以确保被修饰的变量在所有线程中都是可见的,具体来说有以下两方面保证:

  1. 读操作从主内存中加载最新的值:当一个变量被声明为 volatile 时,JMM 会确保每次线程读取这个变量时,都是从主内存中直接读取最新的值,而不会使用工作内存中的缓存值。

  2. 写操作立即同步到主内存:同样,当一个线程对 volatile 变量进行写操作时,JMM 会强制将这个更新后的值立即刷新回主内存,使得其他线程可以立即看到最新的变化。

        以下是一个简单的代码示例,说明 volatile 如何确保线程之间的可见性:

public class VolatileExample {
    private volatile boolean flag = true;

    public void updateFlag() {
        flag = false;  // 这里修改flag值会立即同步到主内存
    }

    public void checkFlag() {
        while (flag) {
            // 这里每次读取flag的值,都是从主内存读取最新值
        }
    }
}

在这个例子中:

  • flagvolatile 修饰后,线程执行 updateFlag() 方法将 flag 设置为 false 时,值会立即写回主内存。
  • 另一个线程执行 checkFlag() 方法,每次读取 flag 值时,都会从主内存中获取最新的值,而不会使用线程缓存。

volatile 的局限性

        虽然 volatile 能解决可见性问题,但它并不能保证操作的原子性。只能用在简单的赋值操作中举例来说,count++ 这样的操作包含了多个步骤(读取 count 值、增加 1 并写回),volatile 并不能确保多个线程同时执行该操作时不会产生冲突。

使用 volatilesynchronized 实现单例模式

synchronized 关键字

  • 定义synchronized 是一种重量级的同步机制,可以修饰方法或代码块,用于控制线程访问的顺序。
  • 作用synchronized 可以确保同一时刻只有一个线程执行同步代码块或方法,保证了代码的原子性和可见性。进入 synchronized 块的线程会自动获取锁,执行完毕后会释放锁。
  • 适用场景:适用于需要保护某段关键代码的场景,例如对共享资源的读写操作。

轻量级和重量级同步的区别

在多线程编程中,轻量级和重量级同步主要指同步机制对系统资源的占用程度,以及对性能的影响。

1. 轻量级(volatile 属于轻量级同步)

volatile 被称为轻量级同步,原因是它仅仅确保了变量的可见性,但并不保证原子性。它没有像 synchronized 那样的锁机制,不会阻塞线程,因此不会引起线程上下文切换。使用 volatile 不需要进入同步块,也就没有额外的资源消耗和性能开销。

优点

  • 无锁机制,性能较高。
  • 可以保证变量的最新值对所有线程可见。

局限性

  • 只能用在简单的赋值操作中,不适用于复合操作(如 count++)。
  • 不保证操作的原子性,不适合需要排他访问的场景。

2. 重量级(synchronized 属于重量级同步)

synchronized 是重量级同步,因为它涉及锁机制。当一个线程进入 synchronized 块或方法时,其他线程无法同时进入该块,这会导致线程的阻塞和等待。锁的获取和释放会带来额外的系统开销,比如线程的上下文切换(切换线程时操作系统保存和恢复线程的状态),因此性能相对较低,属于重量级操作。

优点

  • 可以保证操作的原子性和可见性。
  • 适用于需要保护复合操作的场景,例如共享资源的修改。

局限性

  • 由于线程需要等待锁,性能较低。
  • 在大量线程争抢锁时,可能会导致性能下降。

为什么选择轻量或重量

轻量和重量的选择取决于程序对同步的需求和性能的权衡:

  • 如果只是需要简单的变量可见性(比如某个标志位的状态),使用 volatile 更合适。
  • 如果操作涉及多个步骤且需要原子性保障(如增减计数器、更新共享资源),synchronized 更可靠,尽管它会带来更多性能开销。

双重检查锁定模式(Double-Checked Locking)

双重检查锁定是一种延迟初始化的懒汉式单例模式,它利用 volatilesynchronized 确保线程安全,同时避免了每次获取实例时都进入同步块的性能开销。

public class Singleton {
    // 使用 volatile 关键字,确保 instance 对所有线程的可见性
    private static volatile Singleton instance = null;

    // 私有构造函数,防止外部创建实例
    private Singleton() {}

    // 提供一个全局访问点
    public static Singleton getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (Singleton.class) {  // 同步代码块
                if (instance == null) {  // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

详细解读双重检查锁定中的过程

  1. 第一个 if (instance == null)

    • 多个线程可能同时调用 getInstance() 方法。
    • 因为这是在同步块外执行的第一次检查,所以多个线程会同时通过 if (instance == null) 的判断,认为 instancenull(因为最开始 instance 确实是 null)。
  2. synchronized (Singleton.class)

    • 虽然多个线程通过了第一次检查,但只有一个线程能成功获取锁,进入 synchronized 块。其他线程会在同步块外等待,直到锁被释放。
    • 第一个进入 synchronized 块的线程会创建 Singleton 实例。
  3. 第二个 if (instance == null)

    • 第一个获得锁的线程在 synchronized 块中再次检查 instance == null。因为这是第一次创建实例的线程,所以 instance 仍然为 null,于是这个线程创建实例。
    • 当第一个线程释放锁后,其他等待的线程会依次进入 synchronized 块。此时 instance 已经被创建,所以这些线程在 synchronized 块内的第二次检查 if (instance == null) 会发现 instance 不再是 null,因此不会再次创建实例。

        双重检查的原因是性能优化。在大多数情况下,单例对象已经被创建,我们不需要进入 synchronized 块,从而减少了同步的开销。

2. 代理模式

模式概述

        代理模式是一种设计模式,它让一个对象(代理对象)代替另一个对象去处理请求。我们用代理对象来控制对实际对象的访问,这样我们可以在访问实际对象前后添加一些额外功能,比如控制权限、记录日志、延迟加载资源等。

        想象一下,当你要进入一个大型活动会场时,会有一个安保人员检查你的门票。这时,安保人员就是一个代理,他控制了你对会场的“访问”。通过代理人员的检查,确保只有合法的客人才能进入会场。

适用场景
代理模式适用于以下几种情况:

  1. 访问控制:限制谁可以访问对象,比如权限控制。
  2. 延迟加载:在需要时才加载某些资源,比如数据库连接或大文件。
  3. 日志和监控:代理对象可以记录谁在什么时间访问了资源,方便后续分析。

代理模式的结构

代理模式通常包含以下三个角色:

  • 接口或抽象类(Subject):定义了实际对象和代理对象共同的接口。这样代理对象和实际对象可以被同样的方式调用。

  • 实际对象(RealSubject):这是被代理的对象,它包含了业务逻辑,比如文件读取、数据库连接等功能。

  • 代理对象(Proxy):这是负责“代理”访问的对象,它持有对实际对象的引用,并实现了与实际对象相同的接口。

让我们来通过一个例子一步步地解释代理模式。

示例:延迟加载图片

假设我们有一个图片类 RealImage,它需要从磁盘加载图片的操作,但这个操作可能比较耗时。因此,我们可以创建一个代理类 ImageProxy,用来在需要时才实际加载图片。

  1. 定义图片接口

    首先,我们定义一个 Image 接口,这样代理类和实际图片类都可以实现这个接口,并保持相同的操作方法(在这里是 display() 方法):

    public interface Image {
        void display();
    }
    
  2. 实现实际图片类(RealSubject)

    接下来,我们创建 RealImage 类,它负责从磁盘加载图片。加载图片的操作可能很耗时,我们可以用 System.out.println 模拟这种加载的延迟效果。

    public class RealImage implements Image {
        private String fileName;
    
        public RealImage(String fileName) {
            this.fileName = fileName;
            loadFromDisk();  // 模拟加载图片的耗时操作
        }
    
        private void loadFromDisk() {
            System.out.println("Loading " + fileName);
        }
    
        public void display() {
            System.out.println("Displaying " + fileName);
        }
    }
    
  3. 创建代理类(Proxy)

    然后,我们创建 ImageProxy 类,它是 Image 接口的代理实现。代理类持有实际图片对象的引用(RealImage),并在需要时才去创建和加载它。通过代理,我们可以延迟 RealImage 的初始化,直到第一次调用 display() 才加载图片。

    public class ImageProxy implements Image {
        private RealImage realImage;   // 持有实际图片对象的引用
        private String fileName;
    
        public ImageProxy(String fileName) {
            this.fileName = fileName;
        }
    
        public void display() {
            if (realImage == null) {  // 仅在需要时才加载实际图片
                realImage = new RealImage(fileName);
            }
            realImage.display();
        }
    }
    
  4. 测试代理模式的效果

    在客户端代码中,我们通过代理类 ImageProxy 来访问图片对象。这样,我们可以在第一次调用 display() 方法时才实际加载图片,避免了每次创建图片时都加载的开销。

    public class ProxyPatternDemo {
        public static void main(String[] args) {
            Image image = new ImageProxy("test_image.jpg");
    
            // 第一次调用 display,实际图片会被加载
            image.display();
            
            // 第二次调用 display,使用已经加载的图片
            image.display();
        }
    }
    

执行结果

Loading test_image.jpg
Displaying test_image.jpg
Displaying test_image.jpg

解析

  • 延迟加载:代理类 ImageProxy 通过 realImage == null 的检查,仅在第一次调用 display() 方法时才去创建 RealImage,从而实现了延迟加载。

  • 访问控制:用户通过 ImageProxy 访问 RealImage,从而将实际图片的加载过程隔离出来,用户不必直接创建和加载图片对象,而是通过代理类来控制加载行为。

总结代理模式

代理模式在不修改实际对象的情况下,控制了对实际对象的访问,还可以增加额外的操作,例如延迟加载和访问权限验证等。代理模式非常适用于需要访问控制或延迟初始化的场景。

3. 适配器模式

模式概述

        适配器模式是一种设计模式,它将一个类的接口转换为客户端期望的接口。简单来说,适配器模式解决了接口不兼容的问题,使得原本无法直接使用的类能够配合工作。

举个简单的例子:假如你的手机充电器插头是USB-C型,但你的插座是三孔的,这时你就需要一个适配器,它能够把USB-C型插头转换成符合三孔插座的插头,让你可以正常充电。

适用场景
适配器模式适用于以下情况:

  1. 接口不兼容:当使用的接口与已有类的接口不匹配时,通过适配器连接两者。
  2. 复用现有类:不改变已有类的代码,让它适配新的接口要求。

适配器模式的结构

适配器模式一般包含以下几部分:

  • 目标接口(Target):客户端期望使用的接口。
  • 已有接口(Adaptee):原本不兼容的接口,需要被适配的接口。
  • 适配器(Adapter):实现目标接口,并将已有接口的功能转换为目标接口的功能。

实现步骤

我们来通过一个具体示例逐步理解适配器模式的实现。

示例:音频播放器扩展

假设我们有一个音频播放器 AudioPlayer,它只能播放 MP3 格式的音频文件。现在,我们需要扩展播放器,让它可以播放其他格式的音频文件(如 VLC 和 MP4 格式)。

1. 目标接口 (MediaPlayer)

AudioPlayer 类需要实现 MediaPlayer 接口,该接口定义了播放器的基本方法。

public interface MediaPlayer {
    void play(String audioType, String fileName); // 播放音频文件
}

2. 被适配接口 (AdvancedMediaPlayer)

我们将定义一个 AdvancedMediaPlayer 接口,用来支持播放 MP4 和 VLC 格式的文件。这个接口包含两个方法:一个用于播放 MP4 文件,另一个用于播放 VLC 文件。

public interface AdvancedMediaPlayer {
    void playVlc(String fileName); // 播放 VLC 文件
    void playMp4(String fileName); // 播放 MP4 文件
}

3. 被适配的类 (VlcPlayerMp4Player)

然后我们实现 AdvancedMediaPlayer 接口的具体类。VlcPlayer 用于播放 VLC 格式的文件,Mp4Player 用于播放 MP4 格式的文件。

public class VlcPlayer implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        System.out.println("Playing VLC file. Name: " + fileName);
    }

    @Override
    public void playMp4(String fileName) {
        // 不实现
    }
}

public class Mp4Player implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        // 不实现
    }

    @Override
    public void playMp4(String fileName) {
        System.out.println("Playing MP4 file. Name: " + fileName);
    }
}

4. 适配器类 (MediaAdapter)

为了使 AudioPlayer 能够播放 MP4 和 VLC 格式的文件,我们创建一个适配器类 MediaAdapter,该类将 MediaPlayer 接口的 play() 方法与 AdvancedMediaPlayer 的方法连接起来。

public class MediaAdapter implements MediaPlayer {
    AdvancedMediaPlayer advancedMusicPlayer;

    public MediaAdapter(String audioType) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedMusicPlayer = new VlcPlayer(); // 支持 VLC 格式
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedMusicPlayer = new Mp4Player(); // 支持 MP4 格式
        }
    }

    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedMusicPlayer.playVlc(fileName);
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedMusicPlayer.playMp4(fileName);
        }
    }
}

5. 音频播放器类 (AudioPlayer)

AudioPlayer 类实现了 MediaPlayer 接口,并且在播放 MP3 文件时直接处理,如果是其他格式,则通过 MediaAdapter 来适配。

public class AudioPlayer implements MediaPlayer {
    MediaAdapter mediaAdapter;

    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("mp3")) {
            System.out.println("Playing MP3 file. Name: " + fileName);
        }
        else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
            mediaAdapter = new MediaAdapter(audioType); // 使用适配器
            mediaAdapter.play(audioType, fileName);
        }
        else {
            System.out.println("Invalid media. " + audioType + " format not supported");
        }
    }
}

6. 测试类 (AdapterPatternDemo)

最终,我们可以创建一个测试类来验证我们的 AudioPlayer 是否能够成功支持 MP3、MP4 和 VLC 格式的文件。

public class AdapterPatternDemo {
    public static void main(String[] args) {
        AudioPlayer audioPlayer = new AudioPlayer();

        audioPlayer.play("mp3", "beyond the horizon.mp3");  // MP3 文件
        audioPlayer.play("mp4", "alone.mp4");              // MP4 文件
        audioPlayer.play("vlc", "far far away.vlc");       // VLC 文件
        audioPlayer.play("avi", "mind me.avi");            // 不支持的格式
    }
}

执行结果

Playing MP3 file. Name: beyond the horizon.mp3
Playing MP4 file. Name: alone.mp4
Playing VLC file. Name: far far away.vlc
Invalid media. avi format not supported

总结

  1. 目标接口 (MediaPlayer):为 AudioPlayer 类提供统一的播放方法。
  2. 被适配接口 (AdvancedMediaPlayer):定义了播放 MP4 和 VLC 文件的方法。
  3. 适配器类 (MediaAdapter):实现了 MediaPlayer 接口,并通过适配的方式调用 AdvancedMediaPlayer 的方法,支持 MP4 和 VLC 文件格式。
  4. 音频播放器类 (AudioPlayer):实现了 MediaPlayer 接口,并根据文件类型选择是否通过 MediaAdapter 来播放 MP4 或 VLC 文件。

        通过适配器模式,AudioPlayer 类能够扩展支持其他音频格式,而不需要改变现有的代码结构。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【含开题报告+文档+源码】基于SSM的物流管理系统设计与实现

开题报告 随着电子商务的迅猛发展和人们生活水平的提高,快递服务行业正经历着前所未有的增长。占航快递公司作为国内知名的快递企业之一,面临着巨大的机遇和挑战。传统的快递服务管理方式已经无法满足日益增长的业务需求,快递服务流程中的问…

外包干了2年,快要废了。。。

先说一下自己的情况,普通本科毕业,在外包干了2年多的功能测试,这几年因为大环境不好,我整个人心惊胆战的,怕自己卷铺盖走人了,我感觉自己不能够在这样蹉跎下去了,长时间呆在一个舒适的环境真的会…

界面控件DevExpress WPF中文教程:Data Grid——卡片视图设置

DevExpress WPF拥有120个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

使用Matlab神经网络工具箱

综述 在大数据和人工智能时代,深度学习是一种最为常见的数据分析和拟合工具。本报告以常用分析软件Matlab为例,介绍如何通过编写代码实现一个简单的CNN卷积神经网络。 Step 1: 打开matlab,新建脚本 1、安装matlab 2018以上版本后&#xff…

【系统设计——认证授权——基本概念知识】

1. 认证和授权的区别 Authentication(认证) 是验证您的身份的凭据(例如用户名/用户 ID 和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authenticat…

区块链技术入门:以太坊智能合约详解

💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 区块链技术入门:以太坊智能合约详解 区块链技术入门:以太坊智能合约详解 区块链技术入门:以太…

【Spring】更加简单的将对象存入Spring中并使用

前言 本期讲解:通过Controller、Service、Repository、Component、Configurtion类注解、Bean方法注解,来更加简单的在Spring中存与读对象。 目录 1. 类注解 1.1 通过标签 1.2 使用类注解 1.3 什么是类注解 1.4 获取Bean对象命名问题 2. 方法注解 …

Vue(JavaScript)读取csv表格并求某一列之和(大浮点数处理: decimal.js)

文章目录 想要读这个表格,并且求第二列所有价格的和方法一:通过添加文件输入元素上传csv完整(正确)代码之前的错误部分因为价格是小数,所以下面的代码出错。如果把parseFloat改成parseInt,那么求和没有意义…

火山引擎云服务docker 安装

安装 Docker 登录云服务器。 执行以下命令,添加 yum 源。 yum update -y yum install epel-release -y yum clean all yum list依次执行以下命令,添加Docker CE镜像源。更多操作请参考Docker CE镜像。 # 安装必要的一些系统工具 sudo yum install -y yu…

人保财险(外包)面试分享

前言: 这是本月面的第三家公司,太难了兄弟们,外包都不好找了,临近年底,金九银十已经错过了,金三银四虽然存在,但按照这几年的行情,金九银十和金三银四其实已经是不复存在了&#xf…

【D3.js in Action 3 精译_039】4.3 D3 面积图的绘制方法及其边界标签的添加

当前内容所在位置: 第四章 直线、曲线与弧线的绘制 ✔️ 4.1 坐标轴的创建(上篇) 4.1.1 D3 中的边距约定(中篇)4.1.2 坐标轴的生成(中篇) 4.1.2.1 比例尺的声明(中篇)4.1…

美国大选——极具典型的可视化案例!GISer学起来

有人说可视化技术有啥意义,不就做个大屏么? 那真的小看了,就如下图这个美国大选来看,这么复杂混乱的信息,可视化技术给你梳理的明明白白的,简单、直观、形象、便于记忆。 让用户能够从繁杂信息中快速抓到重…

使用PyQt5设计一个简易计算器

目录 设计UI图 代码 结果展示 设计UI图 代码 from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import QFileDialog, QMainWindow, QMessageBox from untitled import Ui_MainWindow import sysclass…

数据结构-数组(稀疏矩阵转置)和广义表

目录 1、数组定义 1)数组存储地址计算示例①行优先②列优先 2)稀疏矩阵的转置三元组顺序表结构定义 ①普通矩阵转置②三元组顺序表转置稀疏矩阵③稀疏矩阵的快速转置 3)十字链表结构定义 2、广义表定义 1)基本操作①GetHead②GetT…

cooladmin使用整理

1、后端关键字自动生成没有代码段提示,原因是拉取的项目代码中没有.vscode文件夹,复制一套至项目src同级即可 2、前端快速创建,在Entity完成后就去快速创建中选数据结构,这时没有对应的内容,数据结构是和controller层a…

Java 网络编程(一)—— UDP数据报套接字编程

概念 在网络编程中主要的对象有两个:客户端和服务器。客户端是提供请求的,归用户使用,发送的请求会被服务器接收,服务器根据请求做出响应,然后再将响应的数据包返回给客户端。 作为程序员,我们主要关心应…

Jmeter命令监控CPU等指标

JMeter 命令行执行脚本得到的报告中,是没有CPU、内存使用率等监控数据的,但是可以使用JMeter插件帮忙。 一、下载jmeter-plugins-manager.jar 下载后将文件放到jmeter安装包lib/ext目录下。打开Jmeter》菜单栏》选项》Plugins Manager 二、安装PerfMon…

ES + SkyWalking + Spring Boot:日志分析与服务监控(三)

目录 一、搭建SkyWalking 1.1 版本选择 1.2 下载安装 1.3 配置启动 1.4 SkyWalking UI介绍 二、Springboot项目使用 2.1 Agent下载 2.2 Agent配置skywalking oap地址 2.3 IDEA配置Agent地址 2.4 生成的ES索引介绍 三、在kibana上查看日志 四、问题和解决 3.1 日志…

如何快速搭建一个spring boot项目

一、准备工作 1.1 安装JDK:确保计算机上已安装Java Development Kit (JDK) 8或更高版本、并配置了环境变量 1.2 安装Maven:下载并安装Maven构建工具,这是Spring Boot官方推荐的构建工具。 1.3 安装代码编辑器:这里推荐使用Inte…

spring-第十三章 AOP

spring 文章目录 spring前言1.AOP介绍2.AOP七大术语3.切点表达式4.使用spring的AOP4.1概述4.2准备工作4.3基于注解方式使用AOP4.3.1准备目标类和目标方法4.3.2编写配置类4.3.3编写通知类4.3.4编写测试类4.3.5通知类型4.3.6切面的先后顺序4.3.7PointCut注解通用切点 4.4基于XML方…