对AOP的理解

news2024/10/2 6:39:57

目录

  • 一、为何需要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 {

    @Override
    public int insertBook(Book book) {
        // 入参检查

        // 日志记录

        // 事务处理

        // 业务逻辑

        return 0;
    }

    @Override
    public int deleteBookById(Long id) {
        // 入参检查

        // 日志记录

        // 事务处理

        // 业务逻辑

        return 0;
    }

    @Override
    public int updateBook(Book book) {
        // 入参检查

        // 日志记录

        // 事务处理

        // 业务逻辑

        return 0;
    }

    @Override
    public 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 {

    @Override
    public int insertBook(Book book) {
        return new BookServiceTemplate() {
            @Override
            protected <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;
    }

    @Override
    public 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 {
    @Override
    public int insertUser(User user) {
        return 0;
    }

    @Override
    public int deleteUserById(Long id) {
        return 0;
    }
}
public class Mail {
}

public interface IMailService {
    int insert(Mail mail);
}

@Service
public class MailServiceImpl implements IMailService {
    @Override
    public 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
    @Transactional
    public 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.coloradmin.cn/o/1549613.html

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

相关文章

C++零基础入门学习视频课程

教程介绍 本专题主要讲解C基础入门学习&#xff0c;所以不会涉及很深入的语法和机制。但会让你整体多面的了解和学习C的核心内容&#xff0c;快速学习使用C&#xff0c;我们的目标是先宏观整体把握&#xff0c;在深入各个击破&#xff01; 学习地址 链接&#xff1a;https:/…

力扣Lc22--- 459. 重复的子字符串(java版)-2024年3月27日

1.题目描述 2.知识点 &#xff08;1&#xff09; 在Java中&#xff0c;.repeat(i) 是一个字符串方法&#xff0c;用于将原始字符串重复 i 次。 例如&#xff0c;对于字符串 “ab”&#xff0c;使用 .repeat(3) 将会返回 “ababab”。 public class RepeatExample {public s…

速看!MC-CCPIT第二十二届中国国际冶金工业展览会

METALLURGY CHINA 2024 MC-CCPIT第二十二届中国国际冶金工业展览会 ——冶金装备品牌展示区 主办单位&#xff1a;中国钢铁工业协会 中国国际贸易促进委员会冶金行业分会 承办单位&#xff1a;冶金工业国际交流合作中心 地 点&#xff1a;上海新国际博览中心 时 间&am…

高阶SQL语句(二)

一 子查询 也被称作内查询或者嵌套查询&#xff0c;是指在一个查询语句里面还嵌套着另一个查询语 句。子查询语句 是先于主查询语句被执行的&#xff0c;其结果作为外层的条件返回给主查询进行下一 步的查询过滤。 ①子语句可以与主语句所查询的表相同&#xff0c;也可以是不…

2024年目前阿里云服务器一个月收费价格表多少钱?

阿里云服务器一个月多少钱&#xff1f;最便宜5元1个月。阿里云轻量应用服务器2核2G3M配置61元一年&#xff0c;折合5元一个月&#xff0c;2核4G服务器30元3个月&#xff0c;2核2G3M带宽服务器99元12个月&#xff0c;轻量应用服务器2核4G4M带宽165元12个月&#xff0c;4核16G服务…

一文彻底搞懂MySQL中事务的五种分类

文章目录 1. 什么是事务2. 事务的分类3. 事务的详解 1. 什么是事务 事务是指作为单个逻辑工作单元执行的一系列操作&#xff0c;这些操作要么全部成功完成&#xff0c;要么全部失败回滚&#xff0c;从而保证数据库操作一致性和完整性的重要机制&#xff0c;它确保了数据库在并…

程序员提效 x10 的必备开源“神器”

工欲善其事&#xff0c;必先利其器。我们每个人的电脑中都会有一些爱不释手的工具软件。 转Linux 桌面2年了&#xff0c;期间尝试过各种各样“神奇”的开源工具&#xff0c;作为一个开源软件爱好者&#xff0c;这里给大家推荐几个这些年工作、学习、生活中常用、跨平台、免费的…

【MySQL】简述SQLの通用语法及4种基本语句介绍(DDL/DML/DQL/DCL)

前言 大家好吖&#xff0c;欢迎来到 YY 滴MySQL系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

CenOS安装yum(超详细)

专栏文章索引&#xff1a;Linux 目录 1.检查yum源是否安装 2.卸载yum源 3.去网站下载yum源&#xff0c;至少需要下载3个 4.安装&#xff08;不要出现其他后缀名为rpm的文件&#xff09; 1.检查yum源是否安装 rpm -qa|grep yum 2.卸载yum源 查看一下是否成功删除 3.去网站下…

esp32CAM环境搭建(arduino+MicroPython+thonny+固件)

arduino ide 开发工具 arduino版本&#xff1a;1.8.19 arduino ide 中文设置&#xff1a;​ file >> preferences >> ​ arduino IDE 获取 ESP32 开发环境&#xff1a;打开 Arduino IDE &#xff0c;找到 文件>首选项 ,将 ESP32 的配置链接填入附加开发板管理网…

【Linux】进程地址空间详解

前言 在我们学习C语言或者C时肯定都听过老师讲过地址的概念而且老师肯定还会讲栈区、堆区等区域的概念&#xff0c;那么这个地址是指的物理内存地址吗&#xff1f;这里这些区域又是如何划分的呢&#xff1f; 我们在使用C语言的malloc或者C的new函数开辟空间时&#xff0c;开辟…

基于 Linux 的更新版 MaxPatrol VM 可扫描 Windows

&#x1f47e; MaxPatrol VM 2.1 是俄罗斯唯一一款可以安装在 Linux 上并以审计和五重测试模式扫描 Windows 主机&#xff08;甚至是旧版本&#xff09;的漏洞管理产品。 让我们告诉你更新后的 MaxPatrol VM 还有哪些有用的功能&#xff1a; 1. 由于采用了新的数据存储模式&a…

【Canvas与艺术】模拟八一电影制片厂电影片头效果

【缘起】 八一厂每部电影前都有其专有开头&#xff0c;如&#xff1a;https://www.ixigua.com/6799821997258834440?logTag2eacce76401e13f9efe7 这个片头可以用canvas模拟下来。 【关键点】 线型放射状粒子系统的运作。 立体感五角星的绘制。 【图例】 【代码】 <!D…

如何模拟在丢包情况下的传输测试(以镭速为例)

在现代社会&#xff0c;网络通信的可靠性和效率是数据传输的关键因素。网络通信中的丢包问题&#xff0c;作为一种普遍存在的现象&#xff0c;可能对数据传输的完整性和效率产生重大影响。本文的目的是探讨在存在丢包的网络环境中&#xff0c;如何通过模拟测试来评估和改进一款…

【Linux】详解进程终止进程等待

一、页表&&写时拷贝的进一步理解 页表中不仅仅只有虚拟地址到物理地址的映射&#xff0c;还包括了很多选项&#xff0c;其中就包括了映射条目的权限。当我们进程的代码和数据加载到内存并和进程地址空间建立映射关系时&#xff0c;如果数据的内容不允许被修改&#xff…

vue 预览excel文件的又一伟大实践 —— vue-office

实际上&#xff0c;预览excel这个功能&#xff0c;我之前已经写过一个文章了。如下&#xff0c;使用的是 luckysheet/luckyExcel 实现的。 vue 实现在线预览Excel-LuckyExcel/LuckySheet实现方案_excel在线预览的方案-CSDN博客 但是最近客户使用发现一个bug&#xff0c;就是某…

vulnhub Kioptrix Level 1通关

目录 环境安装 信息收集 0X01靶机IP搜寻 1.1ifconfig查看本机IP域 1.2.nmap搜寻IP 2.直接arp-scan指令查看IP 0X02端口扫描 0X03漏洞利用 反弹shell 另一种通关方法推荐 环境安装 靶机下载&#xff1a;https://download.vulnhub.com/kioptrix/Kioptrix_Level_1.rar …

10个你必须知道的浏览器指纹检测工具,保护你的隐私安全

在当前的数字时代&#xff0c;个人隐私保护变得越来越重要&#xff0c;特别是对于互联网用户来说。有一种叫做“浏览器指纹”的技术&#xff0c;它能悄悄收集我们使用的浏览器和设备的各种细节信息。这本是为提供个性化服务&#xff0c;但对那些需要在不同平台同时管理多个账号…

应对Locked勒索病毒威胁:你的数据安全准备好了吗?

导言&#xff1a; .Locked勒索病毒&#xff0c;作为一种新型的恶意软件&#xff0c;已经在全球范围内引起了广泛的关注。这种病毒通过加密受害者的文件&#xff0c;并要求支付赎金以获取解密密钥&#xff0c;从而实现对受害者的勒索。本文旨在深入解析.Locked勒索病毒的特点、…

AXI Memory Mapped to PCI Express学习笔记(一)——PCIe事务

1 PCIe事务 AXI事务对于PCIe来说&#xff0c;主要涉及到在AXI总线和PCIe总线之间进行数据交换和通信的过程。在PCIe系统中&#xff0c;AXI总线作为一个连接不同组件的桥梁&#xff0c;可以实现高效的数据传输和事务处理。 AXI事务通常包括读事务和写事务。在读事务中&#xf…