Spring 学习笔记《切面编程 AOP 之 暗黑肉夹馍诞生》
发布日期:2021-06-30 14:57:34 浏览次数:2 分类:技术文章

本文共 13475 字,大约阅读时间需要 44 分钟。

Spring AOP 暗黑肉夹馍

简单理解

AOP 和 代理模式

  • 要说 AOP 得先说代理。代理就像钢铁侠的战衣,它把托尼·斯塔克装在里面,赋予了托尼很多非凡的能力。之所以这样是为了避免埃迪毒液那样共生,它们耦合太强了,强行拆开宿主很可能会死的(蜘蛛侠这种原本就是超级英雄的除外)
  • AOP就是代理模式的一种具体实现。如果我们自己实现代理模式,则需要写一个代理对象让它继承目标对象或实现与目标一样的接口(这是为了让我们可以偷梁换柱,用代理对象神不知鬼不觉的替换原目标对象)。AOP则是帮我实现了动态创建代理对象这一过程,我们要做的就是配置。告诉它我们要在什么位置【切点】做什么【通知】。并且AOP底层使用了IoC,这样偷梁换柱的工作也不用我们动手了。

低配版名词解释

马仔甲:报告老大,最近王麻子的势力愈来愈大,夜总会、赌场都开到咱们底盘来了。

马老大:太不给子了,咱们去了它。
马仔乙:王麻子场子那么多,都要砸了吗?
马老大:咱是受过教育的,凡事讲个策略,知道啥叫定点切除不?
马仔甲:请老大明示。
马老大:通知兄弟们,王麻子就在大麻子夜总会。行动目标绑人为主,顺手打砸。

  1. Joinpoint【连接点】:
    王麻子的所有生意都是打击目标。这就是连接点(Spring AOP只支持你在方法执行前后和抛异常时搞事情)
  2. Pointcut 【切点】:
    但是我们的行动目标是大麻子夜总会,因为我们想抓王麻子。这就是切点(切点就是被我们盯上的连接点,配置里写表达式说明 or 用注解声明)
  3. Advice【通知】:
    行动前准备工作是什么,行动后收尾工作事什么,如果行动出翔怎么办。这就是通知内容。(如果大麻子夜总会是个方法,在添加AOP的前后区别就在于:王麻子被我们绑走了)
  4. Aspect【切面】:
    切点通知这两个概念加在一起就组成了切面。从连接点中挑选出切点然后定义通知的工作就是编织切面

举例说明

下面的例子,你原本通过 food.stuffing() 能得到一块剁碎的【卤肉】,但使用了 AOP 后你通过 food.stuffing() 获得的将是一个加了面饼辣子面葱花【肉夹馍】

当然AOP最常见的应用如【日志】【事务】【权限管理】等。在不侵入的情况下,对原有功能进行扩展。

两种配置方法:

  • aop:advisor
    通过接口来区分各个通知方法,所以需要实现相应接口
  • aop:aspect
    通过子节点 aop:before、aop:after、aop:around、after-throwing 来区分通知方法,
    只要将对应的方法名指定给他们就行了。

一、Schema 配置方式 <aop:advisor />

配置AOP【烹饪流程】

/Spring_AOP/src/applicationContext.xml

先上我们要代理的目标【卤肉】

/Spring_AOP/src/com/jerry/pojo/Food.java

package com.jerry.pojo;public class Food {
public String stuffing() {
System.out.println("加上:一大块卤肉剁碎"); return "加上:一大块卤肉剁碎"; } public String stuffing(String condiment) {
System.out.println("加上:【一大块卤肉】和 【" + condiment + "】一起剁碎"); return "加上:【一大块卤肉】和 【" + condiment + "】一起剁碎"; }}

再祭出我们的通知【调料 + 面饼】

/Spring_AOP/src/com/jerry/advice/MyAdvice.java

