03_03_初识SpringAOP和应用

2024-06-04 00:28
文章标签 初识 应用 03 springaop

本文主要是介绍03_03_初识SpringAOP和应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、SpringAOP的初识与原理

1、概述

  • AOP:面向切面编程
  • OOP:面向对象编程
  • 面相切面编程:是基于OOP基础之上的新编程思想,OOP面向的主要是对象是类,而AOP面向的主要对象是切面,它在处理日志、安全管理、事务管理、权限控制等方面具有非常重要的作用。是一种非侵入式(不改变原来的代码)的代码增强的开发工具,可以减
    少重复代码的编写,降低模块间的耦合度,并有利于提高程序的可拓展性可维护性
  • AOP是Spring中的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来对IOC进行补充。
  • Spring AOP 是基于动态代理实现的,如果被代理的对象已经实现了某个接口,则可以使用JDK动态代理(核心是invocationHandler接口和Proxy类)的方式来创建代理对象;如果被代理对象没有实现某个接口,可以使用Cglib(核心是MethodInterceptor接口和Enhancer类),基于继承的方式,生成一个被代理对象的子类作为代理;
  • 简而言之,在程序运行期间,在不修改原有代码的情况下,增强跟主业务没有关系的公共功能代码到之前写好的方法中的指定位置, 这种编程的方式叫AOP;

2、代理模式

  • AOP的底层是通过代理模式来实现。
  • 代理模式是一种设计模式,其主要目的是为其他对象提供一种代理,以控制对该对象的访问。这意味着用户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
  • 代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。
  • 总的来说,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能。
  • 代理模型分为静态代理和动态代理(JDK动态代理CGLB动态代理)两种。

2.1 静态代理

  • 静态代理就是需要手动去为每一个被代理对象去创建一个代理类。
  • 例如,有一个游戏代练的示例:
    /*** 游戏接口(公共接口)*/
    public interface IGamePlayer {//登录游戏void start();//玩游戏void play();
    }
    
    /*** 菜鸟游戏玩家(目标对象-被代理对象)*/
    public class GamePlayer implements IGamePlayer {//玩家名称private String name;//构造赋值public GamePlayer(String name){this.name = name;}@Overridepublic void start() {System.out.println("正在登录游戏........");System.out.println(name + ":开始了游戏");}@Overridepublic void play() {System.out.println(name + "技术太low,游戏失败!");}
    }
    
        @Testpublic void testProxy(){GamePlayer player = new GamePlayer("菜鸟");player.start();player.play();}
    

菜鸟游戏把把输

  • 现在,因为菜鸟玩家的技术能力太低,玩游戏把把都输,他需要一个代练帮他玩游戏提升等级,我们可以帮他创建一个代理对象帮他代玩。
    /*** 代练游戏玩家(静态代理类)*/
    public class ProxyGamePlayer implements IGamePlayer{//代练private String proxyName;//菜鸟玩家private GamePlayer gamePlayer;public ProxyGamePlayer(String name){this.proxyName = name;this.gamePlayer = new GamePlayer(name);}@Overridepublic void start() {//代练以菜鸟的身份去玩游戏System.out.println("拿到"+proxyName+"的游戏账号密码");gamePlayer.start();}@Overridepublic void play() {//代练帮菜鸟玩游戏System.out.println("代练技术太强,赢得了游戏!");}
    }
    
     @Testpublic void testProxy(){IGamePlayer gamePlayer = new ProxyGamePlayer("菜鸟");gamePlayer.start();gamePlayer.play();}
    

代练赢

  • 静态代理的弊端
    • 需要为每一个被代理的类创建一个代理类,虽然这种方式可以实现,但是成本太高。

2.2 动态代理

