对AOP的理解
目录
- 一、为何需要AOP?
- 1、从实际需求出发
- 2、现有的技术能解决吗?
- 3、AOP可以解决
- 二、如何实现AOP?
- 1、基本使用
- 2、更推荐的做法
- 2.1 “基本使用”存在的隐患
- 2.2 最佳实践
- 2.2.1 参考@Transactional(通过AOP实现事务管理)
- 2.2.2 自定义注解
- 3、后言
一、为何需要AOP?
1、从实际需求出发
public class Book {......
}public interface IBookService {int insertBook(Book book);int deleteBookById(Long id);int updateBook(Book book);Book selectBookById(Long id);
}public class BookServiceImpl implements IBookService {@Overridepublic int insertBook(Book book) {// 入参检查// 日志记录// 事务处理// 业务逻辑return 0;}@Overridepublic int deleteBookById(Long id) {// 入参检查// 日志记录// 事务处理// 业务逻辑return 0;}@Overridepublic int updateBook(Book book) {// 入参检查// 日志记录// 事务处理// 业务逻辑return 0;}@Overridepublic Book selectBookById(Long id) {// 入参检查// 日志记录// 事务处理// 业务逻辑return null;}
}
- 上述代码的问题:
- (1)业务逻辑代码和非业务逻辑代码耦合
- (2)非业务逻辑的代码重复度高,却没有复用
2、现有的技术能解决吗?
- 方案1:模板模式
public abstract class BookServiceTemplate {public <T, E> T execute(E e) {// 入参检查// 日志记录// 事务处理return doProcess(e);}protected abstract <T, E> T doProcess(E e);
}public class BookServiceImpl implements IBookService {@Overridepublic int insertBook(Book book) {return new BookServiceTemplate() {@Overrideprotected <T, E> T doProcess(E e) {return null;}}.doProcess(book);}...
}
- 在需求初期,BookServiceImpl的4个方法的入参检查、日志记录、事务处理都比较一致时,上面的写法还行得通。
- 可一旦其中一个方法(如insertBook)需要改变入参检查、日志记录、事务处理时,事情就变得麻烦了。改模板吧,影响了其他方法(如updateBook)。去掉insertBook方法的模板吧,随着需求的发展,模板模式彻底腐化或被抛弃了。
- 方案2:代理模式
public class BookServiceProxyImpl implements IBookService {private final IBookService bookService;public BookServiceProxyImpl(IBookService bookService) {this.bookService = bookService;}@Overridepublic int insertBook(Book book) {// 入参检查boolean pass = checkParams(book);// 日志记录// 事务处理// 业务逻辑return bookService.insertBook(book);}...// 入参检查private boolean checkParams(Book book) {...}
}
- 挺不错的,其中一个方法(如insertBook)需要改变入参检查、日志记录、事务处理时,改变这个方法即可,对其他方法没影响。
- 但这个代理类不太“干净”,又能看到业务代码逻辑的入口(如insertBook),又能看到非业务逻辑代码(如checkParams)。
3、AOP可以解决
- 将业务逻辑代码和非业务逻辑代码解耦
- 业务逻辑代码在对象A,非业务逻辑代码在对象B
- Spring管理了对象A和对象B,在逻辑执行中,交织对象A的业务逻辑和对象B的非业务逻辑
- 这是我对AOP的简单理解。(AOP:Aspect Oriented Programming,即面向切面编程)
- 面向对象编程(OOP):通过将系统的大功能点拆分为一个一个小功能点,分别交给不同的类/对象负责(封装),并利用类的继承、多态构建系统的结构,让类相互配合,从而让系统运作起来。
- AOP本质还是OOP,是对OOP的补充。

二、如何实现AOP?
1、基本使用
- 示例【来源】:
public class User {
}public interface IUserService {int insertUser(User user);int deleteUserById(Long id);
}@Service
public class UserServiceImpl implements IUserService {@Overridepublic int insertUser(User user) {return 0;}@Overridepublic int deleteUserById(Long id) {return 0;}
}
public class Mail {
}public interface IMailService {int insert(Mail mail);
}@Service
public class MailServiceImpl implements IMailService {@Overridepublic int insert(Mail mail) {return 0;}
}
@Aspect
@Component
public class LoggingAspect {/*** com.forrest.learnspring.aop.example2.user.service.impl.UserServiceImpl类的所有public xxx 方法(yyy)在执行前都会被拦截,<br>* 并执行这个方法*/@Before("execution(public * com.forrest.learnspring.aop.example2.user.service.impl.UserServiceImpl.*(..))")public void doAccessCheck() {System.out.println("[Before] do access check...");}/*** com.forrest.learnspring.aop.example2.mail.service.impl.MailServiceImpl类的所有public xxx 方法(yyy)在执行前都会被拦截,<br>* 并执行这个方法*/@Around("execution(public * com.forrest.learnspring.aop.example2.mail.service.impl.MailServiceImpl.*(..))")public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {System.out.println("[Around] start " + pjp.getSignature());Object retVal = pjp.proceed();System.out.println("[Around] end " + pjp.getSignature());return retVal;}
}
- 如果不加:@EnableAspectJAutoProxy
@ComponentScan("com.forrest.learnspring.aop.example2")
public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();Arrays.stream(beanDefinitionNames).forEach(System.out::println);}
}/**
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
application
loggingAspect
mailServiceImpl
userServiceImpl
*/
- LoggingAspect因为被打上了@Component注解,因此也被加载成bean了。
- 但实际上,loggingAspect没有起任何作用(什么都没输出):
@ComponentScan("com.forrest.learnspring.aop.example2")
public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);IUserService userService = applicationContext.getBean(UserServiceImpl.class);userService.insertUser(new User());}
}
- 加上@EnableAspectJAutoProxy后:
@EnableAspectJAutoProxy
@ComponentScan("com.forrest.learnspring.aop.example2")
public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);IUserService userService = applicationContext.getBean(UserServiceImpl.class);userService.insertUser(new User());}
}
- 多了一个bean:org.springframework.aop.config.internalAutoProxyCreator
- 但报错:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.forrest.learnspring.aop.example2.user.service.impl.UserServiceImpl' available
- 这就诡异了,明明在Spring容器中看到了userServiceImpl,Spring咋说没有呢?!
- 很可能是因为:使用代理对象作为容器中的实际Bean
@EnableAspectJAutoProxy
@ComponentScan("com.forrest.learnspring.aop.example2")
public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);Object bean = applicationContext.getBean("userServiceImpl");System.out.println(bean instanceof Advised); // 带上@EnableAspectJAutoProxy注解,则为true;否则为fasle。}
}
- 这么改就对了:
@EnableAspectJAutoProxy
@ComponentScan("com.forrest.learnspring.aop.example2")
public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);IUserService userService = applicationContext.getBean(IUserService.class);userService.insertUser(new User());}
}
- 或者:
@EnableAspectJAutoProxy
@ComponentScan("com.forrest.learnspring.aop.example2")
public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);IUserService userService = (IUserService) applicationContext.getBean("userServiceImpl");userService.insertUser(new User());}
}
- 输出:
[Before] do access check...
2、更推荐的做法
2.1 “基本使用”存在的隐患
- 目标代码(如
userService.insertUser(new User());)无法感知到自己会被拦截。
2.2 最佳实践
2.2.1 参考@Transactional(通过AOP实现事务管理)
- 对于一个bean,我希望这个bean的insertUser方法开启事务,可以这么写:
@Service
public class UserServiceImpl implements IUserService {@Override@Transactionalpublic int insertUser(User user) {return 0;}......
}// 需要依赖:
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.2.25.RELEASE</version>
</dependency>
- 通过注解,可以“自主”告知Spring,代理“我”吧,我需要AOP。
2.2.2 自定义注解
跟着廖雪峰老师,实现:对计算方法执行的耗时。
public class User {
}public interface IUserService {User register(String email, String password, String name);
}@Service
public class UserServiceImpl implements IUserService {@Override@MetricTime("register")public User register(String email, String password, String name) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}return new User();}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MetricTime {String value();
}
@Aspect
@Component
public class MetricAspect {@Around("@annotation(metricTime)")public Object metric(ProceedingJoinPoint pjp, MetricTime metricTime) throws Throwable {long start = System.currentTimeMillis();try {return pjp.proceed();} finally {long end = System.currentTimeMillis();System.out.println("[Metric] [" + metricTime.value() + "] time cost: " + (end - start));}}
}
@annotation(xxx)中的xxx和MetricTime xxx要一一对应。
@EnableAspectJAutoProxy
@ComponentScan("com.forrest.learnspring.aop.example4.user")
public class Application {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);IUserService userService = applicationContext.getBean(IUserService.class);userService.register("forrest@gmail.com", "123456", "forrest");}
}/**
[Metric] [register] time cost: 1001
*/
3、后言
- 实际开发中,写AOP代码比较少,主要是业务代码本身难以完全拨离非业务代码。
- (1)例如,打日志。不可能只在方法的前后打日志,在方法执行中,也需要加一些日志。
- (2)例如,参数检查。在方法执行前做参数检查,只是一些基本的检查。另外,有些参数的校验本身就属于业务逻辑的一部分。抛开业务本身,是没法判断参数是否正确的。
- (3)例如,事务处理。可以用Spring提供的@Transactional注解。即使要自己定义注解通过AOP实现事务,在写方法体时也要格外注意,别带一些没必要参与事务的逻辑。
- 不过,真需要在执行某些方法时,做一些拦截处理,AOP还是不错的选择。
相关文章:
对AOP的理解
目录 一、为何需要AOP?1、从实际需求出发2、现有的技术能解决吗?3、AOP可以解决 二、如何实现AOP?1、基本使用2、更推荐的做法2.1 “基本使用”存在的隐患2.2 最佳实践2.2.1 参考Transactional(通过AOP实现事务管理)2.…...
C 指针数组
C 指针数组是一个数组,其中的每个元素都是指向某种数据类型的指针。 指针数组存储了一组指针,每个指针可以指向不同的数据对象。 指针数组通常用于处理多个数据对象,例如字符串数组或其他复杂数据结构的数组。 让我们来看一个实例…...
算法系列--动态规划--背包问题(1)--01背包详解
💕"趁着年轻,做一些比较cool的事情"💕 作者:Mylvzi 文章主要内容:算法系列–动态规划–背包问题(1)–01背包详解 大家好,今天为大家带来的是算法系列--动态规划--背包问题(1)--01背包详解 一.什么是背包问题 背包问题…...
【KB】通过Karabiner-Elements实现 optionTAB与 commandTAB 对调/映射 win 的 altTAB 习惯
学习Karabiner-Elements的第一个 demo,因为推荐的例子中过多参数,这是一个简化版。 需求:对调 optionTAB与 commandTAB,然后安装 altTAB 软件,恢复win切换任务的使用习惯。 {"description": "Change ta…...
nvm node包管理工具
下载地址:版本 1.1.9 CoreyButler/NVM-Windows (github.com) 使用nvm -v 检查安装是否成功。 常用命令 # 安装nodjs版本 nvm install 10.16.3nvm install 14.15.4# 切换,使用nodejs nvm use 10.16.3 ## nvm use 报错,1).使用管理员打开…...
程序员如何兼职赚小钱?
程序员由于有技术和手艺其实兼职赚钱的路子还是挺多的,只要你有足够的时间。 1. 做外包 这是比较传统的方式,甲方在一些众包平台上发布开发任务,你可以抢这个任务,但是价格都比较便宜。 任务比较多的平台: 猪八戒、一品威客、开…...
奥比中光深度相机(一):环境配置
文章目录 奥比中光深度相机(一):环境配置简介电脑环境SDK配置步骤安装环境依赖填写路径,点击Configure选择Visual studio点击Generate完成基于Python的SDK配置方法一:使用Cmake直接打开方法二:通过源文件打…...
API网关-Apisix路由配置教程(数据编辑器方式)
文章目录 前言一、端口修改1. apisix 端口修改2. dashboard 端口修改3. 登录密码修改 二、常用插件介绍1. 常用转换插件1.1 proxy-rewrite插件1.1.1 属性字段1.1.2 配置示例 2. 常用认证插件2.1 key-auth插件2.1.1 消费者端字段2.1.2 路由端字段2.1.3 配置示例 2.2 basic-auth插…...
Transformer的前世今生 day10(Transformer编码器
前情提要 ResNet(残差网络) 由于我们加更多层,更复杂的模型并不总会改进精度,可能会让模型与真实值越来越远,如下: 我们想要实现,加上一个层把并不会让模型变复杂,即没有它也没关系…...
【c++模板】泛型编程(你真的懂模版特化、分离编译和非类型参数吗)
🪐🪐🪐欢迎来到程序员餐厅💫💫💫 今日主菜:模板 主厨:邪王真眼 主厨的主页:Chef‘s blog 所属专栏:c大冒险 总有光环在陨落,总有新星在…...
力扣1----10(更新)
1. 两数之和 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 你可以按…...
[Qt] QString::fromLocal8Bit 的使用误区
QString::fromLocal8Bit 是一个平台相关的函数。默认情况下在 Windows 下 就是 gbk 转 utf-8 ,在 Linux就应该是无事发生。因为Linux平台默认的编码方式就是 utf-8 可以通过 void QTextCodec::setCodecForLocale(QTextCodec *c)来修改 Qt默认的编码方式。如下 第一输出乱码的…...
什么是RabbitMQ的死信队列
RabbitMQ的死信队列(Dead Letter Queue,简称DLQ)是一种用于处理消息失败或无法路由的消息的机制。它允许将无法被正常消费的消息重新路由到另一个队列,以便稍后进行进一步处理、分析或排查问题。 当消息对立里面的消息出现以下几…...
力扣面试150 删除有序数组中的重复项 双指针
Problem: 26. 删除有序数组中的重复项 思路 👩🏫 三叶题解 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( 1 ) O(1) O(1) Code class Solution {public int removeDuplicates(int[] nums) {int j 0, n nums.length;for(int i 0;…...
政安晨:【深度学习实践】【使用 TensorFlow 和 Keras 为结构化数据构建和训练神经网络】(二)—— 深度神经网络
政安晨的个人主页:政安晨 欢迎 👍点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras实战演绎 希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正! 概述 深度神经网络(Deep Neural Network…...
【链表】Leetcode 138. 随机链表的复制【中等】
随机链表的复制 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点…...
【计算机网络教程】(第六版)第2章课后习题答案
第二章 2-012-022-032-042-062-072-082-092-102-112-122-132-142-152-16 2-01 物理层要解决哪些问题?物理层的主要特点是什么? 答: 物理层要解决的主要问题: (1)物理层要尽可能地屏蔽掉物理设备和传输媒体&…...
抖音电商“达人客服”产品上线啦!超多作者邀你一起“321上客服”!
有问题别自己克服,来抖音电商找“达人客服” 当代年轻人购物,正在从机智省变成理智购。越来越多的人在达人直播间购物,看重的不止是优惠力度,还有服务保障。 为了帮助达人更好地服务用户,抖音电商上线了「达人客服」…...
华为防火墙二层墙(VAN/SVI/单臂路由)
二层墙只能做地址池形式的NAT。 交换机安全策略防火墙二层墙 路由器安全策略防火墙三层墙 交换机的光口是不能直接插线的,光模块,包括进和出 长距离:单模 短距离:多模 防火墙自身的ping流量需要单独配置...
idea使用git笔记
1.创建分支和切换分支 创建分支 切换分支 2.把新创建的分支提交到远程服务器上(注:如果没有提交的,随便找个文件修改再提交) (1)切换到要提交的分支,add (2)commit (3)push 3.在自己分支修改代码及提交到自己的远…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关
在水泥厂的生产流程中,工业自动化网关起着至关重要的作用,尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关,为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多,其中不少设备采用Devicenet协议。Devicen…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...
TCP/IP 网络编程 | 服务端 客户端的封装
设计模式 文章目录 设计模式一、socket.h 接口(interface)二、socket.cpp 实现(implementation)三、server.cpp 使用封装(main 函数)四、client.cpp 使用封装(main 函数)五、退出方法…...
起重机起升机构的安全装置有哪些?
起重机起升机构的安全装置是保障吊装作业安全的关键部件,主要用于防止超载、失控、断绳等危险情况。以下是常见的安全装置及其功能和原理: 一、超载保护装置(核心安全装置) 1. 起重量限制器 功能:实时监测起升载荷&a…...