package com.jerry.advice;import java.lang.reflect.Method;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.springframework.aop.AfterReturningAdvice;import org.springframework.aop.MethodBeforeAdvice;import org.springframework.aop.ThrowsAdvice;public class MyAdvice implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor, ThrowsAdvice{
/** * 前置通知 * Method arg0 : 原始方法对象 * Object[] arg1 : 实参列表 (代理方法接收的实参传到这里) * Object arg2 : 真实对象(就是这个实例调用的真实方法) */ @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("前置通知:底下铺一张面饼"); } /** * 后置通知 * Object arg0 :真实方法返回值 * Method arg1 :真实方法对象 * Object[] arg2:代理方法接收的实参,使用数组封装传递 * Object arg3 :真实对象(就是这个实例调用的真实方法) */ @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("后置通知:最后顶上盖一张面饼,整个卷好!"); } /** * 环绕通知 * MethodInvocation invocation 封装了: * invocation.getThis() // 调用对象 * invocation.proceed() // 放行,调用链的下一环 * invocation.getMethod() // 获取真实方法对象 * invocation.getArguments()// 获取参数列表 */ @Override public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕通知:面饼上抹一层辣子面"); Object obj = invocation.proceed(); System.out.println("环绕通知:顶上撒一层葱花"); return obj; } /** * 异常通知 * @param ex * @throws Throwable */ public void afterThrowing(Exception ex) throws Throwable{
System.out.println("异常通知:糟糕烤糊了。。。这是意外。。。"); }}

测试代码(伸手要馍)

/Spring_AOP/src/com/jerry/test/Test.java

package com.jerry.test;import org.springframework.context.ApplicationContext;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.jerry.pojo.Food;public class Test {
public static void main(String[] args) {
// ======================== Schema 配置方式 ======================== ApplicationContext ctxSchema = new ClassPathXmlApplicationContext("applicationContext_Schema.xml"); Food foodSchema = (Food)ctxSchema.getBean("food"); foodSchema.stuffing("Schema"); System.out.println("【Schema 配置方式】暗黑肉加馍到手,开吃 666 !!!!!!"); // 不加这个总说 ctx没有关闭,资源泄漏。 ((ConfigurableApplicationContext)ctxSchema).close(); }}

暗黑肉加馍到手,开吃 666 !!!!!!

环绕通知:底下铺一张面饼前置通知:面饼上抹一层辣子面加上:【一大块卤肉】和 【板蓝根】一起剁碎环绕通知:最后顶上盖一张面饼,整个卷好!后置通知:顶上撒一层葱花暗黑肉加馍到手,开吃 666 !!!!!!

二、AspectJ 配置方式 <aop:aspect />

配置一下

/Spring_AOP/src/applicationContext.xml

通知类

这次只能写在一起,仔细看看上面配置文件就能明白了

/Spring_AOP/src/com/jerry/advice/MyAspectJAdvice.java

package com.jerry.advice;import org.aspectj.lang.ProceedingJoinPoint;public class MyAspectJAdvice{
public void before() throws Throwable {
System.out.println("前置通知:底下铺一张面饼"); } public void after() throws Throwable {
System.out.println("后置通知:最后顶上盖一张面饼,整个卷好!"); } public Object round(ProceedingJoinPoint proceeding) throws Throwable {
System.out.println("环绕通知:面饼上抹一层辣子面"); Object obj = proceeding.proceed(); System.out.println("环绕通知:顶上撒一层葱花"); return obj; } public void exception(){
System.out.println("异常通知:糟糕烤糊了。。。这是意外。。。"); }}

测试代码(伸手要馍)

/Spring_AOP/src/com/jerry/test/Test.java

package com.jerry.test;import org.springframework.context.ApplicationContext;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.jerry.pojo.Food;public class Test {
public static void main(String[] args) {
// ======================== AspectJ 配置方式 ======================== ApplicationContext ctxAspectJ = new ClassPathXmlApplicationContext("applicationContext_Schema.xml"); Food foodAspectJ = (Food)ctxAspectJ.getBean("food"); foodAspectJ.stuffing("AspectJ"); System.out.println("【AspectJ 配置方式】暗黑肉加馍到手,开吃 666 !!!!!!"); // 不加这个总说 ctx没有关闭,资源泄漏。 ((ConfigurableApplicationContext)ctxAspectJ).close();}
前置通知:底下铺一张面饼环绕通知:面饼上抹一层辣子面加上:【一大块卤肉】和 【板蓝根】一起剁碎环绕通知:顶上撒一层葱花后置通知:最后顶上盖一张面饼,整个卷好!暗黑肉加馍到手,开吃 666 !!!!!!

三种注解方式:

这里要注意,前面我说了非入侵式的扩展。下面我演示三种添加注释的方式

1、直接在目标方法上添加@Pointcut("...")定义切点的方式,明显有点入侵了。
2、在切面通知类里单独定义一个方法,用来定义切点更理想。
3、和2差不多,只是不用统一的pointcut方法,而是在通知方法注解里直接定义切点

@Pointcut注解

在开始之前先了解一下@Pointcut的能力和用法。

能力:当然就是用来定义切点
用法:下面这个例子定义了一个名为anyOldTransfer的切入点,它将匹配任何名为transfer的方法的执行。

@Pointcut("execution(* transfer(..))")// AspectJ5 切点表达式private void anyOldTransfer() {
}// 切点签名
  • @Pointcut注解的value是一个:AspectJ 5切点表达式。完整的语法介绍请看

Spring AOP支持以下AspectJ切入点指示符(PCD),用于切入点表达式。(完整的 AspectJ切点语言支持更多的扩展类型,SpringAOP可能会在以后的版本中进行支持)

切点类型 说明
execution 用于匹配方法执行连接点,这是您在使用Spring AOP时将使用的主要切入点指示符
within 限制匹配特定类型中的连接点(使用Spring AOP时,简单地执行匹配类型中声明的方法)
this 限制匹配到连接点(使用Spring AOP时方法的执行),其中bean引用(Spring AOP代理)是给定类型的一个实例
target 限制匹配到连接点(使用Spring AOP时方法的执行),其中目标对象(被代理的应用程序对象)是给定类型的实例
args 限制匹配到连接点(使用Spring AOP时方法的执行),其中参数是给定类型的实例
@target 限制匹配到连接点(使用Spring AOP时方法的执行),其中执行对象的类具有给定类型的注解
@args 限制匹配到连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注解
@within 匹配具有给定注解的类中的连接点。也可以直接用在连接点上。
@annotation 匹配具有给定注解的连接点(在Spring AOP中执行的方法)

1、直接在目标方法上添加注解

目标对象 (注意方法没有参数)

/Spring_AOP/src/com/jerry/pojo/Food.java

package com.jerry.pojo;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Component // 为此类创建 Beanpublic class Food {
@Pointcut("execution(* com.jerry.pojo.Food.stuffing())")// 定义切点 public String stuffing() {
System.out.println("加上:一大块卤肉剁碎"); return "加上:一大块卤肉剁碎"; }}

通知方法添加注解

/Spring_AOP/src/com/jerry/advice/MyAspectJAdvice.java

package com.jerry.advice;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.After;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.springframework.stereotype.Component;@Component // 为此类创建 Bean@Aspect // 定义切面。声明当前是通知类public class MyAspectJAdvice{
@Before("com.jerry.pojo.Food.stuffing()") // 定义前置通知 public void before() throws Throwable {
System.out.println("前置通知:底下铺一张面饼"); } @After("com.jerry.pojo.Food.stuffing()") // 定义后置通知 public void after() throws Throwable {
System.out.println("后置通知:最后顶上盖一张面饼,整个卷好!"); } @Around("com.jerry.pojo.Food.stuffing()") // 定义环绕通知 public Object round(ProceedingJoinPoint proceeding) throws Throwable {
System.out.println("环绕通知:面饼上抹一层辣子面"); Object obj = proceeding.proceed(); System.out.println("环绕通知:顶上撒一层葱花"); return obj; } @AfterThrowing("com.jerry.pojo.Food.stuffing()") // 定义异常通知 public void exception(){
System.out.println("异常通知:糟糕烤糊了。。。这是意外。。。"); } }