2.2.1 JDK动态代理
  • 核心关键

    • 一个类:Proxy
      • 概述:Proxy是所有代理类的基类;
      • 作用:创建代理对象,newProxyInstance();
    • 一个接口:InvocationHandler
      • 概述:实现动态织入效果的关键接口;
      • 作用:通过反射机制,执行invoke()方法来实现动态织入的效果;
  • 还是以游戏代练为例,实现Jdk动态代理:

    • 创建一个为被代理对象(目标对象)创建代理类的工具类
    public class GamePlayerProxy {/*** 被代理对象(目标对象)*/private Object targetObj;/*** 有参构造,避免目标对象为null* @param targetObj*/public GamePlayerProxy(Object targetObj){this.targetObj = targetObj;}/*** 获取代理类对象*/public Object getProxyObject(){Object proxyObject = null;/**  ClassLoader loader 类加载器,通常指被代理类的接口的类加载器*  Class<?> [] interfaces  类型,通常指被代理类的接口的类型*  InvacationHandler handler 委托执行的代理类,具体功能增强的逻辑在这里实现*/ClassLoader loader = targetObj.getClass().getClassLoader();Class<?>[] interfaces = targetObj.getClass().getInterfaces();proxyObject = Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {//重写invoke()实现动态织入效果@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object invoke = "";if (method.getName().equals("play")){//逻辑替换System.out.println("代练技术太强,已成功帮菜鸟赢得了游戏!");}else {//逻辑增强//执行被代理的方法/** Object targetObj 被代理的对象* Object... args 被代理的方法的参数*/invoke = method.invoke(targetObj, args);}return invoke;}});//返回创建好的代理类对象return proxyObject;}
    
    • 使用代理类对象来代玩游戏:
      	@Testpublic void testJdkProxy(){//被代理(目标)对象IGamePlayer gamePlayer = new GamePlayer("菜鸟");//获取代理对象(代理对象不能转换为目标对象)GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(gamePlayer);IGamePlayer player = (IGamePlayer) gamePlayerProxy.getProxyObject();//代理玩游戏player.start();player.play();}
    

    jdk动态代理实现

2.2.2 CGLB动态代理
  • 后期补充…

二、SpringAOP的应用

  • 通过上述的例子,已经实现了代打游戏的目的,但是这种动态代理的实现方式调用的是JDK的基本实现,如果需要被代理的目标对象没有实现任何接口,那么是无法为它创建代理对象的,这也是致命的缺陷。而在Spring中我们可以不编写上述如此复杂的代码,只需要利用AOP,就能够轻轻松松实现上述功能,当然,Spring AOP的底层实现也依赖的是动态代理。

1、AOP的核心概念及术语

概念图

  • 切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。
  • 连接点(Join point): 在程序执行过程中某个特定的点。在Spring AOP中,一个连接点总
    是代表一个方法的执行。
  • 通知(Advice): 在切面的某个特定的连接点上执行的动作。
  • 切点(Pointcut): 匹配连接点的断言。
  • 引入(Introduction): 声明额外的方法或者某个类型的字段。
  • 目标对象(Target object): 被一个或者多个切面所通知的对象。
  • 织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象的过程。这个过程可以在编译时、类加载时、运行时完成。

2、AOP的通知类型

  • 前置通知
    • 语法:@Before(value=“execution()”)
    • 执行时机:在切入点表达式中指定的方法执行之前执行(无论是否发生异常,都会执行);
  • 后置通知
    • 语法:@After(value=“execution()”)
    • 执行时机:在切入点表达式中指定的方法执行之后执行(无论是否发生异常,都会执行);
  • 返回通知
    • 语法:@AfterReturning(pointcut = “execution()”,returning = “result”)
    • 执行时机:在切入点表达式中指定的方法返回结果时执行(如果发生异常,则不执行);
  • 异常通知
    • 语法:@AfterThrowing(pointcut = “execution()”,throwing = “e”)
    • 执行时机:在切入点表达式中指定的方法发生异常时执行;
  • 环绕通知
    • 语法:@Around(value = “pointCut()”)
    • 作用:环绕通知可以对前置通知、后置通知、返回通知、异常通知进行整合使用;

3、AOP的应用场景

  • 日志管理
  • 权限认证
  • 安全检查
  • 事务控制

4、AOP的相关配置

4.1 添加pom依赖

 	<!--Aop的AspectJ框架的jar包 --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.28</version></dependency>

4.2 将目标类加入IOC容器

/**
* 计算器接口(公共接口)
*/
public interface CalculatorService {int add(int a,int b);int sub(int a,int b);int mul(int a,int b);int div(int a,int b);}
/**
*计算器实现类对象(目标对象)
*/
@Service
public class CalculatorServiceImpl implements CalculatorService {@Overridepublic int add(int a, int b) {System.out.println("正在执行加法......");return a+b;}@Overridepublic int sub(int a, int b) {System.out.println("正在执行减法......");return a-b;}@Overridepublic int mul(int a, int b) {System.out.println("正在执行乘法......");return a*b;}@Overridepublic int div(int a, int b) {System.out.println("正在执行除法......");return a/b;}
}

4.3 声明切面类并加入IOC容器

@Component   //标识该类是一个组件类(保证这个切面在IOC容器中)
@Aspect      //标识该类是一个切面
public class LogUtil {//可以采用引用切点的方式,让其他通知共同使用@Pointcut(value = "execution(* org.example.mvc.impl.*.*(..))")public void pointCut(){}//前置通知@Before(value = "execution(public int org.example.mvc..CalculatorServiceImpl.*(int, int))")public void beforeMethod(JoinPoint joinPoint){//获取方法名称String methodName = joinPoint.getSignature().getName();//获取参数Object[] args = joinPoint.getArgs();System.out.println("【前置通知】计算器  "+methodName+"  方法,执行参数为:"+ Arrays.toString(args));}//后置通知
//    @After("pointCut() && @annotation(logger)")@After(value = "pointCut()")public  void afterMethod(JoinPoint joinPoint){//获取方法名称String methodName = joinPoint.getSignature().getName();System.out.println("【后置通知】计算器  "+methodName+" 方法,执行完毕");}//后置返回通知@AfterReturning(pointcut = "pointCut()",returning = "result")//注意:returning属性中的返回结果名称要与入参中的参数名一致public void afterReturn(JoinPoint joinPoint,Object result){//获取方法名称String methodName = joinPoint.getSignature().getName();System.out.println("【后置返回通知】计算器 "+methodName+" 方法,执行结果为:"+ result);}//后置异常通知@AfterThrowing(pointcut = "pointCut()",throwing = "ex")//注意:throwing属性中的异常名称要与入参中的异常参数名一致public void afterThrowing(JoinPoint joinPoint,Exception ex){//获取方法名称String methodName = joinPoint.getSignature().getName();StringWriter stringWriter = new StringWriter();ex.printStackTrace(new PrintWriter(stringWriter,true));System.out.println("【后置异常通知】计算器 "+methodName+" 方法,执行时出现异常:" + stringWriter.getBuffer().toString());}//环绕通知@Around(value = "pointCut()")public Object around(ProceedingJoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();Object result = null;try {//前置通知System.out.println("【环绕-前置通知】计算器 "+methodName+" 方法,执行参数为:"+ Arrays.toString(args));//触发目标对象中的目标方法result = joinPoint.proceed();//返回通知System.out.println("【环绕-返回通知】计算器 "+methodName+" 方法,执行结果为:"+ result);} catch (Throwable throwable) {throwable.printStackTrace();//异常通知System.out.println("【环绕-异常通知】计算器 "+methodName+" 方法,执行时出现异常:" + throwable.getMessage());} finally {//后置通知System.out.println("【环绕-后置通知】计算器 "+methodName+" 方法,执行完毕");}return result;}}

切点表达式

  • 切点表达式语法:

    • execution(访问修饰符 方法返回值类型 包名.类名.方法名(参数...))
      • (1)访问修饰符:可写可不写;
      • (2)方法返回值类型:如果是JDK自带的类型,则直接可以用类型名,例如(Int)。如果不是,则需要写上自定义类型的全局限定名,如果返回值的类型不同,则可以用通用符 * 代表任何类型;
      • (3)包名:可以写具体的包名,也可以用通用符*占位代表任何包名,..代表子孙包。例如:cn.、cn.trs.、cn.trs.service…*
      • (4)类的全局限定名:可以写具体的类名,也可以使用通配符*代表任何类的名字;
      • (5)方法名:可以写具体的方法名,也可以使用通配符*代表任何方法的名字,也可以模糊匹配 *Add => userAdd()、roleAdd()
      • (6)参数:如果是JDK自带类型,可以不用写类型的全局限定名,否则,需要写上自定义类型的全局限定名。如果需要匹配任意参数可以写:..代替
  • 合并切点表达式:

    • 可以使用 &&|| !等符号进行合并操作,也可以通过名字来指向切点表达式。
     //&&:两个表达式同时
    execution( public int cn.trs.inter.MyCalculator.*(..)) && execution(* *.*(int,int) )
    //||:任意满足一个表达式即可
    execution( public int cn.trs.inter.MyCalculator.*(..)) && execution(* *.*(int,int) )
    //!:只要不是这个位置都可以进行切入
    //&&:两个表达式同时
    execution( public int cn.trs.inter.MyCalculator.*(..))
    

4.4 开启组件扫描和AOP的注解支持

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><!--开启组件扫描--><context:component-scan base-package="org.example"></context:component-scan><!--开启AspectJ的注解对AOP的支持--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • 在Spring容器中,如果有接口,那么会使用jdk自带的动态代理,如果没有接口,那么会使用cglib的动态代理。

这篇关于03_03_初识SpringAOP和应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1028546

相关文章

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

Python调用另一个py文件并传递参数常见的方法及其应用场景

《Python调用另一个py文件并传递参数常见的方法及其应用场景》:本文主要介绍在Python中调用另一个py文件并传递参数的几种常见方法,包括使用import语句、exec函数、subproce... 目录前言1. 使用import语句1.1 基本用法1.2 导入特定函数1.3 处理文件路径2. 使用ex

将Python应用部署到生产环境的小技巧分享

《将Python应用部署到生产环境的小技巧分享》文章主要讲述了在将Python应用程序部署到生产环境之前,需要进行的准备工作和最佳实践,包括心态调整、代码审查、测试覆盖率提升、配置文件优化、日志记录完... 目录部署前夜:从开发到生产的心理准备与检查清单环境搭建:打造稳固的应用运行平台自动化流水线:让部署像

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或

在Ubuntu上部署SpringBoot应用的操作步骤

《在Ubuntu上部署SpringBoot应用的操作步骤》随着云计算和容器化技术的普及,Linux服务器已成为部署Web应用程序的主流平台之一,Java作为一种跨平台的编程语言,具有广泛的应用场景,本... 目录一、部署准备二、安装 Java 环境1. 安装 JDK2. 验证 Java 安装三、安装 mys

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一

java中VO PO DTO POJO BO DO对象的应用场景及使用方式

《java中VOPODTOPOJOBODO对象的应用场景及使用方式》文章介绍了Java开发中常用的几种对象类型及其应用场景,包括VO、PO、DTO、POJO、BO和DO等,并通过示例说明了它... 目录Java中VO PO DTO POJO BO DO对象的应用VO (View Object) - 视图对象

Go信号处理如何优雅地关闭你的应用

《Go信号处理如何优雅地关闭你的应用》Go中的优雅关闭机制使得在应用程序接收到终止信号时,能够进行平滑的资源清理,通过使用context来管理goroutine的生命周期,结合signal... 目录1. 什么是信号处理?2. 如何优雅地关闭 Go 应用?3. 代码实现3.1 基本的信号捕获和优雅关闭3.2

正则表达式高级应用与性能优化记录

《正则表达式高级应用与性能优化记录》本文介绍了正则表达式的高级应用和性能优化技巧,包括文本拆分、合并、XML/HTML解析、数据分析、以及性能优化方法,通过这些技巧,可以更高效地利用正则表达式进行复杂... 目录第6章:正则表达式的高级应用6.1 模式匹配与文本处理6.1.1 文本拆分6.1.2 文本合并6