Spring面向切面编程(AOP)

news2024/11/16 20:44:27

Spring面向切面编程(AOP)

概念

AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

什么是AOP

面向切面编程:利用AOP可以对业务逻辑的各个部分进行隔离,从而使业务逻辑的各部分之间耦合度降低,提高程序的可重用性,提高了开发效率,通俗的讲,可以实现不修改源代码的方式,在核心业务里面 添加新的功能。

AOP底层的原理就是动态代理 ,真正干活的 bean 是 代理 bean , 代理 bean 对真实 bean 功能增强。

AOP开发术语

  • 连接点(Joinpoint):连接点是程序类中客观存在的方法,可被Spring拦截并切入内容
    • 说白了,类中的哪些方法可以被增强,这些方法就称为是连接点
  • 切入点(Pointcut):被Spring切入连接点
    • 真正被增强的方法称为切入点
  • 通知、增强(Advice):可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知,最终通知等
    • 实际增强的逻辑部分,称为通知(增强)
  • 目标对象(Target):代理的目标对象,真实对象
  • 引介(Introduction):一种特殊的增强,可在运行期为类动态添加Field和Method
  • 织入(Weaving):把通知应用到具体的类,进而创建新的代理类的过程
  • 代理(Proxy):被AOP织入通知后,产生的结果类
  • 切面(Aspect):由切点和通知组成,将横切逻辑织入切面所指定的连接点中。 把通知 应用到 切入点的过程

作用

Spring的AOP编程即是通过动态代理类为原始类的方法添加辅助功能

开发流程

环境搭建

引入AOP相关依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.1.6.RELEASE</version>
</dependency>

spring-context.xml引入AOP命名空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       ">
</beans>

代码演示

定义原始类

public interface UserService {
    public void save();
}
public class UserServiceImpl implements UserService {
    public void save() {
        System.out.println("save method executed...");
    }
}
基于Schema-based实现AOP

定义通知类(添加额外功能)

//实现前置通知接口
public class MyAdvice implements MethodBeforeAdvice { 
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("before advice executed...");
    }
}

定义bean标签

<!--原始对象-->
<bean id="us" class="com.qf.aaron.aop.basic.UserServiceImpl" />

<!--辅助对象-->
<bean id="myAdvice" class="com.qf.aaron.aop.basic.MyAdvice" />

定义切入点(PointCut),形成切面(Aspect)

<aop:config>
    <!--切点-->
    <aop:pointcut id="myPointCut" expression="execution(* save())" />
    <!--组装切面 -->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut" />
</aop:config>
基于AspectJ 实现AOP

创建 通知类

/**
 * @ClassName : MyAspectJAdvice

 * @Description :   AspectJ 的 通知类  无需实现接口
 */
public class MyAspectJAdvice {

    public void before(){
        System.out.println("前置增强代码");
    }

    public Object around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕增强前面的代码");
        // 让目标方法继续执行
        Object result = jp.proceed();
        System.out.println("环绕增强后面的代码");
        return result;
    }


    public void after(){
        System.out.println("最终增强的代码,类似于finally,目标方法有没有异常都要执行的");
    }


    public void throwing(Exception e){
        System.out.println("异常抛出增强代码,只有在目标方法抛出异常时才能执行");
        System.out.println("异常信息是:" + e.getMessage());
    }

    public void afterReturning(Object result){
        System.out.println("后置增强,返回值是:"+result);
    }
}

xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 其他bean的定义... -->

    <!--了解 基于 AspectJ 的 xml 的  AOP实现 配置-->

    <!-- 配置增强类 -->
    <bean id="advice" class="com.qf.spring.aop.advice.MyAspectJAdvice" />

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="pc" expression="execution(* save())"/>
        <aop:aspect ref="advice">
            <!-- aop:before 前置增强的配置 -->
            <aop:before method="before" pointcut-ref="pc" />
            <!-- 环绕目标方法执行 -->
            <aop:around method="around" pointcut-ref="pc" />
            <!-- 异常抛出增强 -->
            <aop:after-throwing method="throwing" pointcut-ref="pc" throwing="e" />
            <!-- 最终增强 -->
            <aop:after method="after" pointcut-ref="pc" />
            <!-- 后置增强,目标方法抛出异常时 后置增强不执行 -->
            <aop:after-returning method="afterReturning" pointcut-ref="pc" returning="result"/>
        </aop:aspect>

    </aop:config>

</beans>

测试

	@Test
	void save() {
	   ApplicationContext context = new ClassPathXmlApplicationContext("/beans.xml");
	   UserService service = context.getBean(UserService.class);
	   service.save();
	}

