Spring—面向切面编程(AOP)详解 项目中如何体现spring中的切面编程
lipiwang 2024-11-13 13:38 58 浏览 0 评论
作者:我要进阿里! 原文:https://www.cnblogs.com/liuhongchen/p/11580319.html?utm_source=tuicool&utm_medium=referral
声明:本问仅仅是一个初学者的学习记录、心得总结,其中肯定有许多错误,不具有参考价值,欢迎大佬指正,谢谢!
一、问题引入
? 在日常写项目的时候,肯定少不了要打印日志。例如,要向数据库中insert一个用户,我想在插入前输出一下相关信息,怎么实现呢?最基本的做法是:在insert方法中写日志输出语句。这样写完全能实现功能,但是会不会显得很冗余?耦合度是不是很高?编程的准则是“高内聚,低耦合”,低耦合的意思就是类与类之间的依赖关系尽量少、关联程度尽量小。
? 而如果在上述情景中使用面向切面编程(AOP),就可以不在insert方法中写日志输出语句却能实现日志输出功能。当然,AOP不止如此。
二、概念引入
1.AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方 式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个 热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑 的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高 了开发的效率。
2.几个基本概念
- 切入点:所有要操作的方法定义,要求业务层方法风格统一 - 分离点:将不可分割的组件单独提取出去定义为单独的操作功能 - 横切关注点:将所有与开发无关的程序组成类单独提取后组织运行 - 织入:将所有切入点、关注点的代码组成在一张完整的程序结构中
3.通知(Advice)
? AOP是通过通知来实现功能的,有如下五种:
- 前置通知(BeforeAdvice)
- 后置通知(AfterAdvice)
- 后置返回通知(AfterReturningAdvice)
- 后置异常通知(AfterThrowingAdvice)
- 环绕通知(AroundAdvice)
三、Pointcut与Execution表达式
? pointcut使用execution表达式表示要被切入的方法(即定义切入点)。
? execution表达式,功能类似于正则表达式,都是用来匹配筛选,只不过正则表达式用来筛选字符串,而execution表达式用来筛选要被切入的方法。
? execution表达式的格式为:
?execution(<注解>? <修饰符>? <返回值类型> <方法名模式>(<参数模式>) <异常>?)) <and args()>?)
? 例:execution(@Deprecated public Void aop.MyAspect.hello(int,String) throws Exception))')
package aop; public class AspectDemo { @Deprecated public void hello(int i,String s) throws Exception{ } }
? 其实不难发现,这个表达式和我们声明的方法的各个部分一一对应
- 注解:(可省略)例如上面代码中的@Deprecated ,就是筛选带有该注解的方法
- 修饰符(可省略)
- public
- protected
- private
- 当然一般用通配符 *
- 返回值类型
- 写各种返回值,一般用通配符 *
- 方法名模式
- 包名部分:在上例中,AspectDemo是位于aop包中的,所以可以通过包名.包名.类名的格式来定位到某个类,例如aop.AspectDemo 中aop. 就是包名部分;
- 当然也可以用通配符
- *:匹配任何数量字符,例如service.*.UserService 表示的是service的直接子包
- ..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包,例如service..代表着匹配service及其包含的所有包;而在方法参数模式中匹配任何数量参数。
- +: 匹配指定类型的子类型;仅能作为后缀放在类型模式后边,例如java.lang.Number+ 表示的是lang包下Numer的子类
- 类名部分:在上例中aop.AspectDemo中aop.是包名部分,AspectDemo就是类名部分,可以用通配符来表示,*用的比较多
- 参数模式
- 写法1:直接按照方法的参数列表写具体类型,上例的方法中参数列表(int i,String s),就可以直接在表达式中写(int,String)
- 写法2:使用通配符:
- “()”表示方法没有任何参数;
- “(..)”表示匹配接受任意个参数的方法
- “(..,java.lang.String)”表示匹配接受java.lang.String类型的参数结束,且其前边可以接受有任意个参数的方法
- “(java.lang.String,..)” 表示匹配接受java.lang.String类型的参数开始,且其后边可以接受任意个参数的方法
- “(*,java.lang.String)” 表示匹配接受java.lang.String类型的参数结束,且其前边接受有一个任意类型参数的方法;
- 异常模式(可省略)
- throws Exception1,Exception2.。。。
- 传入参数(可省略)
- ? and args(arg-name),一般用于AfterAdvice和Around通知
四、前期准备
- 创建项目,导入相关jar包,参考Spring——IOC,此外还需导入aop和aspectj的jar包
- 创建applicationContext.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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <aop:aspectj-autoproxy/> </beans>
? 注意:新增了
?xmlns:aop="http://www.springframework.org/schema/aop"
?http://www.springframework.org/schema/aop
?http://www.springframework.org/schema/beans/spring-aop.xsd
?<aop:aspectj-autoproxy/> 不加这个可能会报错,可坑了
- 创建UserService这个类,内部有insert方法用来注册用户
public class UserService { public void insert(){ System.out.println("UserService正在注册用户……"); } }
- 创建MyAspect类
public class MyAspect {}
五、基于XML配置的AOP
1.BeforeAdvice
? (1)在MyAspect类中创建方法beforeAdvice
public void beforeAdvice(){ System.out.println("【AOP】Before Advice正在执行……"); }
? (2)在applicationContext.xml中配置
?关于pointcut和execution表达式见下文
<!--首先要引入myAspect这个bean,备用--> <bean id="myAspect" class="aop.MyAspect"/> <bean id="userService" class="aop.UserService"/> <aop:config> <!--配置切面,一个aop:aspect标签对应一个Aspect类--> <aop:aspect id="beforeAdvice" ref="myAspect"> <!--配置通知 method对应MyAspect类中定义的方法,pointcut是切入点表达式用于筛选需要被 切入的方法--> <aop:before method="beforeAdvice" pointcut="execution(* aop..*.*(..)))"/> </aop:aspect> </aop:config>
? (3)编写测试类
public class UserServiceTest { public static void main(String[] args) { ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = context.getBean("userService", UserService.class); userService.insert(); } }
? (4)输出结果
【AOP】Before Advice正在执行…… UserService正在注册用户……
? 可以发现,BeforeAdvice就已经实现了
2.AfterAdvice(相当于异常里面的finally语句)
? (1)UserService类同上
? (2)在MyAspect中创建方法afterAdvice
public void afterAdvice(){ System.out.println("【AOP】after Advice…… 不管怎样我都会执行"); }
? (3)修改applicationContext.xml
<aop:config> <aop:aspect id="beforeAdvice" ref="myAspect"> <aop:after method="afterAdvice" pointcut="execution(* aop.*.insert(..)))" /> </aop:aspect> </aop:config>
? (4)编写测试类(同上)
? (5)输出结果
UserService正在注册用户…… 【AOP】after Advice…… 不管怎样我都会执行
3.AfterReturningAdvice
? (1)修改UserService的insert方法,使其有返回值
public class UserService { public int insert(){ System.out.println("UserService正在注册用户……"); return 1; } }
? (2)在MyAspect中新增afterReturningAdvice方法
public void afterReturningAdvice(int result) { System.out.println("【AOP】after advice……返回值为"+result); }
? (3)在applicationContext.xml中配置
<aop:config> <aop:aspect id="beforeAdvice" ref="myAspect"> <aop:after-returning method="afterReturningAdvice" pointcut="execution(* aop.*.insert(..)))" returning="result"/> </aop:aspect> </aop:config>
? 注意:这里这个returning="result"与MyAspect类中对应方法的参数名必须保持一致,本例中都为result
? (4)编写测试类(代码同1.)
? (5)输出结果
UserService正在注册用户…… 【AOP】after advice……返回值为1
4.AfterThrowingAdvice
? (1)修改UserService使其抛异常
public int insert() throws Exception { try { System.out.println("UserService开始注册用户……"); int i=1/0; }catch (Exception e){ throw new Exception("insert方法遇到异常……"); } return 1; }
? (2)在MyAspect中新增方法 afterThrowingAdvice
//这里传入的这个Exception就是捕获到的异常对象 public void afterThrowingAdvice(Exception e){ System.out.println("【AOP】得到异常信息:"+e.getMessage()); }
? (3)修改applicationContext.xml
<aop:config> <aop:aspect id="beforeAdvice" ref="myAspect"> <aop:after-throwing method="afterThrowingAdvice" pointcut="execution(* aop.*.insert(..)))" throwing="e"/> </aop:aspect> </aop:config>
? 注意:这里的throwing="e”就是跑出的异常对象的名字,要与MyAspect中afterThrowingAdvice方法中传入的参数Exception e的名字保持一致。
? (4)编写测试类
public static void main(String[] args) throws Exception { ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = context.getBean("userService", UserService.class); userService.insert(); }
? (5)输出结果
UserService开始注册用户…… 【AOP】得到异常信息insert方法遇到异常…… Exception in thread "main" java.lang.Exception: insert方法遇到异常…… at aop.UserService.insert(UserService.java:12) at aop.UserService$FastClassBySpringCGLIB$7e3b8e5e.invoke(<generated>) ...
5.AroundAdvice
? (1)修改UserService中的insert方法
public int insert(int arg) throws Exception { try { int i = 1 / 0; } catch (Exception e) { throw new Exception("insert方法遇到异常……"); } return 1; }
? (2)在MyAspect中添加方法AroundAdvice
//这里这个ProceedingJointPoint可以理解为切入点对象,可以通过它获取切入点(被切入的方法)的参数、返回值、抛出的异常,并且可以通过pjp.proceed(args);为该切入点设置参数 public int aroundAdvice(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); System.out.println("【AOP】before Advice,获取到insert方法传入的参数为:"+args[0]); Object result; try { result=pjp.proceed(args);//这里是我们手动执行切入点,并传入参数 System.out.println("【AOP】after Returning Advice,返回值为:"+result); }catch (Exception e){ //这里捕获的就是切入点运行时抛出的异常 System.out.println("【AOP】after Throwing Advice,错误信息为:"+e.getMessage()); } System.out.println("【AOP】after advice……不管异常不异常我都执行"); //这个就跟着这样写吧。。如果不写返回值的话会报 null return value does not match... return 1; }
? (3)修改applicationContext.xml文件
<aop:config> <aop:aspect id="beforeAdvice" ref="myAspect"> <aop:around method="aroundAdvice" pointcut="execution(* aop.*.insert(..)))" /> </aop:aspect> </aop:config>
? (4)编写测试类
public static void main(String[] args) throws Exception { ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = context.getBean("userService", UserService.class); userService.insert(2); }
? (5)输出结果
【AOP】before Advice,获取到insert方法传入的参数为:2 【AOP】after Throwing Advice,错误信息为:insert方法遇到异常…… 【AOP】after advice……不管异常不异常我都执行
六、基于注解配置的AOP
? 先把项目状态恢复到 “四、前期准备”的状态,然后在applicationContext.xml中添加下面的语句开启注解和包扫描。
<context:annotation-config/> <context:component-scan base-package="aop"/>
? 注意,这个base-package可以配置多个包,以半角(英文)逗号隔开,例如“aop,mvc,dao,service”,当然,为了省事,可以直接配一个顶级包,他会自动遍历扫描所有的子包及子包的子包等等。
? 然后为MyAspect类和UserService类加上注解
@Component @Aspect public class MyAspect {} @Service public class UserService {}
1.BeforeAdvice
? (1)在MyAspect类中创建beforeAdvice方法,并写好注解
@Before(value = "execution(* aop..*.*(..)))") public void beforeAdvice(){ System.out.println("【AOP】Before Advice正在执行……"); }
? 不需要配任何bean,是不是很爽
? (2)编写测试类
public static void main(String[] args) { ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = context.getBean("userService", UserService.class); userService.insert(); }
? (3)输出结果
【AOP】Before Advice正在执行…… UserService正在注册用户……
2.AfterService(我就不写测试了)
? (1)在MyAspect类中创建afterAdvice方法,并写好注解
@After(value = "execution(* aop..*.*(..)))") public void afterAdvice(){ System.out.println("【AOP】after Advice正在执行……"); }
3.AfterReturningAdvice
? (1)修改UserService中的insert方法
public int insert(){ System.out.println("UserService正在注册用户……"); return 1; }
? (2)在MyAspect类中创建afterReturningAdvice方法,并写好注解
@AfterReturning(value = "execution(* aop..*.*(..))&& args(result))") public void afterReturningAdvice(int result){ System.out.println("【AOP】after Returning Advice正在执行……返回值为:"+result); }
? (3)不写测试了
4.AfterThrowingAdvice
? (1)修改UserService中的insert方法
public int insert() throws Exception { try { int i=1/0; }catch (Exception e){ throw new Exception("【UserService】的insert遇到了错误……"); } return 1; }
? (2)在MyAspect类中创建afterThrowingAdvice方法,并写好注解
@AfterThrowing(value = "execution(* aop..*.*(..)))",throwing = "e") public void afterThrowingAdvice(Exception e){ System.out.println("【AOP】after Throwing Advice正在执行……错误信息为:"+e.getMessage()); }
? (3)不测试了。。
5.AroundAdvice
? (1)把MyAspect中之前写的方法注释掉,不然会影响观察结果
? (2)修改insert方法
public int insert(int arg) throws Exception { try { int i=1/0; }catch (Exception e){ throw new Exception("【UserService】的insert遇到了错误……"); } return 1; }
? (3)在MyAspect类中创建aroundAdvice方法,并写好注解
@Around(value = "execution(* aop..*.*(..)))") public int aroundAdvice(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); System.out.println("【AOP】before Advice,获取到insert方法传入的参数为:"+args[0]); Object result; try { result=pjp.proceed(args); System.out.println("【AOP】after Returning Advice,返回值为:"+result); }catch (Exception e){ System.out.println("【AOP】after Throwing Advice,错误信息为:"+e.getMessage()); } System.out.println("【AOP】after advice……不管异常不异常我都执行"); return 1; }
关注作者:JAVA高级程序员
专注分享:(高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等技术...)
欢迎转发,评论~~~每天Java一下,成为架构师!
相关推荐
- 微软Office Open XML中的数字签名漏洞
-
MicrosoftOffice是最广泛使用的办公文档应用程序之一。对于重要文件,如合同和发票,可以对其内容进行签名,以确保其真实性和完整性。自2019年以来,安全研究人员发现了针对PDF和ODF等其...
- Javaweb知识 day12 XML(javaweb中xml作用)
-
一、XML:1.1概念:ExtensibleMarkupLanguage可扩展标记语言*可扩展:标签都是自定义的。<user><student>1.2功能:...
- 易筋洗髓功——内外同修方可致远(易筋洗髓功口诀)
-
达摩祖师所传易筋、洗髓两经,一分为二,二实为一,无非以方便法门接引众生,而归于慈悲清净之心地。修炼《易筋经》是为强身健体,修炼《洗髓经》是为修心养性,此二者相辅相成,内外兼修,缺一不可。这是一套传统中...
- 道家洗髓功修炼要义,洗髓功如何做到丹田聚气?
-
不管是道家洗髓功,还是洗髓经,其修炼的关键点就在于得气、行气、聚气...那么,作为洗髓功修炼者,具体该怎么做呢?在实际修炼中,就洗髓功的修炼方法来讲,我们可以简单的归纳为修炼三部曲,其具体表现如下:一...
- 「清风聊练功」师门传我易筋经:聊聊我的学习经历和正身图感受
-
一个人的眼界认识,是随着是自身的知识积累和水平不断成长的。开篇为什么要说这么一句呢?是从我的学习经历上感受明显的这句话:一处不到一处迷。我们学传统武术,内功功法,也是从小白到明白一步步走的,走的越远,...
- 内功外练功介绍(练内功 外功)
-
这里介绍我练习的两套动功心得体会。是老道长的八部金刚功、长寿功和增演易筋洗髓经。八部金刚功外练奇经八脉,练出健康强壮的好身体还是可以的,长寿功也是内练功法。这部功法很好的预防效果。这个大家都认同的。说...
- 《增演易筋洗髓内功图说》17卷(1930年(清)周述官撰 1
-
少林空悟老师珍藏
- 国术典籍:《增演易筋洗髓内功图说》【2024年8月编校】
-
《增演易筋洗髓内功图说》系养生气功著作,全书共十八卷。清周述官编撰于光绪二十一年(1895年)。清光绪十九年(1893年),僧人静一空悟将少林功法传授于周述官,并将《增益易筋洗髓内功图说》十二卷(按,...
- 小说:自媒体小白的修道之路-洗髓(自媒体小白运营技巧)
-
谁应了谁的劫,谁又变成了谁的执念。当沧海遗忘了桑田,这世间又多了一个不回家的人!异域空间中,知生缓缓起身,目光扫了一下小帝后,又转身看向画板上的那朵白色蒲公英,自言道:“白瑛,这一世我们莫要再辜负了!...
- 这才是少林洗髓经真相:它是静功和导引术与八段锦暗合
-
不少朋友误解易筋经和洗髓经,将其简单归为强力呼吸的吐纳功以及为了提升房中的关窍功。事实上易筋经和洗髓经是两部功法:易筋经主要为炼体,包含以膜论为核心的十二月怕打筋膜法,以及辅助的呼吸、导引功法;洗髓经...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)