SSM学习——Spring AOP与AspectJ

Spring AOP与AspectJ

概念

AOP的全称为Aspect-Oriented Programming,即面向切面编程。

想象你是汉堡店的厨师,每一份汉堡都有好几层,这每一层都可以视作一个切面。现在有一位顾客想要品尝到不同风味肉馅的汉堡,如果按照传统的方式,你需要做多个汉堡,每个汉堡只有肉馅是不一样的,但是你每做一个汉堡都要重新制作面包。而聪明的厨师只需做一个汉堡,仅将肉饼那一层分成不同口味的几个区域,这样你就不需要再重复制作面包了。

对于程序员也是一样的,有多少个接口就要写或复制多少代码那一定是无法忍受的,我们只想关心不同的那部分。

尽管想通俗来讲,但是还是要去熟悉专业的概念:

  • Aspect:切面,类似于Java类声明,里面会有PointcutAdvice
  • Joint point:连接点,在程序执行过程中某个阶段点
  • Pointcut:切入点,切面与程序流的交叉点,往往此处需要处理
  • Advice:通知或增强,在切入点处所要执行的代码。可以理解为切面类中的方法。
  • Target object:目标对象,指所有被通知的对象。
  • Proxy:代理,将通知应用到目标对象后,被动态创建的对象。
  • Weaving:织入,将切面代码插到目标对象上,从而生成代理对象的过程。

别担心,我们之后会通过代码来慢慢理解。

AOP的实现

AOP的实现主要分为静态代理动态代理,在本教程中静态代理我们用AspectJ,而动态代理用Spring AOP

静态代理在编译期就确定了代理类,而动态代理需要靠反射机制动态生成代理类。

Spring AOP动态代理有两种实现方式:一种是JDK动态代理,这种方式需要接口;另一种是CGLib动态代理,这种方式不依赖接口。

有了以上的知识,我们开始写代码,首先新创建一个Maven项目top.cairbin.test2,如果你不会请回去看之前的章节。

然后在pom.xml<dependencies></dependencies>之间添加依赖包

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.16</version>
</dependency>		
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>		
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version>
</dependency>

我们去实现一个IUser接口,要求接口内有两个方法void addUser()void deleteUser()

package top.cairbin.test2;public interface IUser {void addUser();void deleteUser();
}

接下来定义一个实现该接口的User

package top.cairbin.test2;public class User implements IUser{@Overridepublic void addUser() {System.out.println("进行增加用户操作!");}@Overridepublic void deleteUser() {System.out.println("进行删除用户操作!");}
}

我们定义一个切面类,该类中的两个方法void check()void log()分别模拟权限检查和日志记录功能。

切面类如下所示

package top.cairbin.test2;public class MyAspect {public void check() {System.out.println("正在模拟权限认证");}public void log() {System.out.println("正在模拟日志记录");}
}

JDK动态代理

接下来创建代理类JdkProxy,这个类实现了JDK动态代理的InvocationHandler接口,并实现代理方法。

