Java学习指南
  • Java 编程的逻辑
  • Java进阶
  • Java FrameWorks
  • 了解 USB Type-A,B,C 三大标准接口
  • 深入浅出DDD
  • 重构:改善既有代码的设计
  • 面试大纲
  • 云原生
    • 什么是无服务器(what is serverless)?
  • 博客
    • 深入分析Log4j 漏洞
  • 博客
    • Serverless之快速搭建Spring Boot应用
  • 博客
    • 使用 Prometheus + Grafana + Spring Boot Actuator 监控应用
  • 博客
    • 使用 Prometheus + Grafana 监控 MySQL
  • 博客
    • 使用Github Actions + Docker 部署Spring Boot应用
  • 博客
    • Redis分布式锁之Redisson的原理和实践
  • 博客
    • 数据库中的树结构应该怎样去设计
  • 学习&成长
    • 如何成为技术大牛
  • 开发工具
    • Git Commit Message Guidelines
  • 开发工具
    • git命名大全
  • 开发工具
    • Gradle vs Maven Comparison
  • 开发工具
    • Swagger2常用注解及其说明
  • 开发工具
    • 简明 VIM 练级攻略
  • 微服务
    • 十大微服务设计模式和原则
  • 微服务
    • 微服务下的身份认证和令牌管理
  • 微服务
    • 微服务坏味道之循环依赖
  • 设计模式
    • 设计模式 - JDK中的设计模式
  • 设计模式
    • 设计模式 - Java三种代理模式
  • 设计模式
    • 设计模式 - 六大设计原则
  • 设计模式
    • 设计模式 - 单例模式
  • 设计模式
    • 设计模式 - 命名模式
  • 设计模式
    • 设计模式 - 备忘录模式
  • 设计模式
    • 设计模式 - 概览
  • 设计模式
    • 设计模式 - 没用的设计模式
  • 质量&效率
    • Homebrew 替换国内镜像源
  • 质量&效率
    • 工作中如何做好技术积累
  • Java FrameWorks
    • Logback
      • 自定义 logback 日志过滤器
  • Java FrameWorks
    • Mybatis
      • MyBatis(十三) - 整合Spring
  • Java FrameWorks
    • Mybatis
      • MyBatis(十二) - 一些API
  • Java FrameWorks
    • Mybatis
      • Mybatis(一) - 概述
  • Java FrameWorks
    • Mybatis
      • Mybatis(七) - 结果集的封装与映射
  • Java FrameWorks
    • Mybatis
      • Mybatis(三) - mapper.xml及其加载机制
  • Java FrameWorks
    • Mybatis
      • Mybatis(九) - 事务
  • Java FrameWorks
    • Mybatis
      • Mybatis(二) - 全局配置文件及其加载机制
  • Java FrameWorks
    • Mybatis
      • Mybatis(五) - SqlSession执行流程
  • Java FrameWorks
    • Mybatis
      • Mybatis(八) - 缓存
  • Java FrameWorks
    • Mybatis
      • Mybatis(六) - 动态SQL的参数绑定与执行
  • Java FrameWorks
    • Mybatis
      • Mybatis(十) - 插件
  • Java FrameWorks
    • Mybatis
      • Mybatis(十一) - 日志
  • Java FrameWorks
    • Mybatis
      • Mybatis(四) - Mapper接口解析
  • Java FrameWorks
    • Netty
      • Netty 可靠性分析
  • Java FrameWorks
    • Netty
      • Netty - Netty 线程模型
  • Java FrameWorks
    • Netty
      • Netty堆外内存泄露排查盛宴
  • Java FrameWorks
    • Netty
      • Netty高级 - 高性能之道
  • Java FrameWorks
    • Shiro
      • Shiro + JWT + Spring Boot Restful 简易教程
  • Java FrameWorks
    • Shiro
      • 非常详尽的 Shiro 架构解析!
  • Java FrameWorks
    • Spring
      • Spring AOP 使用介绍,从前世到今生
  • Java FrameWorks
    • Spring
      • Spring AOP 源码解析
  • Java FrameWorks
    • Spring
      • Spring Event 实现原理
  • Java FrameWorks
    • Spring
      • Spring Events
  • Java FrameWorks
    • Spring
      • Spring IOC容器源码分析
  • Java FrameWorks
    • Spring
      • Spring Integration简介
  • Java FrameWorks
    • Spring
      • Spring MVC 框架中拦截器 Interceptor 的使用方法
  • Java FrameWorks
    • Spring
      • Spring bean 解析、注册、实例化流程源码剖析
  • Java FrameWorks
    • Spring
      • Spring validation中@NotNull、@NotEmpty、@NotBlank的区别
  • Java FrameWorks
    • Spring
      • Spring 如何解决循环依赖?
  • Java FrameWorks
    • Spring
      • Spring 异步实现原理与实战分享
  • Java FrameWorks
    • Spring
      • Spring中的“for update”问题
  • Java FrameWorks
    • Spring
      • Spring中的设计模式
  • Java FrameWorks
    • Spring
      • Spring事务失效的 8 大原因
  • Java FrameWorks
    • Spring
      • Spring事务管理详解
  • Java FrameWorks
    • Spring
      • Spring计时器StopWatch使用
  • Java FrameWorks
    • Spring
      • 详述 Spring MVC 框架中拦截器 Interceptor 的使用方法
  • Java FrameWorks
    • Spring
      • 透彻的掌握 Spring 中@transactional 的使用
  • Java
    • Java IO&NIO&AIO
      • Java IO - BIO 详解
  • Java
    • Java IO&NIO&AIO
      • Java NIO - IO多路复用详解
  • Java
    • Java IO&NIO&AIO
      • Java N(A)IO - Netty
  • Java
    • Java IO&NIO&AIO
      • Java IO - Unix IO模型
  • Java
    • Java IO&NIO&AIO
      • Java IO - 分类
  • Java
    • Java IO&NIO&AIO
      • Java NIO - 基础详解
  • Java
    • Java IO&NIO&AIO
      • Java IO - 常见类使用
  • Java
    • Java IO&NIO&AIO
      • Java AIO - 异步IO详解
  • Java
    • Java IO&NIO&AIO
      • Java IO概述
  • Java
    • Java IO&NIO&AIO
      • Java IO - 设计模式
  • Java
    • Java IO&NIO&AIO
      • Java NIO - 零拷贝实现
  • Java
    • Java JVM
      • JVM 优化经验总结
  • Java
    • Java JVM
      • JVM 内存结构
  • Java
    • Java JVM
      • JVM参数设置
  • Java
    • Java JVM
      • Java 内存模型
  • Java
    • Java JVM
      • 从实际案例聊聊Java应用的GC优化
  • Java
    • Java JVM
      • Java 垃圾回收器G1详解
  • Java
    • Java JVM
      • 垃圾回收器Shenandoah GC详解
  • Java
    • Java JVM
      • 垃圾回收器ZGC详解
  • Java
    • Java JVM
      • 垃圾回收基础
  • Java
    • Java JVM
      • 如何优化Java GC
  • Java
    • Java JVM
      • 类加载机制
  • Java
    • Java JVM
      • 类字节码详解
  • Java
    • Java 基础
      • Java hashCode() 和 equals()
  • Java
    • Java 基础
      • Java 基础 - Java native方法以及JNI实践
  • Java
    • Java 基础
      • Java serialVersionUID 有什么作用?
  • Java
    • Java 基础
      • Java 泛型的类型擦除
  • Java
    • Java 基础
      • Java 基础 - Unsafe类解析
  • Java
    • Java 基础
      • Difference Between Statement and PreparedStatement
  • Java
    • Java 基础
      • Java 基础 - SPI机制详解
  • Java
    • Java 基础
      • Java 基础 - final
  • Java
    • Java 基础
      • Java中static关键字详解
  • Java
    • Java 基础
      • 为什么说Java中只有值传递?
  • Java
    • Java 基础
      • Java 基础 - 即时编译器原理解析及实践
  • Java
    • Java 基础
      • Java 基础 - 反射
  • Java
    • Java 基础
      • Java多态的面试题
  • Java
    • Java 基础
      • Java 基础 - 异常机制详解
  • Java
    • Java 基础
      • 为什么要有抽象类?
  • Java
    • Java 基础
      • 接口的本质
  • Java
    • Java 基础
      • Java 基础 - 枚举
  • Java
    • Java 基础
      • Java 基础 - 泛型机制详解
  • Java
    • Java 基础
      • Java 基础 - 注解机制详解
  • Java
    • Java 基础
      • 为什么 String hashCode 方法选择数字31作为乘子
  • Java
    • Java 并发
      • Java 并发 - 14个Java并发容器
  • Java
    • Java 并发
      • Java 并发 - AQS
  • Java
    • Java 并发
      • Java 并发 - BlockingQueue
  • Java
    • Java 并发
      • Java 并发 - CAS
  • Java
    • Java 并发
      • Java 并发 - Condition接口
  • Java
    • Java 并发
      • Java 并发 - CopyOnWriteArrayList
  • Java
    • Java 并发
      • Java 并发 - CountDownLatch、CyclicBarrier和Phaser对比
  • Java
    • Java 并发
      • Java 并发 - Fork&Join框架
  • Java
    • Java 并发
      • Java 并发 - Java CompletableFuture 详解
  • Java
    • Java 并发
      • Java 并发 - Java 线程池
  • Java
    • Java 并发
      • Java 并发 - Lock接口
  • Java
    • Java 并发
      • Java 并发 - ReentrantLock
  • Java
    • Java 并发
      • Java 并发 - ReentrantReadWriteLock
  • Java
    • Java 并发
      • Java 并发 - Synchronized
  • Java
    • Java 并发
      • Java 并发 - ThreadLocal 内存泄漏问题
  • Java
    • Java 并发
      • Java 并发 - ThreadLocal
  • Java
    • Java 并发
      • Java 并发 - Volatile
  • Java
    • Java 并发
      • Java 并发 - 从ReentrantLock的实现看AQS的原理及应用
  • Java
    • Java 并发
      • Java 并发 - 公平锁和非公平锁
  • Java
    • Java 并发
      • Java 并发 - 内存模型
  • Java
    • Java 并发
      • Java 并发 - 原子类
  • Java
    • Java 并发
      • Java 并发 - 如何确保三个线程顺序执行?
  • Java
    • Java 并发
      • Java 并发 - 锁
  • Java
    • Java 的新特性
      • Java 10 新特性概述
  • Java
    • Java 的新特性
      • Java 11 新特性概述
  • Java
    • Java 的新特性
      • Java 12 新特性概述
  • Java
    • Java 的新特性
      • Java 13 新特性概述
  • Java
    • Java 的新特性
      • Java 14 新特性概述
  • Java
    • Java 的新特性
      • Java 15 新特性概述
  • Java
    • Java 的新特性
      • Java 8的新特性
  • Java
    • Java 的新特性
      • Java 9 新特性概述
  • Java
    • Java 调试排错
      • 调试排错 - Java Debug Interface(JDI)详解
  • Java
    • Java 调试排错
      • 调试排错 - CPU 100% 排查优化实践
  • Java
    • Java 调试排错
      • 调试排错 - Java Heap Dump分析
  • Java
    • Java 调试排错
      • 调试排错 - Java Thread Dump分析
  • Java
    • Java 调试排错
      • 调试排错 - Java动态调试技术原理
  • Java
    • Java 调试排错
      • 调试排错 - Java应用在线调试Arthas
  • Java
    • Java 调试排错
      • 调试排错 - Java问题排查:工具单
  • Java
    • Java 调试排错
      • 调试排错 - 内存溢出与内存泄漏
  • Java
    • Java 调试排错
      • 调试排错 - 在线分析GC日志的网站GCeasy
  • Java
    • Java 调试排错
      • 调试排错 - 常见的GC问题分析与解决
  • Java
    • Java 集合
      • Java 集合 - ArrayList
  • Java
    • Java 集合
      • Java 集合 - HashMap 和 ConcurrentHashMap
  • Java
    • Java 集合
      • Java 集合 - HashMap的死循环问题
  • Java
    • Java 集合
      • Java 集合 - LinkedHashSet&Map
  • Java
    • Java 集合
      • Java 集合 - LinkedList
  • Java
    • Java 集合
      • Java 集合 - PriorityQueue
  • Java
    • Java 集合
      • Java 集合 - Stack & Queue
  • Java
    • Java 集合
      • Java 集合 - TreeSet & TreeMap
  • Java
    • Java 集合
      • Java 集合 - WeakHashMap
  • Java
    • Java 集合
      • Java 集合 - 为什么HashMap的容量是2的幂次方
  • Java
    • Java 集合
      • Java 集合 - 概览
  • Java
    • Java 集合
      • Java 集合 - 高性能队列Disruptor详解
  • 分布式
    • RPC
      • ⭐️RPC - Dubbo&hsf&Spring cloud的区别
  • 分布式
    • RPC
      • ⭐️RPC - Dubbo的架构原理
  • 分布式
    • RPC
      • ⭐️RPC - HSF的原理分析
  • 分布式
    • RPC
      • ⭐️RPC - 你应该知道的RPC原理
  • 分布式
    • RPC
      • ⭐️RPC - 动态代理
  • 分布式
    • RPC
      • 深入理解 RPC 之协议篇
  • 分布式
    • RPC
      • RPC - 序列化和反序列化
  • 分布式
    • RPC
      • ⭐️RPC - 服务注册与发现
  • 分布式
    • RPC
      • RPC - 核心原理
  • 分布式
    • RPC
      • ⭐️RPC - 框架对比
  • 分布式
    • RPC
      • ⭐️RPC - 网络通信
  • 分布式
    • 分布式事务
      • 分布式事务 Seata TCC 模式深度解析
  • 分布式
    • 分布式事务
      • 分布式事务的实现原理
  • 分布式
    • 分布式事务
      • 常用的分布式事务解决方案
  • 分布式
    • 分布式事务
      • 手写实现基于消息队列的分布式事务框架
  • 分布式
    • 分布式算法
      • CAP 定理的含义
  • 分布式
    • 分布式算法
      • Paxos和Raft比较
  • 分布式
    • 分布式算法
      • 分布式一致性与共识算法
  • 分布式
    • 分布式锁
      • ⭐️分布式锁的原理及实现方式
  • 分布式
    • 搜索引擎
      • ElasticSearch与SpringBoot的集成与JPA方法的使用
  • 分布式
    • 搜索引擎
      • 全文搜索引擎 Elasticsearch 入门教程
  • 分布式
    • 搜索引擎
      • 十分钟学会使用 Elasticsearch 优雅搭建自己的搜索系统
  • 分布式
    • 搜索引擎
      • 腾讯万亿级 Elasticsearch 技术解密
  • 分布式
    • 日志系统
      • Grafana Loki 简明教程
  • 分布式
    • 日志系统
      • 分布式系统中如何优雅地追踪日志
  • 分布式
    • 日志系统
      • 如何优雅地记录操作日志?
  • 分布式
    • 日志系统
      • 日志收集组件—Flume、Logstash、Filebeat对比
  • 分布式
    • 日志系统
      • 集中式日志系统 ELK 协议栈详解
  • 分布式
    • 消息队列
      • 消息队列 - Kafka
  • 分布式
    • 消息队列
      • 消息队列 - Kafka、RabbitMQ、RocketMQ等消息中间件的对比
  • 分布式
    • 消息队列
      • 消息队列之 RabbitMQ
  • 分布式
    • 消息队列
      • 消息队列 - 使用docker-compose构建kafka集群
  • 分布式
    • 消息队列
      • 消息队列 - 分布式系统与消息的投递
  • 分布式
    • 消息队列
      • 消息队列 - 如何保证消息的可靠性传输
  • 分布式
    • 消息队列
      • 消息队列 - 如何保证消息的顺序性
  • 分布式
    • 消息队列
      • 消息队列 - 如何保证消息队列的高可用
  • 分布式
    • 消息队列
      • 消息队列 - 消息队列设计精要
  • 分布式
    • 监控系统
      • 深度剖析开源分布式监控CAT
  • 大数据
    • Flink
      • Flink架构与核心组件
  • 微服务
    • Dubbo
      • 基于dubbo的分布式应用中的统一异常处理
  • 微服务
    • Dubbo
      • Vim快捷键
  • 微服务
    • Service Mesh
      • Istio 是什么?
  • 微服务
    • Service Mesh
      • OCTO 2.0:美团基于Service Mesh的服务治理系统详解
  • 微服务
    • Service Mesh
      • Service Mesh是什么?
  • 微服务
    • Service Mesh
      • Spring Cloud向Service Mesh迁移
  • 微服务
    • Service Mesh
      • 数据挖掘算法
  • 微服务
    • Service Mesh
      • Seata Saga 模式
  • 微服务
    • Spring Cloud
      • Seata TCC 模式
  • 微服务
    • Spring Cloud
      • Spring Cloud Config
  • 微服务
    • Spring Cloud
      • Seata AT 模式
  • 微服务
    • Spring Cloud
      • Spring Cloud Gateway
  • 微服务
    • Spring Cloud
      • Spring Cloud OpenFeign 的核心原理
  • 微服务
    • Spring Cloud
      • Seata XA 模式
  • 数据库
    • Database Version Control
      • Liquibase vs. Flyway
  • 数据库
    • Database Version Control
      • Six reasons to version control your database
  • 数据库
    • MySQL
      • How Sharding Works
  • 数据库
    • MySQL
      • MySQL InnoDB中各种SQL语句加锁分析
  • 数据库
    • MySQL
      • MySQL 事务隔离级别和锁
  • 数据库
    • MySQL
      • MySQL 索引性能分析概要
  • 数据库
    • MySQL
      • MySQL 索引设计概要
  • 数据库
    • MySQL
      • MySQL出现Waiting for table metadata lock的原因以及解决方法
  • 数据库
    • MySQL
      • MySQL的Limit性能问题
  • 数据库
    • MySQL
      • MySQL索引优化explain
  • 数据库
    • MySQL
      • MySQL索引背后的数据结构及算法原理
  • 数据库
    • MySQL
      • MySQL行转列、列转行问题
  • 数据库
    • MySQL
      • 一条SQL更新语句是如何执行的?
  • 数据库
    • MySQL
      • 一条SQL查询语句是如何执行的?
  • 数据库
    • MySQL
      • 为什么 MySQL 使用 B+ 树
  • 数据库
    • MySQL
      • 为什么 MySQL 的自增主键不单调也不连续
  • 数据库
    • MySQL
      • 为什么我的MySQL会“抖”一下?
  • 数据库
    • MySQL
      • 为什么数据库不应该使用外键
  • 数据库
    • MySQL
      • 为什么数据库会丢失数据
  • 数据库
    • MySQL
      • 事务的可重复读的能力是怎么实现的?
  • 数据库
    • MySQL
      • 大众点评订单系统分库分表实践
  • 数据库
    • MySQL
      • 如何保证缓存与数据库双写时的数据一致性?
  • 数据库
    • MySQL
      • 浅谈数据库并发控制 - 锁和 MVCC
  • 数据库
    • MySQL
      • 深入浅出MySQL 中事务的实现
  • 数据库
    • MySQL
      • 浅入浅出MySQL 和 InnoDB
  • 数据库
    • PostgreSQL
      • PostgreSQL upsert功能(insert on conflict do)的用法
  • 数据库
    • Redis
      • Redis GEO & 实现原理深度分析
  • 数据库
    • Redis
      • Redis 和 I/O 多路复用
  • 数据库
    • Redis
      • Redis分布式锁
  • 数据库
    • Redis
      • Redis实现分布式锁中的“坑”
  • 数据库
    • Redis
      • Redis总结
  • 数据库
    • Redis
      • 史上最全Redis高可用技术解决方案大全
  • 数据库
    • Redis
      • Redlock:Redis分布式锁最牛逼的实现
  • 数据库
    • Redis
      • 为什么 Redis 选择单线程模型
  • 数据库
    • TiDB
      • 新一代数据库TiDB在美团的实践
  • 数据库
    • 数据仓库
      • 实时数仓在有赞的实践
  • 数据库
    • 数据库原理
      • OLTP与OLAP的关系是什么?
  • 数据库
    • 数据库原理
      • 为什么 OLAP 需要列式存储
  • 系统设计
    • DDD
      • Domain Primitive
  • 系统设计
    • DDD
      • Repository模式
  • 系统设计
    • DDD
      • 应用架构
  • 系统设计
    • DDD
      • 聊聊如何避免写流水账代码
  • 系统设计
    • DDD
      • 领域层设计规范
  • 系统设计
    • DDD
      • 从三明治到六边形
  • 系统设计
    • DDD
      • 阿里盒马领域驱动设计实践
  • 系统设计
    • DDD
      • 领域驱动设计(DDD)编码实践
  • 系统设计
    • DDD
      • 领域驱动设计在互联网业务开发中的实践
  • 系统设计
    • 基础架构
      • 容错,高可用和灾备
  • 系统设计
    • 数据聚合
      • GraphQL及元数据驱动架构在后端BFF中的实践
  • 系统设计
    • 数据聚合
      • 高效研发-闲鱼在数据聚合上的探索与实践
  • 系统设计
    • 服务安全
      • JSON Web Token 入门教程
  • 系统设计
    • 服务安全
      • 你还在用JWT做身份认证嘛?
  • 系统设计
    • 服务安全
      • 凭证(Credentials)
  • 系统设计
    • 服务安全
      • 授权(Authorization)
  • 系统设计
    • 服务安全
      • 理解OAuth2.0
  • 系统设计
    • 服务安全
      • 认证(Authentication)
  • 系统设计
    • 架构案例
      • 微信 Android 客户端架构演进之路
  • 系统设计
    • 高可用架构
      • 业务高可用的保障:异地多活架构
  • 计算机基础
    • 字符编码
      • Base64原理解析
  • 计算机基础
    • 字符编码
      • 字符编码笔记:ASCII,Unicode 和 UTF-8
  • 计算机基础
    • 操作系统
      • 为什么 CPU 访问硬盘很慢
  • 计算机基础
    • 操作系统
      • 为什么 HTTPS 需要 7 次握手以及 9 倍时延
  • 计算机基础
    • 操作系统
      • 为什么 Linux 默认页大小是 4KB
  • 计算机基础
    • 操作系统
      • 磁盘IO那些事
  • 计算机基础
    • 操作系统
      • 虚拟机的3种网络模式
  • 计算机基础
    • 服务器
      • mac终端bash、zsh、oh-my-zsh最实用教程
  • 计算机基础
    • 服务器
      • Nginx强制跳转Https
  • 计算机基础
    • 服务器
      • curl 的用法指南
  • 计算机基础
    • 网络安全
      • 如何设计一个安全的对外接口?
  • 计算机基础
    • 网络安全
      • 浅谈常见的七种加密算法及实现
  • 计算机基础
    • 网络编程
      • MQTT - The Standard for IoT Messaging
  • 计算机基础
    • 网络编程
      • 两万字长文 50+ 张趣图带你领悟网络编程的内功心法
  • 计算机基础
    • 网络编程
      • 为什么 TCP 协议有 TIME_WAIT 状态
  • 计算机基础
    • 网络编程
      • 为什么 TCP 协议有性能问题
  • 计算机基础
    • 网络编程
      • 为什么 TCP 协议有粘包问题
  • 计算机基础
    • 网络编程
      • 为什么 TCP 建立连接需要三次握手
  • 计算机基础
    • 网络编程
      • 为什么 TCP/IP 协议会拆分数据
  • 计算机基础
    • 网络编程
      • 使用 OAuth 2 和 JWT 为微服务提供安全保障
  • 计算机基础
    • 网络编程
      • 四种常见的 POST 提交数据方式
  • 计算机基础
    • 网络编程
      • 有赞TCP网络编程最佳实践
  • 计算机基础
    • 网络编程
      • 看完这篇HTTP,跟面试官扯皮就没问题了
  • 计算机基础
    • 网络编程
      • 详细解析 HTTP 与 HTTPS 的区别
  • 质量&效率
    • 快捷键
      • Idea快捷键(Mac版)
  • 质量&效率
    • 快捷键
      • Shell快捷键
  • 质量&效率
    • 快捷键
      • conduit
  • 质量&效率
    • 敏捷开发
      • Scrum的3种角色
  • 质量&效率
    • 敏捷开发
      • Scrum的4种会议
  • 质量&效率
    • 敏捷开发
      • ThoughtWorks的敏捷开发
  • 质量&效率
    • 敏捷开发
      • 敏捷开发入门教程
  • 运维&测试
    • Docker
      • Docker (容器) 的原理
  • 运维&测试
    • Docker
      • Docker Compose:链接外部容器的几种方式
  • 运维&测试
    • Docker
      • Docker 入门教程
  • 运维&测试
    • Docker
      • Docker 核心技术与实现原理
  • 运维&测试
    • Docker
      • Dockerfile 最佳实践
  • 运维&测试
    • Docker
      • Docker开启Remote API 访问 2375端口
  • 运维&测试
    • Docker
      • Watchtower - 自动更新 Docker 镜像与容器
  • 运维&测试
    • Kubernetes
      • Kubernetes 介绍
  • 运维&测试
    • Kubernetes
      • Kubernetes 在有赞的实践
  • 运维&测试
    • Kubernetes
      • Kubernetes 学习路径
  • 运维&测试
    • Kubernetes
      • Kubernetes如何改变美团的云基础设施?
  • 运维&测试
    • Kubernetes
      • Kubernetes的三种外部访问方式:NodePort、LoadBalancer 和 Ingress
  • 运维&测试
    • Kubernetes
      • 谈 Kubernetes 的架构设计与实现原理
  • 运维&测试
    • 压测
      • 全链路压测平台(Quake)在美团中的实践
  • 运维&测试
    • 测试
      • Cpress - JavaScript End to End Testing Framework
  • 运维&测试
    • 测试
      • 代码覆盖率-JaCoCo
  • 运维&测试
    • 测试
      • 浅谈代码覆盖率
  • 运维&测试
    • 测试
      • 测试中 Fakes、Mocks 以及 Stubs 概念明晰
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP中的Bean是如何被AOP代理的
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP原生动态代理和Cglib动态代理
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP实现方式(xml&注解)
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP是如何收集切面类并封装的
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP概述
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP的底层核心后置处理器
  • Java FrameWorks
    • Spring
      • Spring AOP
        • Spring AOP的延伸知识
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(一)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(三)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(二)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(五)
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - IOC(四) - 循环依赖与解决方案
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot - 启动引导
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot JarLauncher
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot Web Mvc 自动装配
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot 使用ApplicationListener监听器
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot 声明式事务
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot 嵌入式容器
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot引起的“堆外内存泄漏”排查及经验总结
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot的启动流程
  • Java FrameWorks
    • Spring
      • Spring Boot
        • Spring Boot自动化配置源码分析
  • Java FrameWorks
    • Spring
      • Spring Boot
        • 如何自定义Spring Boot Starter?
  • Java FrameWorks
    • Spring
      • Spring IOC
        • IOC - 模块装配和条件装配
  • Java FrameWorks
    • Spring
      • Spring IOC
        • IOC - 配置源(xml,注解)
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Environment
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring ApplicationContext
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanDefinition
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanFactory
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanFactoryPostProcessor
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring BeanPostProcessor
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(一) - 概述
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(三) - 实例化阶段
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(二) - BeanDefinition
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(五) - 销毁阶段
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Bean的生命周期(四) - 初始化阶段
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring ComponentScan
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Events
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC 基础篇
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC 总结
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC 进阶篇
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring IOC容器的生命周期
  • Java FrameWorks
    • Spring
      • Spring IOC
        • Spring Resource
  • Java FrameWorks
    • Spring
      • Spring MVC
        • DispatcherServlet的初始化原理
  • Java FrameWorks
    • Spring
      • Spring MVC
        • DispatcherServlet的核心工作原理
  • Java FrameWorks
    • Spring
      • Spring MVC
        • WebMvc的架构设计与组件功能解析
  • Java FrameWorks
    • Spring
      • Spring Security
        • Spring Boot 2 + Spring Security 5 + JWT 的单页应用 Restful 解决方案
  • Java FrameWorks
    • Spring
      • Spring Security
        • Spring Security Oauth
  • Java FrameWorks
    • Spring
      • Spring Security
        • Spring Security
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • DispatcherHandler的工作原理(传统方式)
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • DispatcherHandler的工作原理(函数式端点)
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • WebFlux的自动装配
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • 快速了解响应式编程与Reactive
  • Java FrameWorks
    • Spring
      • Spring WebFlux
        • 快速使用WebFlux
  • 分布式
    • 协调服务
      • Zookeeper
        • Zookeeper - 客户端之 Curator
  • 分布式
    • 协调服务
      • Zookeeper
        • 详解分布式协调服务 ZooKeeper
  • 分布式
    • 协调服务
      • etcd
        • 高可用分布式存储 etcd 的实现原理
  • 数据库
    • Database Version Control
      • Flyway
        • Database Migrations with Flyway
  • 数据库
    • Database Version Control
      • Flyway
        • How Flyway works
  • 数据库
    • Database Version Control
      • Flyway
        • Rolling Back Migrations with Flyway
  • 数据库
    • Database Version Control
      • Flyway
        • The meaning of the concept of checksums
  • 数据库
    • Database Version Control
      • Liquibase
        • Introduction to Liquibase Rollback
  • 数据库
    • Database Version Control
      • Liquibase
        • LiquiBase中文学习指南
  • 数据库
    • Database Version Control
      • Liquibase
        • Use Liquibase to Safely Evolve Your Database Schema
  • 系统设计
    • 流量控制
      • RateLimiter
        • Guava Rate Limiter实现分析
  • 系统设计
    • 流量控制
      • Sentinel
        • Sentinel 与 Hystrix 的对比
  • 系统设计
    • 流量控制
      • Sentinel
        • Sentinel工作主流程
  • 系统设计
    • 流量控制
      • 算法
        • 分布式服务限流实战
  • 系统设计
    • 解决方案
      • 秒杀系统
        • 如何设计一个秒杀系统
  • 系统设计
    • 解决方案
      • 红包系统
        • 微信高并发资金交易系统设计方案--百亿红包背后的技术支撑
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 什么是预排序遍历树算法(MPTT,Modified Preorder Tree Traversal)
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 加密算法
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 推荐系统算法
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • linkerd
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 查找算法
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 缓存淘汰算法中的LRU和LFU
  • 计算机基础
    • 数据结构与算法
      • 其他相关
        • 负载均衡算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - Paxos算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - Raft算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - Snowflake算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - ZAB算法
  • 计算机基础
    • 数据结构与算法
      • 分布式算法
        • 分布式算法 - 一致性Hash算法
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - Bitmap & Bloom Filter
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - Map & Reduce
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - Trie树/数据库/倒排索引
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 分治/hash/排序
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 双层桶划分
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 外(磁盘文件)排序
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理 - 布隆过滤器
  • 计算机基础
    • 数据结构与算法
      • 大数据处理
        • 大数据处理算法
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 文本预处理:后缀树(Suffix Tree)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 模式预处理:BM 算法 (Boyer-Moore)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 模式预处理:KMP 算法(Knuth-Morris-Pratt)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配 - 模式预处理:朴素算法(Naive)(暴力破解)
  • 计算机基础
    • 数据结构与算法
      • 字符串匹配算法
        • 字符串匹配
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 分支限界算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 分治算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 动态规划算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 回溯算法
  • 计算机基础
    • 数据结构与算法
      • 常用算法
        • 贪心算法
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 十大排序算法
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(一)之3种简单排序(选择,冒泡,直接插入)
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(三)之堆排序
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(二)之希尔排序
  • 计算机基础
    • 数据结构与算法
      • 排序算法
        • 图解排序算法(四)之归并排序
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 树的高度和深度
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 红黑树深入剖析及Java实现
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 线性结构 - Hash
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 线性结构 - 数组、链表、栈、队列
  • 计算机基础
    • 数据结构与算法
      • 数据结构
        • 逻辑结构 - 树
  • 运维&测试
    • 测试
      • Spock
        • Groovy 简明教程
  • 运维&测试
    • 测试
      • Spock
        • Spock 官方文档
  • 运维&测试
    • 测试
      • Spock
        • Spock单元测试框架介绍以及在美团优选的实践
  • 运维&测试
    • 测试
      • TDD
        • TDD 实践 - FizzFuzzWhizz(一)
  • 运维&测试
    • 测试
      • TDD
        • TDD 实践 - FizzFuzzWhizz(三)
  • 运维&测试
    • 测试
      • TDD
        • TDD 实践 - FizzFuzzWhizz(二)
  • 运维&测试
    • 测试
      • TDD
        • 测试驱动开发(TDD)- 原理篇
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Nacos
          • Nacos 服务注册的原理
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Nacos
          • Nacos 配置中心原理分析
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • 服务调用过程
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • Spring Cloud Bus
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • Spring Cloud Consul
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Seata
          • Spring Cloud Stream
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Sentinel
          • Sentinel 与 Hystrix 的对比
  • 微服务
    • Spring Cloud
      • Spring Cloud Alibaba
        • Sentinel
          • Sentinel
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • How Hystrix Works
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • Hystrix
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • Hystrix原理与实战
  • 微服务
    • Spring Cloud
      • Spring Cloud Netflix
        • Hystrix
          • Spring Cloud Hystrix基本原理
