对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.在自己分支修改代码及提交到自己的远…...
XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...