AOP小结

  • 通过AOP提供的编码流程,更便利的定制切面,更方便的定制了动态代理
  • 进而彻底解决了辅助功能冗余的问题
  • 业务类中职责单一性得到更好保障
  • 辅助功能也有很好的复用性

通知类【可选】

定义通知类,达到通知效果

前置通知:MethodBeforeAdvice

后置通知:AfterAdvice

后置通知:AfterReturningAdvice //有异常不执行,方法会因异常而结束,无返回值

异常通知:ThrowsAdvice

环绕通知:MethodInterceptor

没有必要把通知的执行顺序记得非常精确,因为 spring 新版本 5 和 之前的旧版本 通知的执行顺序 不一样

通配切入点【可选】

根据表达式通配切入点

<!--匹配参数-->
<aop:pointcut id="myPointCut" expression="execution(* *(com.qf.aaron.aop.basic.User))" />
<!--匹配方法名(无参)-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--匹配方法名(任意参数)-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))" />
<!--匹配返回值类型-->
<aop:pointcut id="myPointCut" expression="execution(com.qf.aaron.aop.basic.User *(..))" />
<!--匹配类名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.UserServiceImpl.*(..))" />
<!--匹配包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop.basic.*.*(..))" />
<!--匹配包名、以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.qf.aaron.aop..*.*(..))" />

切入点表达式:expression 知道对哪个类的那个方法进行增强
语法结构: execution([权限修饰符] [返回值类型] [类全路径] [方法名称] ([参数列表])

JDK和CGLIB选择【可选】

  • spring底层,包含了jdk代理和cglib代理两种动态代理生成机制
  • 基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理
class DefaultAopProxyFactory{
    // 该方法中明确定义了 JDK代理和CGLib代理的选取规则
    // 基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理
    public AopProxy createAopProxy(){...}
}

后处理器【可选】

  • spring中定义了很多后处理器
  • 每个bean在创建完成之前 ,都会有一个后处理过程,即再加工,对bean做出相关改变和调整
  • spring-AOP中,就有一个专门的后处理器,负责通过原始业务组件(Service),再加工得到一个代理组件
    在这里插入图片描述

后处理器定义

/**
 * 定义bean后处理器
 * 作用:在bean的创建之后,进行再加工
 */
public class MyBeanPostProcessor implements BeanPostProcessor{

    /**
     * 在bean的init方法之前执行
     * @param bean  原始的bean对象
     * @param beanName
     * @return
     * @throws BeansException
     */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后处理器 在init之前执行```"+bean.getClass());
        return bean;
    }
	/**
     * 在bean的init方法之后执行
     * @param bean  postProcessBeforeInitialization返回的bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("后处理器 在init之后执行```"+bean.getClass());
        return bean;// 此处的返回是 getBean() 最终的返回值
    }
}

配置后处理器

<!-- 配置后处理器,将对工厂中所有的bean声明周期进行干预 -->
<bean class="com.qianfeng.beanpostprocessor.MyBeanPostProcessor"></bean>

bean的生命周期

bean 对象从创建到销毁的过程

  • bean 的实例化 通过构造方法创建bean 的实例 默认是无参构造
  • 给bean 的属性赋值
  • 执行初始化的方法
  • 得到完整的bean 对象 ,这时的bean 对象才能够使用
  • 销毁bean

要考虑 bean 的后置处理器 BeanPostProcessor
创建一个类实现BeanPostProcessor 重写 他的两个方法

public class MyBeanPost implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在bean 初始化之前执行");

        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        System.out.println("在bean初始化之后执行");
        if(bean instanceof User){
            User user = (User) bean;
            user.setName("愿天下程序员少走弯路!");
            return user;
        }

        return bean;
    }
}

在配置文件 配置这个 bean

 <bean id="myPostProcessor" class="com.qf.postprocessor.MyBeanPost"></bean>

总结

  • bean 的实例化 通过构造方法创建bean 的实例 默认是无参构造

  • 给bean 的属性赋值

  • 把 bean 的 实例 传递给 bean的前置处理器的方法 postProcessBeforeInitialization

  • 执行初始化的方法

  • 把 bean 的 实例 传递给 bean的后置处理器的方法 postProcessAfterInitialization

  • 得到完整的bean 对象 ,这时的bean 对象才能够使用

  • 销毁bean 当容器关闭的时候 调用销毁的方法

    • 自定义初始化方法:添加“init-method”属性,Spring则会在创建对象之后,调用此方法。
    • 自定义销毁方法:添加“destroy-method”属性,Spring则会在销毁对象之前,调用此方法。
    • 销毁:工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。
    • 分类:Singleton对象由Spring容器销毁、Prototype对象由JVM销毁。