由 GitBook 提供支持
在本页
  • 1. 基于xml的aspect实现AOP
  • 1.0 测试代码的搭建
  • 1.1 基于xml的基本环境搭建
  • 1.2 基于xml的AOP实现
  • 1.3 切入点表达式的多种写法
  • 2. 基于AspectJ实现AOP
  • 2.1 Spring AOP与AspectJ
  • 2.2 基于注解的AOP配置
  • 2.3 环绕通知的编写
  • 2.4 切入点表达式的更多使用方法

这有帮助吗?

  1. Java FrameWorks
  2. Spring
  3. Spring AOP

Spring AOP实现方式(xml&注解)

1. 基于xml的aspect实现AOP

1.0 测试代码的搭建

为了接下来的演示更具有通用性,咱这里造一个 Service 层的接口,一个接口的实现类,一个普通的 Service 类,以及一个切面类 Logger :

OrderService :

public interface OrderService {
    
    void createOrder();
    
    void deleteOrderById(String id);
    
    String getOrderById(String id);
    
    List<String> findAll();
}

OrderServiceImpl :

public class OrderServiceImpl implements OrderService {
    
    @Override
    public void createOrder() {
        System.out.println("OrderServiceImpl 创建订单。。。");
    }
    
    @Override
    public void deleteOrderById(String id) {
        System.out.println("OrderServiceImpl 删除订单,id为" + id);
    }
    
