对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还是不错的选择。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot国际化配置流程(超详细)

前言 最新第一次在做SpringBoot的国际化&#xff0c;网上搜了很多相关的资料&#xff0c;都是一些简单的使用例子&#xff0c;达不到在实际项目中使用的要求&#xff0c;因此本次将结合查询的资料以及自己的实践整理出SpringBoot配置国际化的流程。 SpringBoot国际化 "i…

用系统观念打造智慧公厕,引领智慧城市的发展

智慧公厕&#xff0c;作为智慧城市建设的一部分&#xff0c;具有重要意义。在高度发达的科技条件下&#xff0c;如何打造高质量的智慧公厕是一个值得思考的问题。本文将以智慧公厕源头实力厂家广州中期科技有限公司&#xff0c;大量精品案例项目现场实景实图实例&#xff0c;探…

Rust控制台输出跑马灯效果,实现刷新不换行,实现loading效果

要在 Rust 中实现控制台刷新而不换行&#xff0c;以实现类似 "loading" 状态的效果&#xff0c;你可以使用 \r&#xff08;回车符&#xff09;来覆盖上一行的内容。 use std::io::{self, Write}; use std::thread; use std::time::Duration;fn main() {let loading_…

浅模仿小米商城布局(有微调)

CSS文件 *{margin: 0;padding: 0;box-sizing: border-box; }div[class^"h"]{height: 40px; } div[class^"s"]{height: 100px; } .h1{width: 1528px;background-color: green; } .h11{background-color:rgb(8, 220, 8); } .h111{width: 683px;background-c…

动态内存操作函数使用过程中会遇见的问题

越界访问 首先我们上一个代码&#xff0c;看看这个的代码的问题 这个代码的问题显而易见 &#xff0c;就是在循环里面&#xff0c;产生了越界访问的问题&#xff0c;这里你开辟了10个整形空间&#xff0c;但是从0-10一共是11个整形空间。导致访问不合法的空间&#xff0c;从而…

卷积神经网络-卷积层

卷积神经网络-卷积层 1多层感知机&#xff08;MLP&#xff09;2卷积神经网络&#xff08;CNN&#xff09;3MLP和CNN关系与区别4仍然有人使用MLP的原因&#xff1a;5MLP的局限性&#xff1a;MLP的应用领域&#xff1a;总结&#xff1a;6全连接到卷积全连接层 vs 卷积层结构差异应…

一文教你学会用群晖NAS配置WebDAV服务结合内网穿透实现公网同步Zotero文献库

文章目录 前言1. Docker 部署 Trfɪk2. 本地访问traefik测试3. Linux 安装cpolar4. 配置Traefik公网访问地址5. 公网远程访问Traefik6. 固定Traefik公网地址 前言 Trfɪk 是一个云原生的新型的 HTTP 反向代理、负载均衡软件&#xff0c;能轻易的部署微服务。它支持多种后端 (D…

LLLM并发加速部署方案(llama.cpp、vllm、lightLLM、fastLLM)

大模型并发加速部署 解析当前应用较广的几种并发加速部署方案&#xff01; llama.cpp vllm lightLLM fastLLM

使用yolov9来实现人体姿态识别估计(定位图像或视频中人体的关键部位)教程+代码

yolov9人体姿态识别&#xff1a; 相较于之前的YOLO版本&#xff0c;YOLOv9可能会进一步提升处理速度和精度&#xff0c;特别是在姿态估计场景中&#xff0c;通过改进网络结构、利用更高效的特征提取器以及优化损失函数等手段来提升对复杂人体姿态变化的捕捉能力。由于YOLOv9的…

Java SPI 机制

SPI 机制的定义 在Java中&#xff0c;SPI&#xff08;Service Provider Interface&#xff09;机制是一种用于实现软件组件之间松耦合的方式。它允许在应用程序中定义服务接口&#xff0c;并通过在类路径中发现和加载提供该服务的实现来扩展应用程序功能。 SPI 机制通常涉及三…

ubuntu 中安装docker

1 资源地址 进入ubuntu官网下载Ubuntu23.04的版本的镜像 2 安装ubuntu 这里选择再Vmware上安装Ubuntu23.04.6 创建一个虚拟机&#xff0c;下一步下一步 注意虚拟机配置网络桥接&#xff0c;CD/DVD选择本地的镜像地址 开启此虚拟机&#xff0c;下一步下一步等待镜像安装。 3…

Idea2023.3.6版本无法启动设置界面-settings界面打不开无反应---IntelliJ Idea工作笔记013

先说一下网上有,把某个文件删除的 有说是因为汉化问题的 可以看到,其实都不是,这样弄就好了,很简单 Please report thisjava.lang.ClassCastException: class [Lcom.intellij.execution.filters.CompositeInputFilter$InputFilterWrapper; cannot be cast to class java.uti…

Java多线程实战-从零手搓一个简易线程池(二)线程池与拒绝策略实现

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️本系列源码仓库&#xff1a;多线程并发编程学习的多个代码片段(github) &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正…

文件操作(下)(想要了解如何操作文件,那么看这一片就足够了!)

前言&#xff1a;在文件操作&#xff08;上&#xff09;中&#xff0c;我们讲到了基础的文件操作&#xff0c;包括文件的打开&#xff0c;文件的关闭&#xff0c;以及文件的基础读写&#xff0c;那么除了之前学习的读写之外&#xff0c;还有什么其他的方式对文件进行读写操作吗…

Python提示‘ModuleNotFoundError: No module named ‘numpy.core._multiarray_umath‘

一、问题背景 在学习Python编程使用matplotlib时&#xff0c;总是提示: ModuleNotFoundError: No module named numpy.core._multiarray_umath 问题大致描述如下&#xff1a; D:\WorkSpace\PythonWorkSpace\Python编程-从入门到实践\venv\Scripts\python.exe D:\WorkSpace\Pyt…

Jenkins用户角色权限管理

Jenkins作为一款强大的自动化构建与持续集成工具&#xff0c;用户角色权限管理是其功能体系中不可或缺的一环。有效的权限管理能确保项目的安全稳定&#xff0c;避免敏感信息泄露。 1、安装插件&#xff1a;Role-based Authorization Strategy 系统管理 > 插件管理 > 可…

ES面试题

1、如何同步索引库 同步调用 在完成数据库操作后&#xff0c;直接调用搜索服务提供的接口 异步通知 在完成数据库操作后&#xff0c;发送MQ消息 搜索服务监听MQ&#xff0c;接收到消息后完成数据修改 监听binlog 2、分词器 ik分词器 ik_smart ik_max_word 自定义分词器 以拼…

安静:内向性格的竞争力 - 三余书屋 3ysw.net

精读文稿 这期我们介绍的这本书叫做《安静》&#xff0c;副标题是《内向性格的竞争力》。本书共有267页&#xff0c;我会用大约25分钟的时间为你讲述书中的精髓。内向性格具备什么样的竞争力&#xff1f;内向性格的人在人际交往和日常生活中似乎总是吃亏&#xff0c;因为他们不…

Postman传对象失败解决

文章目录 情景复现解决方案总结 情景复现 postman中调用 debug发现pId传入失败 分析解释&#xff1a; 实体类中存在pId、uid和num字段 controller层将GoodsCar作为请求体传入 解决方案 当时觉得很奇怪&#xff0c;因为uid和num可以被接收&#xff0c;而pId和num的数据类型相…

安卓Activity上滑关闭效果实现

最近在做一个屏保功能&#xff0c;需要支持如图的上滑关闭功能。 因为屏保是可以左右滑动切换的&#xff0c;内部是一个viewpager 做这个效果的时候&#xff0c;关键就是要注意外层拦截触摸事件时&#xff0c;需要有条件的拦截&#xff0c;不能影响到内部viewpager的滑动处理…