2、单独定义一个方法添加注解

目标对象

这里和方法一不同的地方就只是去掉了一句 @Pointcut("execution(* com.jerry.pojo.Food.stuffing())") 注解

/Spring_AOP/src/com/jerry/pojo/Food.java

package com.jerry.pojo;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;@Component // 为此类创建 Beanpublic class Food {
public String stuffing() {
System.out.println("加上:一大块卤肉剁碎"); return "加上:一大块卤肉剁碎"; }}

通知方法添加注解

  1. 专门添加了一个方法pointcut()并在其上添加注解@Pointcut("execution(* com.jerry.pojo.*.*(..))")定义切点
  2. 把后面所有通知的注解参数换成pointcut()
  3. 额外的一点,这此定义切点的表达式换成了"execution(* com.jerry.pojo.*.*(..))"(匹配pojo包下的所有类所有方法所有参数列表

/Spring_AOP/src/com/jerry/advice/MyAspectJAdvice.java

package com.jerry.advice;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.After;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.springframework.stereotype.Component;@Component // 为此类创建 Bean@Aspect // 定义切面。声明当前是通知类public class MyAspectJAdvice{
@Pointcut("execution(* com.jerry.pojo.*.*(..))")// 定义切点 public void pointcut(){
} @Before("pointcut()") public void before() throws Throwable {
System.out.println("底下铺一张面饼"); } @After("pointcut()") public void after() throws Throwable {
System.out.println("最后顶上盖一张面饼,整个卷好!"); } @Around("pointcut()") public Object round(ProceedingJoinPoint proceeding) throws Throwable {
System.out.println("面饼上抹一层辣子面"); Object obj = proceeding.proceed(); System.out.println("顶上撒一层葱花"); return obj; } @AfterThrowing("pointcut()") public void exception(){
System.out.println("糟糕烤糊了。。。这是意外。。。"); }}