    @Override
    public String getOrderById(String id) {
        System.out.println("OrderServiceImpl 查询订单,id为" + id);
        return id;
    }
    
    @Override
    public List<String> findAll() {
        System.out.println("OrderServiceImpl 查询所有订单。。。");
        return Arrays.asList("111", "222", "333");
    }
}

FinanceService :

public class FinanceService {
    
    public void addMoney(double money) {
        System.out.println("FinanceService 收钱 === " + money);
    }
    
    public double subtractMoney(double money) {
        System.out.println("FinanceService 付钱 === " + money);
        return money;
    }
    
    public double getMoneyById(String id) {
        System.out.println("FinanceService 查询账户,id为" + id);
        return Math.random();
    }
}

Logger 切面类:

public class Logger {
    
    public void beforePrint() {
        System.out.println("Logger beforePrint run ......");
    }
    
    public void afterPrint() {
        System.out.println("Logger afterPrint run ......");
    }
    
    public void afterReturningPrint() {
        System.out.println("Logger afterReturningPrint run ......");
    }
    
    public void afterThrowingPrint() {
        System.out.println("Logger afterThrowingPrint run ......");
    }
}

1.1 基于xml的基本环境搭建

1.1.1 导入Maven坐标依赖

既然是学习 SpringFramework 的 AOP ,那自然就要引入 Spring 的 AOP 模块对应的依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

