(十五)Spring之面向切面编程AOP
创始人
2024-02-17 05:15:04
0

文章目录

  • 基础环境
  • AOP介绍
  • AOP的七大术语
  • 切点表达式
  • Spring的AOP的使用
    • 环境准备
    • 基于AspectJ的AOP注解式开发
      • 通知类型
        • 前置通知@Before
        • 后置通知@AfterReturning
        • 环绕通知@Around
        • 异常通知@AfterThrowing
        • 最终通知@After
        • 关于JoinPoint
      • 切面的先后顺序
      • 通用切点表达式
      • 全注解式开发AOP
    • 基于XML配置方式的AOP(了解)

上一篇:(十四)Spring之回顾代理模式

IoC使软件组件松耦合。AOP让你能够捕捉系统中经常使用的功能,把它转化成组件。
AOP(Aspect Oriented Programming):面向切面编程,面向方面编程。(AOP是一种编程技术)
AOP是对OOP的补充延伸。
AOP底层使用的就是动态代理来实现的。
Spring的AOP使用的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。

基础环境

spring6里程碑版本的仓库
依赖:spring context依赖、junit依赖、log4j2依赖
log4j2.xml文件放到类路径下。

AOP介绍

一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务
这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。
如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:

  • 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处。
  • 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。

使用AOP可以很轻松的解决以上问题。
在这里插入图片描述
总结AOP:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。

AOP的优点:

  • 第一:代码复用性增强。
  • 第二:代码易维护。
  • 第三:使开发者更关注业务逻辑。

AOP的七大术语

  • 1.连接点 Joinpoint:描述的是位置
    在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。

  • 2.切点 Pointcut:本质上就是方法
    在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)

  • 3.通知 Advice:本质上就是增强代码

    通知又叫增强,就是具体你要织入的代码。
    通知包括:

    • 前置通知:切点(方法)之前
    • 后置通知:切点(方法)之后
    • 环绕通知:前置后置都有通知
    • 异常通知:catch里面
    • 最终通知:finally里面
  • 4.切面 Aspect
    切点 + 通知就是切面。

  • 5.织入 Weaving
    把通知应用到目标对象上的过程。

  • 6.代理对象 Proxy
    一个目标对象被织入通知后产生的新对象。

  • 7.目标对象 Target
    被织入通知的对象。

代码体现:

public class UserService{public void do1(){System.out.println("do 1");}public void do2(){System.out.println("do 2");}public void do3(){System.out.println("do 3");}public void do4(){System.out.println("do 4");}public void do5(){System.out.println("do 5");}// 核心业务方法public void service(){try {//连接点 Joinpoint// 对于do1()来说 前置通知do1();//切点 Pointcut  (1)前置通知和后置通知都有叫环绕通知    (2)这一整个叫做切面(切点+通知)// 对于do1()来说 后置通知//连接点 Joinpointdo2();//切点 Pointcut//连接点 Joinpointdo3();//切点 Pointcut//连接点 Joinpointdo5();//切点 Pointcut//连接点 Joinpoint}catch (Exception e){//连接点 Joinpoint   异常通知}finally {//连接点 Joinpoint  最终通知}}
}

切点表达式

切点表达式用来定义通知(Advice)往哪些方法上切入。
切入点表达式语法格式:

execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])

访问控制权限修饰符:

  • 可选项。
  • 没写,就是4个权限都包括。
  • 写public就表示只包括公开的方法。

返回值类型:

  • 必填项。
  • *表示返回值类型任意。

全限定类名:

  • 可选项。
  • 两个点“…”代表当前包以及子包下的所有类。
  • 省略时表示所有的类。

方法名:

  • 必填项。
  • *表示所有方法。
  • set*表示所有的set方法。

形式参数列表:

  • 必填项
  • () 表示没有参数的方法
  • (…) 参数类型和个数随意的方法
  • (*) 只有一个参数的方法
  • (*, String) 第一个参数类型随意,第二个参数是String的。