生命周期注解(初始化注解、销毁注解)

@PostConstruct //初始化 
public void init(){
    System.out.println("init method executed");
}

@PreDestroy //销毁
public void destroy(){
    System.out.println("destroy method executed");
}

生命周期阶段
随工厂启动创建 ==》 构造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 构建完成 ==》随工厂关闭销毁(单例bean:singleton)
被使用时创建 ==》 构造方法 ==》 set方法(注入值) ==》 init(初始化) ==》 构建完成 ==》JVM垃圾回收销毁(多例bean:prototype)

流程图
在这里插入图片描述

在这里插入图片描述
完结撒花!愿每一位程序员少走弯路是我的创作的初心!

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

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

相关文章

DICOM笔记-CT图像的边界

常见CT图像在有效范围内都是有效CT值。 对CT值的处理也就仅限于做斜率和截距的线性处理&#xff1b; 可参加常用的DICOM标签信息&#xff1a; DICOM笔记-DICOM常用Tag标签汇总_dicom tag列表_黑山老妖的博客的博客-CSDN博客文件引言MetaInfoGroupElementTag Description中文解…

尚无忧货运物流app系统享集运转运uniapp系统

物流货运app系统 找货源 找车源 查找货源 开通会员 开创性的物流货运管理云系统&#xff0c;将货运环节中的制造商、承运商、司机和收货方链接在同一平台&#xff0c;轻松管理运输。 <template> <diy ref"diy" v-if"isDiy"></diy&…

vue——antd+elementUi——table表格实现滚动加载(分页滚动加载)——技能提升

今天遇到一个需求&#xff0c;就是要实现表格的滚动加载。 通常我们经常实现的效果是&#xff1a;下图中带分页的表格 如果要实现滚动分页加载的话&#xff0c;则需要保证的一点就是数据量不能过大&#xff0c;过多的数据量会导致页面的卡顿。 下面来介绍滚动分页加载的实现…

jmeter的使用

一、jmeter介绍和下载 1.1 jmeter介绍 Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试&#xff0c;它最初被设计用于Web应用测试&#xff0c;但后来扩展到其他测试领域。 JMeter 可以用于对服务器、网络或对象模拟巨大的负载&#xff0c;来自不…

【Python】FastAPI 配置日志即 logging 模块使用

目录 1. 日志 2. FastAPI 示例 1. 日志 日志是对软件执行时所发生事件的一种追踪方式。软件开发人员对他们的代码添加日志调用&#xff0c;借此来指示某事件的发生。一个事件通过一些包含变量数据的描述信息来描述&#xff08;比如&#xff1a;每个事件发生时的数据都是不同的…

Python之并发编程多线程理论

一、什么是线程 在传统操作系统中&#xff0c;每个进程有一个地址空间&#xff0c;而且默认就有一个控制线程 线程顾名思义&#xff0c;就是一条流水线工作的过程&#xff0c;一条流水线必须属于一个车间&#xff0c;一个车间的工作过程是一个进程 车间负责把资源整合到一起…

项目开发中异常处理需要注意的问题(详细!!)

文章目录 1、各层在对异常处理时需要注意的问题2、业务代码层面对于异常的处理姿势3、错误的异常处理方式&#xff1a;1、丢弃异常2、丢失异常的原始信息3、抛出异常时不指定任何信息 4、线程池处理异常方法 1、各层在对异常处理时需要注意的问题 这是日常开发中请求的处理过程…

springcloud-alibaba (05)Seata实现分布式事务-个人笔记

前言 本文将介绍如何使用Seata实现分布式事务。将覆盖以下主题&#xff1a; seata下载与安装如何配置和启动Seata服务器如何编写应用程序以使用Seata如何解决常见问题 本文只是我个人seata学习笔记&#xff0c;不是什么学习教程 如果你是一名Java开发人员&#xff0c;那么你…

官方喊你来免费下载 Navicat Premium 16.2 Beta 中文版 | Redis 体验官火热招募中

今天&#xff0c;我们发布了 Navicat 16.2 Beta 中文版&#xff0c;它适用于 Windows、macOS 和 Linux 平台。届时&#xff0c;我们诚邀广大 Redis 用户及爱好者亲测 Beta 版&#xff0c;希望 Redis 新功能将为 Redis 相关工作者&#xff08;应用开发人员、DBA 和数据分析师等&…

【paddlecls】多机多卡-linux(二:环境搭建)