注意,这里导入 aop 的依赖之后,借助 IDEA 的 Maven 窗口,可以发现 spring-aop 模块其实已经被 spring-context 模块依赖了:

所以导不导 aop 的模块,当前工程中早就已经有 spring-aop 这个 jar 包的依赖啦。

1.1.2 编写配置文件

既然是基于 xml 配置文件的,那咱先把配置文件搞定。

在工程的 resources 目录下新建一个 xmlaspect.xml 文件,并首先把上面提到的几个类都注册进 IOC 容器中:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="financeService" class="com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService"/>

    <bean id="orderService" class="com.linkedbear.spring.aop.a_xmlaspect.service.impl.OrderServiceImpl"/>

    <bean id="logger" class="com.linkedbear.spring.aop.a_xmlaspect.component.Logger"/>
</beans>

1.1.3 测试运行

先不干任何多余的事情,直接编写启动类,驱动 IOC 容器并取出 FinanceService ,调用它的方法:

public class XmlAspectApplication {
    
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop/xmlaspect.xml");
        FinanceService financeService = ctx.getBean(FinanceService.class);
        financeService.addMoney(123.45);
        System.out.println(financeService.getMoneyById("abc"));
    }
}

运行 main 方法,控制台打印原生的对象输出的结果:

FinanceService 收钱 === 123.45
FinanceService 查询账户,id为abc
0.08681906193896294