异常:

  • 可选项。
  • 省略时表示任意异常类型。

例如:
表示service包下所有的类中以delete开始的所有方法

execution(public * com.mall.service.*.delete*(..))

所有类的所有方法

execution(* *(..))

mall包下所有的类的所有的方法

execution(* com.mall..*(..))

Spring的AOP的使用

Spring对AOP的实现包括以下3种方式:

  • 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。(常用)
  • 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。(不常用)
  • 第三种方式:Spring框架自己实现的AOP,基于XML配置方式。

实际开发中,都是Spring+AspectJ来实现AOP。所以我们重点学习第一种和第二种方式。而实际开发基本使用注解方式,最重点的是注解方式的学习。
什么是AspectJ?(Eclipse组织的一个支持AOP的框架。AspectJ框架是独立于Spring框架之外的一个框架,Spring框架用了AspectJ)
AspectJ项目起源于帕洛阿尔托(Palo Alto)研究中心(缩写为PARC)。该中心由Xerox集团资助,Gregor Kiczales领导,从1997年开始致力于AspectJ的开发,1998年第一次发布给外部用户,2001年发布1.0 release。为了推动AspectJ技术和社团的发展,PARC在2003年3月正式将AspectJ项目移交给了Eclipse组织,因为AspectJ的发展和受关注程度大大超出了PARC的预期,他们已经无力继续维持它的发展。

环境准备

使用Spring+AspectJ的AOP除了还需要引入aop依赖和aspects依赖:
而如果使用Maven则只需引入context依赖就会自动关联spring-aop依赖,没有使用Maven则需要把aop的jar包引入项目。

    org.springframeworkspring-aspects6.0.0-M2

Spring配置文件中添加context命名空间和aop命名空间:



基于AspectJ的AOP注解式开发

首先需要创建spring.xml文件:

  • 1.引入context命名空间和aop命名空间
  • 2.添加组件扫描
  • 3.开启自动代理
    开启之后,Spring容器在扫描类的时候,会查看类上是否有@Aspect注解,如果有,则会自动给这个类生成代理对象。
    使用aop:aspectj-autoproxy开启自动代理
    • proxy-target-class属性:默认为false
      • true:表示强制使用CGLIB动态代理

      • false:表示,接口使用JDK动态代理,类使用CGLIB动态代理




创建目标类和目标方法,纳入spring管理:

@Service
public class UserService {//目标类public void login(){//目标方法System.out.println("正在登录。。。");}public void logout(){System.out.println("正在退出。。。");}
}

创建切面类:纳入spring管理,除此之外,需要加一个@Aspect注解,通知Spring框架这个类是一个切面类,到时候这个切面类会编写通知,添加切点表达式

@Component
@Aspect
public class LogAspect {//切面=切点+通知
}

通知类型

通知类型包括:

  • 前置通知:@Before 目标方法执行之前的通知
  • 后置通知:@AfterReturning 目标方法执行之后的通知
  • 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
  • 异常通知:@AfterThrowing 发生异常之后执行的通知
  • 最终通知:@After 放在finally语句块中的通知

前置通知@Before

@Before注解代表该通知为前置通知,注解里面需要写切点表达式
在LogAspect 切面类添加前置通知:

	//前置通知@Before("execution(* com.aop.annotation.service..*(..))")public void beforeAdvice(){System.out.println("前置通知。。。增强代码。。。");}