构建并进入 docker 容器后&#xff0c;我们进入下一步&#xff1a; 1. 退出/进入 docker 容器&#xff1a; 在进入 Docker 容器后&#xff0c;可使用组合键 Ctrl P Q 退出当前容器&#xff0c;同时不关闭该容器&#xff1b; 如需再次进入容器&#xff0c;可使用下述命令&am…

微信小程序项目实例——生活记账本

今日推荐&#x1f481;‍♂️ 2023五月天演唱会&#x1f3a4;&#x1f3a4;&#x1f3a4;大家一起冲冲冲&#x1f3c3;‍♂️&#x1f3c3;‍♂️&#x1f3c3;‍♂️ &#x1f52e;&#x1f52e;&#x1f52e;&#x1f52e;&#x1f52e;往期优质项目实例&#x1f52e;&…

蓝牙资讯|Counterpoint发布2023年Q1中国智能手表报告

根据市场调查机构 Counterpoint Research 公布的最新报告&#xff0c;2023 年第 1 季度中国智能手表出货量同比下降 28%&#xff0c;环比下降 16%&#xff0c;达到过去 12 个季度以来的最低水平。 本季度智能手表市场中&#xff0c;华为、苹果和小天才&#xff08;imoo&#…

想要入坑C++?当我拿出菱形虚拟继承,阁下又该如何应对

文章目录 &#x1f490;专栏导读&#x1f490;文章导读&#x1f337;继承的定义方式&#x1f337;继承方式与访问限定符&#x1f337;基类和派生类对象赋值转换&#x1f337;继承中的作用域&#x1f337;派生类的默认成员函数&#x1f337;继承与友元&#x1f337;继承与静态成…

基于 ESP32 通过 SMTP 服务器 来发送电子邮件信息

电子邮件在全球范围内被用作数字通信的重要组成部分。电子邮件主要用于官方通信目的,因为它最方便、成本效益高、保存记录、覆盖全球且环保。电子邮件是一种非常快捷的通信方式,只是您需要稳定的互联网连接。 在这个项目中,我们将使用ESP32开发板发送电子邮件(纯文本和 HTM…

因为计算机中丢失VCRUNTIME140怎么办?为什么会丢失VCRUNTIME140.dll

vcruntime140.dll是一个Windows动态链接库&#xff0c;其主要功能是为C/C编译的程序提供运行时支持。这个库在Microsoft Visual Studio 2015中被引入&#xff0c;其名称中的“140”代表版本号。在我们打开运行软件或者游戏程序的时候&#xff0c;电脑提示因为计算机中丢失VCRUN…

windows下编译roadrunner和作为laravel服务器实践

roadrunner源码地址&#xff1a;https://gitee.com/mirrors/RoadRunner?_fromgitee_search windows下编译roadrunner源码获得rr.exe可执行文件 将rr.exe拷贝到laravel目录下 .rr.yaml配置文件内容&#xff1a; version: 3 server: command: "php vendor/spiral/road…

基于Python+AIML+Tornado的智能聊天机器人(NLP+深度学习)含全部工程源码+语料库 适合个人二次开发

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境Tornado 环境 模块实现1. 前端2. 后端3. 语料库4. 系统测试 其它资料下载 前言 本项目旨在利用AIML技术构建一个聊天机器人&#xff0c;实现用户通过聊天界面与机器人交互的功能。通过提供的工程源代码&#xf…

【业务功能篇07】Mysql 模糊查询

业务场景&#xff1a;我们对不同的业务逻辑进行数据处理时&#xff0c;多数是离不开需要模糊匹配的时候&#xff0c;比如要获取该表某个字段中&#xff0c;含有某个具体的字符内容&#xff0c;过滤出业务想要的数据。 这里介绍有这么几种&#xff1a; 一、MySQL通配符模糊查询(…

3D模型Web轻量化工具,如何监测矿藏开采安全与效率?

随着科技的进步&#xff0c;各个领地都在不断探索和应用新的技术来提高效率和准确性。HOOPS技术作为一种先进的3D可视化和模拟技术&#xff0c;正在采掘和地质科学领域发挥着重要的作用。本文将探讨HOOPS技术在采掘和地质科学中的具体应用&#xff0c;并分析其对这些领域的影响…

Maven高级1-分模块开发与依赖问题

1. 分模块开发与设计 将原始模块按照功能拆分成若干个子模块&#xff0c;方便模块间的相互调用&#xff0c;接口共享&#xff1b; 言简意赅就是把功能模块放出去&#xff0c;然后通过在pom文件中导入坐标找到&#xff1b; 注意拆出来的功能模块需要通过Maven指令安装模块选择in…