至此,这些都是在前面 IOC 的基础内容了,接下来才是正儿八经的基于 xml 的 AOP 。

1.2 基于xml的AOP实现

要配置 xml 的 AOP ,需要几个步骤,咱一步一步来。

1.2.1 导入命名空间

要编写 AOP 的配置,需要在 xml 上导入命名空间:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd 
                           http://www.springframework.org/schema/aop 
                           https://www.springframework.org/schema/aop/spring-aop.xsd">

然后,在配置文件中按提示键,会发现多了 3 个 aop 开头的标签:

1.2.2 编写aop配置

接下来就要利用上面的这三个标签中的 <aop:config> 来配置 AOP 了。这个配置也比较简单,就两步。第一步要先声明一个切面:

<bean id="logger" class="com.linkedbear.spring.aop.a_xmlaspect.component.Logger"/>

<aop:config>
    <aop:aspect id="loggerAspect" ref="logger">
        
    </aop:aspect>
</aop:config>

一个 aspect 就是一个切面,id 随便起,只要是全局唯一即可;ref 跟 IOC 部分提到的 ref 一样,都是引用容器中的某个 bean ,这里咱要使用 Logger 作为切面类,所以 ref 就引用 logger 这个 bean 。

接下来,咱要配置一下通知类型。上一章咱说过了 Spring 一共有 5 种通知类型,这里咱先配置一个前置通知:

<bean id="logger" class="com.linkedbear.spring.aop.a_xmlaspect.component.Logger"/>

<aop:config>
    <aop:aspect id="loggerAspect" ref="logger">
        <aop:before method="beforePrint"
                    pointcut="??????"/>
    </aop:aspect>
</aop:config>

有了通知方法 method 了,切入点怎么搞定呢?哎,这里咱要学习一个新的知识点:切入点表达式。

1.2.3 切入点表达式入门

最开始学习切入点表达式,咱先介绍最最常用的一种写法,而且这种写法刚好对标的就是 AOP 术语中的切入点。

这样,小册先写一个,小伙伴们先瞅瞅这都什么含义:

execution(public void com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService.addMoney(double))

是不是貌似还有点门道呢?下面咱来解释这个表达式的含义:

  • execution :以此法编写的切入点表达式,将使用方法定位的模式匹配连接点

    • 说白了,用 execution 写出来的表达式,都是直接声明到类中的方法的

  • public :限定只切入 public 类型的方法

  • void :限定只切入返回值类型为 void 的方法

  • com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService :限定只切入 FinanceService 这个类的方法

  • addMoney :限定只切入方法名为 addMoney 的方法

  • (double) :限定只切入方法的参数列表为一个参数,且类型为 double 的方法

所以,用这个表达式,就可以直接锁定到上面 FinanceService 的 addMoney 方法。

1.2.4 应用切入点表达式

接下来咱把上面写好的切入点表达式填到 pointcut 里:

<bean id="logger" class="com.linkedbear.spring.aop.a_xmlaspect.component.Logger"/>

<aop:config>
    <aop:aspect id="loggerAspect" ref="logger">
        <aop:before method="beforePrint"
                    pointcut="execution(public void com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService.addMoney(double))"/>
    </aop:aspect>
</aop:config>

1.2.5 测试运行

编写测试启动类,使用 xml 配置文件驱动 IOC 容器,并从 IOC 容器中取出 FinanceService ,分别执行它的三个方法:

public class XmlAspectApplication {
    
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop/xmlaspect.xml");
        FinanceService financeService = ctx.getBean(FinanceService.class);
        financeService.addMoney(123.45);
        financeService.subtractMoney(543.21);
        financeService.getMoneyById("abc");
    }
}

运行 main 方法,控制台打印了 Logger 的前置通知方法 beforePrint :

Logger beforePrint run ......
FinanceService 收钱 === 123.45
FinanceService 付钱 === 543.21
FinanceService 查询账户,id为abc

确实,上面编写的切入点表达式已经生效了,AOP 的效果得以体现。

1.3 切入点表达式的多种写法

咱继续讲解切入点表达式的编写方式哈。切入点表达式的写法比较多,咱先掌握 execution 风格写法,后面再学习更多的风格。

1.3.1 基本通配符

把上面的切入点表达式改一下,看看小伙伴们是否能猜得到它的含义:

execution(public * com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService.*(double))

还是很好猜的吧!这里有两个地方替换成了通配符 * ,咱解释一下它的含义:

  • void 的位置替换为 * ,代表不限制返回值类型,是什么都可以

  • FinanceService.*(double) 这里面的方法值替换为 * ,代表不限制方法名,什么方法都可以切入

所以,这样被切入的方法就变多了,除了 addMoney 方法之外,subtractMoney 也应该被切入了。

是不是这样呢,咱可以继续配置一个方法来检验一下。在 aop:config 中,继续添加后置通知:

<aop:config>
    <aop:aspect id="loggerAspect" ref="logger">
        <aop:before method="beforePrint"
                    pointcut="execution(public void com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService.addMoney(double))"/>
        <aop:after method="afterPrint"
                   pointcut="execution(public * com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService.*(double))"/>
    </aop:aspect>
</aop:config>

其它的不需要任何改动,直接运行 main 方法,控制台会打印两次 afterPrint 方法,分别是 addMoney 与 subtractMoney 方法的调用,证明确实切到了两个方法。

Logger beforePrint run ......
FinanceService 收钱 === 123.45
Logger afterPrint run ......
FinanceService 付钱 === 543.21
Logger afterPrint run ......
FinanceService 查询账户,id为abc

注意:这个方法参数中,对于基本数据类型,直接声明即可;引用数据类型则要写类的全限定名!

1.3.2 方法通配符

继续修改上面的切入点表达式:

execution(public * com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService.*(*))

这次的参数列表中标注了一个 * ,它代表方法的参数列表中必须有一个参数,至于类型那无所谓。

将 aop:after 的切入点表达式换为上面的写法,重新运行 main 方法,会发现 getMoneyById 方法也生效了:

Logger beforePrint run ......
FinanceService 收钱 === 123.45
Logger afterPrint run ......
FinanceService 付钱 === 543.21
Logger afterPrint run ......
FinanceService 查询账户,id为abc
Logger afterPrint run ......

1.3.3 类名通配符

咱继续变化切入点表达式:

execution(public * com.linkedbear.spring.aop.a_xmlaspect.service.*.*(*))

这次连类名都任意了,所以这下 OrderService 接口也会被切入了。

咱继续编写一个 aop:after-returning 的通知:

<aop:config>
    <aop:aspect id="loggerAspect" ref="logger">
        <aop:before method="beforePrint"
                    pointcut="execution(public void com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService.addMoney(double))"/>
        <aop:after method="afterPrint"
                   pointcut="execution(public * com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService.*(..))"/>
        <aop:after-returning method="afterReturningPrint"
                             pointcut="execution(public * com.linkedbear.spring.aop.a_xmlaspect.service.*.*(..))"/>
    </aop:aspect>
</aop:config>

然后咱点击 aop:after-returning 标签左边的通知标识,发现 OrderService 的实现类也被切入了!

所以我们又得知一个关键点:如果切入点表达式覆盖到了接口,那么如果这个接口有实现类,则实现类上的接口方法也会被切入增强。

1.3.4 方法任意通配

如果我们重载一个 subtractMoney 方法,在方法的参数列表加上一个 id :

public double subtractMoney(double money, String id) {
    System.out.println("FinanceService 付钱 === " + money);
    return money;
}

注意写完这个方法后,IDEA 的左边并没有切入点的影响:

说明 (*) 并不能切入两个参数的方法。那如果我想无论方法参数有几个,甚至没有参数,我都想切入,那该怎么写呢?

答案是换用 .. ,就像这样:

execution(public * com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService.*(..))

这样写完再切到 FinanceService 的类中,就发现所有方法都被切入了。

1.3.5 包名通配符

与类名、方法名的通配符一样,一个 * 代表一个目录,比如下面的这个切入点表达式:

execution(public * com.linkedbear.spring.aop.a_xmlaspect.*.*.*(..))

它代表的是切入 com.linkedbear.spring.aop.a_xmlaspect 包下的一级包下的任意类的任意方法(好绕。。。)。

注入 com.linkedbear.spring.aop.a_xmlaspect.controller 、com.linkedbear.spring.aop.a_xmlaspect.service 、com.linkedbear.spring.aop.a_xmlaspect.dao 等包下的所有类,都会被切到。

如果要切多级包怎么办呢?总不能一个 * 接着一个 * 写吧!所以方法参数列表中的 .. 在这里也能用:

execution(public * com.linkedbear.spring..*.*(..))

这个切入点表达式就代表 com.linkedbear.spring 包下的所有类的所有方法都会被切入。

最后多说一嘴,public 这个访问修饰符可以直接省略不写,代表切入所有访问修饰符的方法,那就相当于变成了这样:

execution(* com.linkedbear.spring..*.*(..))

1.3.6 抛出异常的切入

最后说下抛出异常的切入,对于某些显式声明了会抛出异常的方法,可以使用异常通知来切入这部分方法。

例如咱给 subtractMoney 方法添加一个 Exception 的抛出:

public double subtractMoney(double money, String id) throws Exception {
    System.out.println("FinanceService 付钱 === " + money);
    return money;
}

这样,在切入方法时,可以在类名方法名后面加上 throws 的异常类型即可:

execution(public * com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService.*(..) throws java.lang.Exception)

好了,到这里基本上 execution 风格的切入点表达式写法就差不多了,小伙伴们多多练习几个写法,并配合着 IDE 和测试代码,一定要掌握呀。

2. 基于AspectJ实现AOP

2.1 Spring AOP与AspectJ

在 SpringFramework 的官方文档中,AOP 的介绍下面有一个段落,它说明了 Spring AOP 与 AspectJ 的关系:

docs.spring.io/spring-fram…

Spring provides simple and powerful ways of writing custom aspects by using either a schema-based approach or the @AspectJ annotation style. Both of these styles offer fully typed advice and use of the AspectJ pointcut language while still using Spring AOP for weaving.

Spring 通过使用基于模式的方法或 @AspectJ 注解样式,提供了编写自定义切面的简单而强大的方法。这两种样式都提供了完全类型化的通知,并使用了 AspectJ 切入点表达式语言,同时仍使用 Spring AOP 进行通知的织入。

由此可知,SpringFramework 实现注解配置 AOP ,是整合了 AspectJ 完成的。在第 41 章中,小册也提到了 SpringFramework 中的通知类型就是基于 AspectJ 制定的:

  • Before 前置通知:目标对象的方法调用之前触发

  • After 后置通知:目标对象的方法调用之后触发

  • AfterReturning 返回通知:目标对象的方法调用完成,在返回结果值之后触发

  • AfterThrowing 异常通知:目标对象的方法运行中抛出 / 触发异常后触发

  • Around 环绕通知:编程式控制目标对象的方法调用

2.2 基于注解的AOP配置

2.2.1 标注@Component注解

上一章中咱注册 Bean 是使用 <bean> 标签的方式注册,这一章咱使用注解驱动,那就在两个 Service 类上标注 @Component 注解:

@Component
public class FinanceService { ... }

@Component
public class OrderServiceImpl implements OrderService { ... }

2.2.2 修改Logger切面类

这次使用 AspectJ 注解配置,切面类上也得做改动了。

首先,在 Logger 上标注 @Component 注解,将其注册到 IOC 容器中。然后还得标注一个 **@Aspect** 注解,代表该类是一个切面类:

@Aspect
@Component
public class Logger { ... }

接下来,就是给这些方法标注通知注解了。小册先写一个,小伙伴们一下子就知道了:

@Aspect
@Component
public class Logger {
    
    @Before("execution(public * com.linkedbear.spring.aop.a_xmlaspect.service.FinanceService.*(..))")
    public void beforePrint() {
        System.out.println("Logger beforePrint run ......");
    }
}

嚯,这也太简单了是吧!那前置通知叫 @Before ,那后置通知就是 @After 咯?当然啦,相应的,返回通知 @AfterReturning ,异常通知 @AfterThrowing ,环绕通知 @Around 。

这些编写思路都是一样的,所以咱可以简单的这样写一下:

@Aspect
@Component
public class Logger {
    
    @Before("execution(public * com.linkedbear.spring.aop.b_aspectj.service.FinanceService.*(..))")
    public void beforePrint() {
        System.out.println("Logger beforePrint run ......");
    }
    
    @After("execution(* com.linkedbear.spring.aop.b_aspectj.service.*.*(String)))")
    public void afterPrint() {
        System.out.println("Logger afterPrint run ......");
    }
    
    @AfterReturning("execution(* com.linkedbear.spring.aop.b_aspectj.service.*.*(String)))")
    public void afterReturningPrint() {
        System.out.println("Logger afterReturningPrint run ......");
    }
    
    @AfterThrowing("execution(* com.linkedbear.spring.aop.b_aspectj.service.*.*(String)))")
    public void afterThrowingPrint() {
        System.out.println("Logger afterThrowingPrint run ......");
    }
}

2.2.3 编写配置类

配置类中,无需做任何多余的操作,只需要几个注解即可:

@Configuration
@ComponentScan("com.linkedbear.spring.aop.b_aspectj")
@EnableAspectJAutoProxy
public class AspectJAOPConfiguration {
    
}

注意这里用了一个新的注解:**@EnableAspectJAutoProxy** ,是不是突然产生了一点亲切感(模块装配 + 条件装配)!用它可以开启基于 AspectJ 的自动代理,简言之,就是开启注解 AOP 。