3、在通知方法注解里直接定义切点

为方便理解,我直接在方法二的代码上修改。如果有需要,你完全可以为不同的通知分别各自定义切点

@Component@Aspectpublic class MyAspectJAdvice{
// @Pointcut("execution(* com.jerry.pojo.*.*(..))")// 定义切点// public void pointcut(){
// } // @Before("pointcut()") @Before("execution(* com.jerry.pojo.*.*(..))") public void before() throws Throwable {
System.out.println("底下铺一张面饼"); }// @After("pointcut()") @After("execution(* com.jerry.pojo.*.*(..))") public void after() throws Throwable {
System.out.println("最后顶上盖一张面饼,整个卷好!"); }// @Around("pointcut()") @Around("execution(* com.jerry.pojo.*.*(..))") public Object round(ProceedingJoinPoint proceeding) throws Throwable {
System.out.println("面饼上抹一层辣子面"); Object obj = proceeding.proceed(); System.out.println("顶上撒一层葱花"); return obj; }// @AfterThrowing("pointcut()") @AfterThrowing("execution(* com.jerry.pojo.*.*(..))") public void exception(){
System.out.println("糟糕烤糊了。。。这是意外。。。"); }}

配置文件(共用部分)

/Spring_AOP/src/applicationContext.xml

测试代码(伸手要馍)

/Spring_AOP/src/com/jerry/test/Test.java

package com.jerry.test;import org.springframework.context.ApplicationContext;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.jerry.pojo.Food;public class Test {
public static void main(String[] args) {
// ======================== Annotation 配置方式 ======================== ApplicationContext ctxAnnotation = new ClassPathXmlApplicationContext("applicationContext_Annotation.xml"); Food foodAnnotation = (Food)ctxAnnotation.getBean("food"); System.out.println("-----------------------【Annotation 配置方式】我们配置的方法,这里调用有参方法,所以Aop没有工作 -----------------------"); foodAnnotation.stuffing("Annotation"); System.out.println("-----------------------【Annotation 配置方式】调用无参方法,AOP正常工作 -----------------------"); foodAnnotation.stuffing(); System.out.println("【Annotation 配置方式】暗黑肉加馍到手,开吃 666 !!!!!!"); // 不加这个总说 ctx没有关闭,资源泄漏。 ((ConfigurableApplicationContext)ctxAnnotation).close(); }}
环绕通知:面饼上抹一层辣子面前置通知:底下铺一张面饼切点:一大块卤肉剁碎环绕通知:顶上撒一层葱花后置通知:最后顶上盖一张面饼,整个卷好!暗黑肉加馍到手,开吃 666 !!!!!!

传递参数

这里就已注解方式举例。如下@Before@Around中可以获得目标方法的参数值了。

注意:beforearound现在只对有参的目标方法起作用了。

@Component@Aspectpublic class MyAspectJAdvice{
@Before(value="execution(* com.jerry.pojo.*.*(..)) && args(stuffing)", argNames = "stuffing") public void before(String stuffing) throws Throwable {
"老坛酸菜".equals(stuffing); System.out.println("底下铺一张面饼"); } @After("execution(* com.jerry.pojo.*.*(..))") public void after() throws Throwable {
System.out.println("最后顶上盖一张面饼,整个卷好!"); } @Around(value="execution(* com.jerry.pojo.*.*(..)) && args(stuffing)", argNames = "stuffing") public Object round(ProceedingJoinPoint proceeding, String stuffing) throws Throwable {
"老坛酸菜".equals(stuffing); System.out.println("面饼上抹一层辣子面"); Object obj = proceeding.proceed(); System.out.println("顶上撒一层葱花"); return obj; } @AfterThrowing("execution(* com.jerry.pojo.*.*(..))") public void exception(){
System.out.println("糟糕烤糊了。。。这是意外。。。"); }}

源码:

参考资料

转载地址:https://jerryjin.blog.csdn.net/article/details/86612501 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Android 学习笔记 -《基础概念科普》
下一篇:Spring Boot 学习笔记《使用 Spring Initializer 创建项目》

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月09日 17时18分50秒