package top.cairbin.test2;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class JdkProxy implements InvocationHandler{private final IUser user;public JdkProxy(IUser user) {this.user = user;}public static Object createProxy(IUser user) {ClassLoader classLoader = JdkProxy.class.getClassLoader();// 被代理对象实现的所有接口Class[] clazz = user.getClass().getInterfaces();// 使用代理类,进行增强,返回的是代理后的对象return  Proxy.newProxyInstance(classLoader,clazz,new JdkProxy(user));}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 声明切面MyAspect myAspect = new MyAspect();// 前增强myAspect.check();// 在目标类上调用方法,并传入参数Object obj = method.invoke(user, args);// 后增强myAspect.log();return obj;}}

然后尝试在App.javamain方法中使用它们

package top.cairbin.test2;public class App 
{public static void main( String[] args ){// 创建目标对象IUser user= new User();// 创建代理,并从代理中获取增强后的目标对象IUser user2 = (IUser)JdkProxy.createProxy(user);// 执行方法user2.addUser();user2.deleteUser();}
}

输出结果如下图所示

CGLib动态代理

我们不妨尝试使用CGLib来玩一下

创建一个新的类,名称为CglibProxy,并实现接口MethodInterceptor以及相应的方法

package top.cairbin.test2;import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;public class CglibProxy implements MethodInterceptor {public static Object createProxy(Object target){Enhancer enhancer = new Enhancer();  enhancer.setSuperclass(target.getClass());  enhancer.setCallback(new CglibProxy()); return enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, 	Object[] args, MethodProxy proxy) throws Throwable {// 声明切面MyAspect myAspect = new MyAspect();// 前增强myAspect.check();//获取增强后的目标对象Object target = proxy.invokeSuper(obj, args);// 后增强myAspect.log();return target;}
}

尝试调用下

package top.cairbin.test2;public class App 
{public static void main( String[] args ){IUser user = (IUser)CglibProxy.createProxy (new User());user.addUser();user.deleteUser();}
}

不出所料,果然成功了

我们仔细观察CGLib的这几段代码,在CglibProxy类中我们并没有用到IUser这个接口,而是返回Object,然后外面也就是调用者那里强制转换为IUser类型!

不妨再“懒”一些,我们借助Spring的依赖注入,从Spring的容器中直接返回增强后的实现了IUser接口的对象试一试。

首先在resources/AppCtx.xml中编写Bean的配置(这里有坑,如果行不通回前面的文章看看)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"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-4.3.xsd"><!-- 目标类 --><bean id="user" class="top.cairbin.test2.User" /><!-- 切面类 --><bean id="myAspect" class="top.cairbin.test2.MyAspect" /><!-- 使用Spring代理工厂定义一个名称为userProxy的代理对象 --><bean id="userProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><!-- 指定代理实现的接口--><property name="proxyInterfaces" value="top.cairbin.test2.IUser" /><!-- 指定目标对象 --><property name="target" ref="user" /><!-- 指定切面,织入环绕通知 --><property name="interceptorNames" value="myAspect" /><!-- 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理 --><property name="proxyTargetClass" value="true" /></bean>
</beans>

修改下MyAspect类,并实现接口MethodInterceptor

package top.cairbin.test2;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;public class MyAspect implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation mi) throws Throwable {this.check();// 执行目标方法Object obj = mi.proceed();this.log();return obj;}public void check() {System.out.println("正在模拟权限认证");}public void log() {System.out.println("正在模拟日志记录");}
}

App类中的main方法调用如下

package top.cairbin.test2;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class App 
{public static void main( String[] args ){ApplicationContext app = new ClassPathXmlApplicationContext("AppCtx.xml");IUser user = (IUser)app.getBean("userProxy");user.addUser();user.deleteUser();}
}

点击运行得到结果

AspectJ静态代理

使用AspectJ静态代理,我们重新设计下MyAspect切面类

package top.cairbin.test2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/*** 切面类,在此类中编写通知*/
@Aspect
@Component
public class MyAspect {// 定义切入点表达式@Pointcut("execution(* top.cairbin.test2.*.*(..))")// 使用一个返回值为void、方法体为空的方法来命名切入点private void myPointCut(){}// 前置通知@Before("myPointCut()")public void myBefore(JoinPoint joinPoint) {System.out.print("前置通知 :模拟执行权限检查...,");System.out.print("目标类是:"+joinPoint.getTarget() );System.out.println(",被织入增强处理的目标方法为:"+joinPoint.getSignature().getName());}// 后置通知@AfterReturning(value="myPointCut()")public void myAfterReturning(JoinPoint joinPoint) {System.out.print("后置通知:模拟记录日志...," );System.out.println("被织入增强处理的目标方法为:"+ joinPoint.getSignature().getName());}// 环绕通知	@Around("myPointCut()")public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {// 开始System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");// 执行当前目标方法Object obj = proceedingJoinPoint.proceed();// 结束System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");return obj;}// 异常通知@AfterThrowing(value="myPointCut()",throwing="e")public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {System.out.println("异常通知:" + "出错了" + e.getMessage());}// 最终通知@After("myPointCut()")public void myAfter() {System.out.println("最终通知:模拟方法结束后的释放资源...");}
}

对于切入点注解@Pointcut("execution(* top.cairbin.test2.*.*(..))")表示对top.cairbin.test2这个包下的所有类的所有方法生效。

我们再来看看Spring中的Advice的几种类型:

  • org.springframework.aop.MethodBeforeAdvice,前置通知,目标方法执行前实施,可用于权限管理。
  • org.springframework.aop.AfterReturningAdvice,后置通知,在目标方法执行后实施,用于关闭文件流、上传文件、删除临时文件等。
  • org.aopalliance.intercept.MethodInterceptor,环绕通知,在目标方法实施前后,一般用于日志或事务管理。
  • org.springframework.aop.ThrowsAdvice,异常抛出通知,在抛出异常后实施。
  • org.springframework.aop.IntroductionInterceptor,引介通知,在目标类中添加新方法和属性,可以应用于修改老版本程序。

我们还要实现自动扫描和依赖注入,看看我们的User

package top.cairbin.test2;
import org.springframework.stereotype.Component;@Component
public class User implements IUser{@Overridepublic void addUser() {System.out.println("进行增加用户操作!");}@Overridepublic void deleteUser() {System.out.println("进行删除用户操作!");}
}

自然也少不了AppCtx.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-4.3.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"><!-- 指定需要扫描的包,使注解生效 --><context:component-scan base-package="top.cairbin.test2" /><!-- 启动基于注解的声明式AspectJ支持 --><aop:aspectj-autoproxy />
</beans>

main方法中测试下,为了清楚,我这里仅调用了addUser一个方法

package top.cairbin.test2;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class App 
{public static void main( String[] args ){ApplicationContext app = new ClassPathXmlApplicationContext("AppCtx.xml");IUser user = (IUser)app.getBean("user");user.addUser();}
}

我们发现当环绕通知与前置通知和后置通知同时使用的时候,优先级如下:

  • 环绕通知开始
  • 前置通知
  • 方法执行
  • 后置通知
  • 环绕通知结束

想必到了这里,你对AspectJ的使用有了一定的了解,但是对于相应的注解还是不太清楚,请仔细阅读下方图片中的表格,结合一开始的术语体会下:

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

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

相关文章

RFID:锂电池自动化产线的智能监护者

RFID&#xff1a;锂电池自动化产线的智能监护者 一个拥有尖端工业科技的黑灯工厂里&#xff0c;自动化技术已经代替大部分的人工&#xff0c;在每天的自动化生产中会有大量的产品问世。但是人员少&#xff0c;自动化多的工厂怎么做生产管理&#xff0c;产品溯源呢&#xff1f;…

一定要学会的考研数学刷题方法,效果真的炸裂‼️

以1800题为例 1800是知识覆盖的最全面的&#xff0c;但要是刷的方法不对&#xff0c;它就是一个大坑。第一章我记得刷了整整一周。动不动就卡住&#xff0c;而且题目质量真的有点堪忧&#xff0c;答案跳步跳的真的怀疑人生&#xff0c;有时候看个答案都能看半天。而且1800的题…

正大国际:什么是qi货合约?

期货合约是一种标准化的、在期货交易所进行交易的合约。它规定了在未来某个特定时间点以约定价格买入或卖出某种特定商品或金融工具的义务。期货合约的买卖双方都需要遵守交易所的规定和交易规则 期货合约的主要特点包括&#xff1a; 标准化&#xff1a;期货合约规定了商品的品…

企业客户信息反馈平台的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读300套最新项目持续更新中..... 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含ja…

为什么品牌宣传需要深度稿件?媒介盒子揭秘

在信息洪流中&#xff0c;品牌想要占据用户心智仅靠传统的广告方式很难达成目标&#xff0c;只有真正有价值的信息才能吸引用户注意力&#xff0c;品牌方可以通过深度稿件来实现。 深度传播稿是一种高质量的、需要花费较长时间和精力来撰写的宣传文章&#xff0c;篇幅较长&…

Leetcode 322. 零钱兑换

心路历程&#xff1a; 这道题和上一道完全平方数的和基本上一摸一样&#xff0c;甚至比上一道题还简单&#xff0c;基于dp的建模&#xff1a; 状态&#xff1a;当前的目标总金额 动作&#xff1a;选哪一个硬币 返回值&#xff1a;凑成该目标总金额的最少硬币个数 这道题如果硬…

建议有这些需求的企业部署SD-WAN!

SD-WAN技术在当今企业网络领域引领着一股新的风潮。其通过软件控制和管理广域网连接&#xff0c;为企业提供了更加灵活、智能和高效的网络解决方案。SD-WAN都具备广泛的适用性&#xff0c;特别适合以下几类企业场景&#xff1a; 分布式企业&#xff1a; 对于跨越多个地理位置的…

boost库搜索引擎

文章目录 0. 前言1. 搜索引擎原理2. 技术栈和项目环境3. 正排索引和倒排索引3.1 正排索引3.2 倒排索引3.3 模拟查找 4. 获取数据源5. 数据清洗5.1 保存路径5.2 解析文件提取标题提取内容构造url 5.4 保存内容 6. 建立索引6.1 建立正排索引6.2 建立倒排索引6.3 构建索引 7. 搜索…

阿里云优惠券领取方法、领券页面及使用教程

随着云计算技术的不断发展和普及&#xff0c;越来越多的企业和个人选择使用阿里云作为他们的云服务提供商。为了吸引更多的用户&#xff0c;阿里云提供了各种优惠券&#xff0c;让用户在购买云产品和服务时可以享受到优惠。本文将为大家详细介绍阿里云优惠券的领取方法、领券页…

鸿蒙OS元服务开发说明:【WebGL网页图形库开发接口】

一、场景介绍 WebGL主要帮助开发者在前端开发中完成图形图像的相关处理&#xff0c;比如绘制彩色图形等。目前该功能仅支持使用兼容JS的类Web开发范式开发。 二、接口说明 表1 WebGL主要接口列表 鸿蒙OS开发更多内容↓点击HarmonyOS与OpenHarmony技术鸿蒙技术文档开发知识更…

递归遍历目录结构和树状展现

在D盘下创建文件夹“电影”&#xff0c;在文件夹“电影”下创建“华语”、“好莱坞”&#xff0c;在文件夹“华语”下创建文件“人民的名义.mp4”、“天安门传奇.mp4”、“程序员统治世界.mp4”&#xff0c;在文件夹“好莱坞”下创建文件“国王的演讲.mp4”、“速度与激情8.mp4…

web 技术中前端和后端交互过程

1、客户端服务器交互过程 客户端:上网过程中,负责浏览资源的电脑,叫客户端服务器:在因特网中,负责存放和对外提供资源的电脑叫服务器 服务器的本质: 就是一台电脑,只不过相比个人电脑它的性能高很多,个人电脑中可以通过安装浏览器的形式,访问服务器对外提供的各种资源。 个人…