如果要使用 xml 配置文件开启注解 AOP ,则需要添加一个 <aop:aspectj-autoproxy/> 的标签声明(它等价于 @EnableAspectJAutoProxy 注解)。

2.2.4 测试运行

好啦,编写测试启动类的步骤已经很简单了吧,咱就不啰嗦了,直接上代码:

public class AnnotationAspectJApplication {
    
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AspectJAOPConfiguration.class);
        FinanceService financeService = ctx.getBean(FinanceService.class);
        financeService.addMoney(123.45);
        financeService.subtractMoney(543.21);
        financeService.getMoneyById("abc");
    }
}

运行 main 方法,控制台打印出了 Logger 的前置 、后置 、返回通知:

Logger beforePrint run ......
FinanceService 收钱 === 123.45
Logger beforePrint run ......
FinanceService 付钱 === 543.21
Logger beforePrint run ......
FinanceService 查询账户,id为abc
Logger afterReturningPrint run ......
Logger afterPrint run ......

2.3 环绕通知的编写

除了前面提到的 4 种基本的通知类型之外,还有环绕通知没有说。环绕通知的编写其实在第 40 章回顾动态代理的时候就已经写过了,对,InvocationHandler 和 MethodInterceptor 的编写本身就是环绕通知的体现。换做使用 AspectJ 的写法,又要如何来编写呢?咱也要来学习一下。

2.3.1 添加新的环绕通知方法

在 Logger 类中,咱添加一个 aroundPrint 方法:(切入的方法就不覆盖那么多了,一个就好)

@Around("execution(public * com.linkedbear.spring.aop.b_aspectj.service.FinanceService.addMoney(..))")
public void aroundPrint() {
    
}

然后咱回想一下,InvocationHandler 的结构是什么来着?得有入参,里面有对应的方法、参数,还得有返回值 Object 。。。可是这里啥也没有呀,这咱怎么写呢?

所以我们要先学习一个通知方法中的特殊参数:ProceedingJoinPoint 。

在 aroundPrint 方法的参数中添加 ProceedingJoinPoint ,并把方法的返回值类型改为 Object :

@Around("execution(public * com.linkedbear.spring.aop.b_aspectj.service.FinanceService.addMoney(..))")
public Object aroundPrint(ProceedingJoinPoint joinPoint) {
    
}

然后来看,ProceedingJoinPoint 有一个 proceed 方法,执行了它,就相当于之前咱在动态代理中写的 method.invoke(target, args); 方法了:

@Around("execution(public * com.linkedbear.spring.aop.b_aspectj.service.FinanceService.addMoney(..))")
public Object aroundPrint(ProceedingJoinPoint joinPoint) throws Throwable {
    return joinPoint.proceed(); // 此处会抛出Throwable异常
}

之后剩下的部分,咱就很熟悉了,快速的来编写一下吧:

@Around("execution(public * com.linkedbear.spring.aop.b_aspectj.service.FinanceService.addMoney(..))")
public Object aroundPrint(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Logger aroundPrint before run ......");
    try {
        Object retVal = joinPoint.proceed();
        System.out.println("Logger aroundPrint afterReturning run ......");
        return retVal;
    } catch (Throwable e) {
        System.out.println("Logger aroundPrint afterThrowing run ......");
        throw e;
    } finally {
        System.out.println("Logger aroundPrint after run ......");
    }
}

仔细观看小册的这种写法,是不是刚刚好就是上面 4 种通知的结合呀!

2.3.2 测试运行

直接重新运行 main 方法,可以发现在控制台有同时打印环绕通知和前置通知:

Logger aroundPrint before run ......
Logger beforePrint run ......
FinanceService 收钱 === 123.45
Logger aroundPrint afterReturning run ......
Logger aroundPrint after run ......

由此也得出了一个小小的结论:同一个切面类中,环绕通知的执行时机比单个通知要早。

2.4 切入点表达式的更多使用方法

上一章咱只是在切入点表达式的学习中接触了一些比较简单的写法和用法,这一章咱继续学习更多的使用方法。

2.4.1 抽取通用切入点表达式

注意上面咱在 Logger 类中标注的切入点表达式:

@After("execution(* com.linkedbear.spring.aop.b_aspectj.service.*.*(String)))")
public void afterPrint() {
    System.out.println("Logger afterPrint run ......");
}

@AfterReturning("execution(* com.linkedbear.spring.aop.b_aspectj.service.*.*(String)))")
public void afterReturningPrint() {
    System.out.println("Logger afterReturningPrint run ......");
}

这两个切入点表达式是一样的,如果这种同样的切入点表达式一多起来,回头修改起来那岂不是太费劲了?Spring 当然也为我们考虑到这一点了,所以它分别就 xml 和注解的方式提供了抽取通用表达式的方案。

2.4.1.1 AspectJ注解抽取

在注解 AOP 切面中,定义通用的切入点表达式只需要声明一个空方法,并标注 @Pointcut 注解即可:

@Pointcut("execution(* com.linkedbear.spring.aop.b_aspectj.service.*.*(String)))")
public void defaultPointcut() {

}

其它的通知要引用这个切入点表达式,只需要标注方法名即可,效果是一样的。

@After("defaultPointcut()")
public void afterPrint() {
    System.out.println("Logger afterPrint run ......");
}

@AfterReturning("defaultPointcut()")
public void afterReturningPrint() {
    System.out.println("Logger afterReturningPrint run ......");
}

2.4.1.2 xml抽取

相应的,在 xml 配置文件中,也有一个专门的标签来抽取,那就是 aop:pointcut :

<aop:config>
    <aop:aspect id="loggerAspect" ref="logger">
        <aop:pointcut id="defaultPointcut" 
                      expression="execution(public * com.linkedbear.spring.aop.a_xmlaspect.service.*.*(..))"/>
        <!-- ... -->
        <aop:after-returning method="afterReturningPrint"
                             pointcut-ref="defaultPointcut"/>
    </aop:aspect>
</aop:config>

注意,要引用 xml 的切入点表达式,需要使用 pointcut-ref 而不是 pointcut 属性!

2.4.2 @annotation的使用

除了 execution 之外,还有一种切入点表达式也比较常用:@annotation() 。

它的使用方式就非常简单了,只需要在括号中声明注解的全限定名即可。下面咱简单演示一下。

咱还是使用 Logger 作为切面类,这次咱声明一个 @Log 注解,用于标注要打印日志的方法:

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

然后,切入点表达式中只需要以下声明即可:

@annotation(com.linkedbear.spring.aop.b_aspectj.component.Log)

以此法声明的切入点表达式会搜索整个 IOC 容器中标注了**@Log**注解的所有 bean 全部增强 。

接下来咱就在 FinanceService 的 subtractMoney 方法中标注一个 @Log 注解:

@Log
public double subtractMoney(double money) {
    System.out.println("FinanceService 付钱 === " + money);
    return money;
}

重新运行 main 方法,发现只有 subtractMoney 方法有打印后置通知的日志了:

Logger beforePrint run ......
FinanceService 收钱 === 123.45
Logger beforePrint run ......
FinanceService 付钱 === 543.21
Logger afterPrint run ......      // 此处打印了
Logger beforePrint run ......
FinanceService 查询账户,id为abc
Logger afterReturningPrint run ......
上一页Spring AOP下一页Java FrameWorks

最后更新于2年前

这有帮助吗?

img
img
img
img