测试程序:

    @Testpublic void testAOP(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");UserService userService = applicationContext.getBean("userService", UserService.class);userService.login();System.out.println("---------------------------------");userService.logout();}

请添加图片描述

后置通知@AfterReturning

@AfterReturning代表该通知为后置通知,注解里面需要写切点表达式
在LogAspect 切面类添加后置通知:

//后置通知@AfterReturning("execution(* com.aop.annotation.service..*(..))")public void afterReturningAdvice(){System.out.println("后置通知。。。增强代码。。。");}

再次运行测试程序:
请添加图片描述

环绕通知@Around

@Around代表该通知为环绕通知,注解里面需要写切点表达式
环绕通知会收到一个ProceedingJoinPoint参数,用来执行目标方法。
在LogAspect 切面类添加环绕通知:

    @Around("execution(* com.aop.annotation.service..*(..))")public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {//前面代码System.out.println("前环绕。。。");//执行目标joinPoint.proceed();//后面代码System.out.println("后环绕。。。");}

再次运行测试程序:
可见环绕是最大的通知,在前置之前,在后置之后。
请添加图片描述

异常通知@AfterThrowing

@AfterThrowing代表该通知为异常通知,注解里面需要写切点表达式
为了更好演示,重新创建一个目标类和目标方法:该类抛一个异常

@Service
public class OrderService {public void generate(){System.out.println("正在生成订单。。。");throw new RuntimeException("异常");}
}

在LogAspect 切面类添加异常通知:

	@AfterThrowing("execution(* com.aop.annotation.service..*(..))")public void afterThrowingAdvice(){System.out.println("异常通知。。。");}

修改测试程序:

    @Testpublic void testAOP(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");UserService userService = applicationContext.getBean("userService", UserService.class);userService.login();System.out.println("---------------------------------");userService.logout();System.out.println("---------------------------------");OrderService orderService = applicationContext.getBean("orderService", OrderService.class);orderService.generate();}

运行程序:发现发生异常后,后环绕和后置通知已经没了,这是因为执行方法的时候抛异常之后不会再往下执行通知
请添加图片描述

最终通知@After

@After代表该通知为最终通知,注解里面需要写切点表达式
在LogAspect 切面类添加最终通知:

	@After("execution(* com.aop.annotation.service..*(..))")public void afterAdvice(){System.out.println("最终通知。。。");}

再次运行测试程序:
可见最终通知一定会执行,不管有没有异常,最终通知,在后置之后,后环绕之前,由此可见环绕通知确实是最大的通知
请添加图片描述

关于JoinPoint

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.。
JoinPoint是在Spring调用的切面方法时候自动传过来的,我们可以直接使用

方法名功能
Signature getSignature();获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs();获取传入目标方法的参数对象
Object getTarget();获取被代理的对象
Object getThis();获取代理对象

ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中, 添加了两个方法.

Object proceed() throws Throwable //执行目标方法 
Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法 

切面的先后顺序

我们知道,业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高。

在LogAspect切面类添加注解:

@Order(2)

把OrderService目标类的异常注释掉:

@Service
public class OrderService {public void generate(){System.out.println("正在生成订单。。。");//throw new RuntimeException("异常");}
}

再定义一个切面类:优先级设置为1,使用一下JoinPoint

/*** 安全切面*/
@Component
@Aspect
@Order(1)//数字越小优先级越高
public class SecurityAspect {@Before("execution(* com.aop.annotation.service..*(..))")public void beforeAdvice(JoinPoint joinPoint){System.out.println("安全的前置通知。。。");Signature signature = joinPoint.getSignature();//获取目标方法的签名System.out.println(signature.getName());}
}

测试程序:

    @Testpublic void test(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");OrderService orderService = applicationContext.getBean("orderService", OrderService.class);orderService.generate();}

请添加图片描述
如果把优先级换一下,再次运行:
请添加图片描述

通用切点表达式

回顾之前的切点表达式,发现切点表达式是重复的
这样的缺点是:

  • 第一:切点表达式重复写了多次,没有得到复用。
  • 第二:如果要修改切点表达式,需要修改多处,难维护。

怎么解决,可以使用通用切点表达式:@Pointcut注解将切点表达式单独的定义出来,在需要的位置引入即可。
在LogAspect 切面类添加一个方法使用@Pointcut注解:
方法只是个标记,方法名随意,方法体不需要写任何代码,只需要使用一个@Pointcut注解标注

    //通用切点表达式@Pointcut(value = "execution(* com.aop.annotation.service..*(..))")public void execution_use(){}

怎么引用,只需要在通知注解中写该方法即可,例如:

    //@After("execution(* com.aop.annotation.service..*(..))")@After("execution_use()")

这个通用表达式配置,只要一配置,任何切面类都可以使用,例如刚才的安全切面类,可以这样写。

/*** 安全切面*/
@Component
@Aspect
@Order(3)//数字越小优先级越高
public class SecurityAspect {//@Before("execution(* com.aop.annotation.service..*(..))")@Before("com.aop.annotation.service.LogAspect.execution_use()")public void beforeAdvice(JoinPoint joinPoint){System.out.println("安全的前置通知。。。");/*这个JoinPoint是在Spring调用的这个方法时候自动传过来的我们可以直接使用*/Signature signature = joinPoint.getSignature();//获取目标方法的签名System.out.println(signature.getName());}
}

再次运行test的测试程序:
请添加图片描述

全注解式开发AOP

就是编写一个类,在这个类上面使用大量注解来代替spring的配置文件,spring配置文件消失了,如下:

@Configuration //代替Spring配置文件
@ComponentScan("com.aop.annotation.service")//代替注解扫描
@EnableAspectJAutoProxy(proxyTargetClass = true)//代替开启aspectj自动代理,
public class SpringConfig {
}

测试程序:

    @Testpublic void testNoXML(){ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);OrderService orderService = applicationContext.getBean("orderService", OrderService.class);orderService.generate();}

请添加图片描述

基于XML配置方式的AOP(了解)

第一步:编写目标类

/*** 目标类*/
public class UserService {public void login(){System.out.println("正在登录。。。");}
}

第二步:编写切面类,并且编写通知

/*** 切面*/
public class TimerAspect {//通知public void arountAdvice(ProceedingJoinPoint joinPoint) throws Throwable {//前环绕long begin = System.currentTimeMillis();joinPoint.proceed();//执行目标//后环绕long end = System.currentTimeMillis();System.out.println("耗时:"+ (end-begin));}
}

第三步:编写spring配置文件




测试程序:

    @Testpublic void testXML(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");UserService userService = applicationContext.getBean("userService", UserService.class);userService.login();}

请添加图片描述

相关内容

热门资讯

从创始人驱动到制度驱动,“中国... 导语:随着一代传奇人物陈景河退入幕后,紫金矿业这一庞大的矿业帝国能否续写神话? 李平/作者砺石商业...
“两高”联合发布第三批行政公益... 中新网12月22日电 据“最高人民检察院”微博消息,为进一步发挥典型案例指导引领作用,12月22日,...
福田区举行年度律师大会 《粤港... 深圳新闻网2025年12月22日讯(深圳特区报记者 张玮玮 林清容 通讯员 蔡梓帆 张圳伟)12月1...
浙江天台男子行凶致一死一伤 起... 封面新闻记者 张奕丹 今年5月,浙江省天台县发生一起一死一伤的刑事案件。12月22日,被害人的家属陈...
闪电律解·每周精选问答丨遗产继... 齐鲁网·闪电新闻12月22日讯 随着网友法律意识的提升,越来越多的人选择通过法律渠道解决自己生活工作...
以和为贵化纠纷 情理兼顾续亲情 近日,桦甸市人民法院成功调解一起亲友间民间借贷纠纷,既化解了债务矛盾,又维系了双方亲属情谊,以司法温...
西北联大旧址濒临灭失公益诉讼促... 行政公益诉讼抢救濒危抗战文物。 12月22日,最高人民法院、最高人民检察院联合发布第三批行政公益诉讼...
原创 央... #以案件说法# 近年来,随着消费升级和二手商品市场的崛起,许多商家开始通过直播平台进行二手衣物的回收...
点赞!习水纠纷调解只进一扇门 日前,习水县人民调解委员会接到一起劳资纠纷求助。工作人员第一时间详细核查当事人诉求及案件细节,随即联...