AOP切面编程

Wesley13
• 阅读 433

AOP切面编程

简介

如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用。

面向切面编程,通过预编译和动态代理实现程序功能的统一维护的一种技术,主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等

AOP 的目的

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性

核心概念:

实际使用SpringAOP之前,了解他的概念是必不可少的一个过程,

SpringAOP主要围绕以下概念展开:

**切面[Aspect]**:一个关注点的模块化,这个关注点可能会横切多个对象

**连接点[Joinpoint]**:程序执行过程中某个特定的连接点

**通知[Advice]**:在切面的某个特的连接点上执行的动作

**切入点[Pointcut]**:匹配连接的断言,在Aop中通知和一个切入点表达式关联

**引入[Intruduction]**:在不修改类代码的前提下,为类添加新的方法和属性

**目标对象[Target Object]**:被一个或者多个切面所通知的对象

**Aop代理[AOP Proxy]**:AOP框架创建的对象,用来实现切面契约(aspect contract)(包括方法执行等)

**织入[Weaving]**:把切面连接到其他的应用程序类型或者对象上,并创建一个被通知的对象,氛围:编译时织入,类加载时织入,执行时织入

通知类型Advice:

**前置通知[before advice]**在某个连接点(jion point)之前执行的通知,但不能阻止连接点前的执行(除非抛出一个异常)

*正常返回后***通知[after returning advice]**在某个连接点(jion point)正常执行完后执行通知

抛出异常通知[after throwing advice] 在方法异常退出时执行的通知

**返回通知[****after(finally) advice]**在方法抛出异常退出时候的执行通知(不管正常返回还是异常退出)

**环绕通知[around advice]**包围一个连接点(jion point)的通知

切入点表达式

例如定义切入点表达式

  1. execution(* com.sample.service.impl..*.*(..))

execution()是最常用的切点函数,其语法如下所示:

 整个表达式可以分为五个部分:

1、execution(): 表达式主体。

2、第一个 * 号:表示返回类型,* 号表示所有的类型。

3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。

4、第二个 * 号:表示类名,* 号表示所有的类。

5、*(..):最后这个星号表示方法名,* 号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

  1. execution(public * *(..)) //切入点为执行所有的public方式时

  2. execution(* set*(..)) //切入点执行所有的set开始的方法时

  3. execution(* com.xyz.service.Account.*(..)) //切入点执行Account类的所有方法时

  4. execution(* com.xyz.service.*.*(..)) //切入点执行com.xyz.service包下的所有方法时

  5. execution(* com.xyz.service..*.*(..)) //切入点执行com.xyz.service包以及其子包的所有的方法时

上边这种方式aspectj和springaop通用的,其他方式可以自己查找资料

实例讲解

为了更好的说明 AOP 的概念,我们来举一个实际中的例子来说明:

AOP切面编程

在上面的例子中,包租婆的核心业务就是签合同,收房租,那么这就够了,灰色框起来的部分都是重复且边缘的事,交给中介商就好了,这就是 AOP 的一个思想:让关注点代码与业务代码分离!

实际的代码

1.业务主体类,就是现在对应的包猪婆的业务逻辑。

  1. package pojo;

  2. import org.springframework.stereotype.Component;

  3. @Component("landlord")

  4. public class Landlord {

  5. public void service() {

  6. // 仅仅只是实现了核心的业务功能

  7. System.out.println("签合同");

  8. System.out.println("收房租");

  9. }

  10. }

2.Broker 主要是上面所对应的中间商业务处理类,它任务就是带租客看房以及谈价格,其中**@Before和@After是在包猪婆业务的前后分别做好相应的售前和售后的工作(其实这里可以用一个环绕通知**来弄,看后面的代码)。

  1. package aspect;

  2. import org.aspectj.lang.annotation.After;

  3. import org.aspectj.lang.annotation.Aspect;

  4. import org.aspectj.lang.annotation.Before;

  5. import org.springframework.stereotype.Component;

  6. @Component

  7. @Aspect

  8. class Broker {

  9. @Before("execution(* pojo.Landlord.service())")

  10. public void before(){

  11. System.out.println("带租客看房");

  12. System.out.println("谈价格");

  13. }

  14. @After("execution(* pojo.Landlord.service())")

  15. public void after(){

  16. System.out.println("交钥匙");

  17. }

  18. }

3.在 applicationContext.xml 中配置自动注入,并告诉 Spring IoC 容器去哪里扫描这两个 Bean:

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <beans xmlns="http://www.springframework.org/schema/beans"

  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  4. xmlns:context="http://www.springframework.org/schema/context"

  5. xmlns:aop="http://www.springframework.org/schema/aop"

  6. xsi:schemaLocation="http://www.springframework.org/schema/beans

  7. http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

  8. <context:component-scan base-package="aspect" />

  9. <context:component-scan base-package="pojo" />

  10. <aop:aspectj-autoproxy/>

  11. </beans>

4.测试代码,运行包猪婆的主体服务类,可以看到日志会将中间商以及包猪婆的所有工作都打印了出来组成了一个完成的租房流程。

  1. package test;

  2. import org.springframework.context.ApplicationContext;

  3. import org.springframework.context.support.ClassPathXmlApplicationContext;

  4. import pojo.Landlord;

  5. public class TestSpring {

  6. public static void main(String[] args) {

  7. ApplicationContext context =

  8. new ClassPathXmlApplicationContext("applicationContext.xml");

  9. Landlord landlord = (Landlord) context.getBean("landlord", Landlord.class);

  10. landlord.service();

  11. }

  12. }

5.执行看到效果:

AOP切面编程

这个例子使用了一些注解,现在看不懂没有关系,但我们可以从上面可以看到我们在 Landlord 的 service() 方法中仅仅实现了核心的业务代码,其余的关注点功能是根据我们设置的切面自动补全的。

使用注解

其实跟上面的实际代码是一样,不过这里有点不同的话,你可以直接用@ComponentScan注解能扫描指定路径下的标识了Spring Bean注解(@Component或者是@Component参与合成的注解。

下面就再次啰嗦一下分点说明如何使用注解实现AOP

1、连接点

Spring 是方法级别的 AOP 框架,我们主要也是以某个类额某个方法作为连接点,另一种说法就是:选择哪一个类的哪一方法用以增强功能。

  1. ....

  2. public void service() {

  3. // 仅仅只是实现了核心的业务功能

  4. System.out.println("签合同");

  5. System.out.println("收房租");

  6. }

  7. ....

我们在这里就选择上述 Landlord 类中的 service() 方法作为连接点。

2、创建切面

选择好了连接点就可以创建切面了,我们可以把切面理解为一个拦截器,当程序运行到连接点的时候,被拦截下来,在开头加入了初始化的方法,在结尾也加入了销毁的方法而已,在 Spring 中只要使用 @Aspect 注解一个类,那么 Spring IoC 容器就会认为这是一个切面了:

  1. package aspect;

  2. import org.aspectj.lang.annotation.After;

  3. import org.aspectj.lang.annotation.Aspect;

  4. import org.aspectj.lang.annotation.Before;

  5. import org.springframework.stereotype.Component;

  6. @Component

  7. @Aspect

  8. class Broker {

  9. @Before("execution(* pojo.Landlord.service())")

  10. public void before(){

  11. System.out.println("带租客看房");

  12. System.out.println("谈价格");

  13. }

  14. @After("execution(* pojo.Landlord.service())")

  15. public void after(){

  16. System.out.println("交钥匙");

  17. }

  18. }

注意:被定义为切面的类仍然是一个 Bean ,需要 @Component 注解标注,注入到Spring容器中。

代码部分中在方法上面的注解看名字也能猜出个大概,下面来列举一下 Spring 中的 AspectJ 注解:

注解

说明

@Before

前置通知,在连接点方法前调用

@Around

环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法,后面会讲

@After

后置通知,在连接点方法后调用

@AfterReturning

返回通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常

@AfterThrowing

异常通知,当连接点方法异常时调用

3、定义切点

在上面的注解中定义了 execution 的正则表达式,Spring 通过这个正则表达式判断具体要拦截的是哪一个类的哪一个方法:

  1. execution(* pojo.Landlord.service())

依次对这个表达式作出分析:

execution:代表执行方法的时候会触发 * :代表任意返回类型的方法 pojo.Landlord:代表类的全限定名 service():被拦截的方法名称

通过上面的表达式,Spring 就会知道应该拦截 pojo.Lnadlord 类下的 service() 方法。上面的演示类还好,如果多出都需要写这样的表达式难免会有些复杂,我们可以通过使用 @Pointcut 注解来定义一个切点来避免这样的麻烦:

  1. package aspect;

  2. import org.aspectj.lang.annotation.After;

  3. import org.aspectj.lang.annotation.Aspect;

  4. import org.aspectj.lang.annotation.Before;

  5. import org.aspectj.lang.annotation.Pointcut;

  6. import org.springframework.stereotype.Component;

  7. @Component

  8. @Aspect

  9. class Broker {

  10. @Pointcut("execution(* pojo.Landlord.service())")

  11. public void lService() {

  12. }

  13. @Before("lService()")

  14. public void before() {

  15. System.out.println("带租客看房");

  16. System.out.println("谈价格");

  17. }

  18. @After("lService()")

  19. public void after() {

  20. System.out.println("交钥匙");

  21. }

  22. }

4、环绕通知

我们来探讨一下环绕通知,这是 Spring AOP 中最强大的通知,因为它集成了前置通知和后置通知,它保留了连接点原有的方法的功能,所以它及强大又灵活,让我们来看看:

  1. package aspect;

  2. import org.aspectj.lang.ProceedingJoinPoint;

  3. import org.aspectj.lang.annotation.Around;

  4. import org.aspectj.lang.annotation.Aspect;

  5. import org.springframework.stereotype.Component;

  6. @Component

  7. @Aspect

  8. class Broker {

  9. // 注释掉之前的 @Before 和 @After 注解以及对应的方法

  10. // @Before("execution(* pojo.Landlord.service())")

  11. // public void before() {

  12. // System.out.println("带租客看房");

  13. // System.out.println("谈价格");

  14. // }

  15. //

  16. // @After("execution(* pojo.Landlord.service())")

  17. // public void after() {

  18. // System.out.println("交钥匙");

  19. // }

  20. // 使用 @Around 注解来同时完成前置和后置通知

  21. @Around("execution(* pojo.Landlord.service())")

  22. public void around(ProceedingJoinPoint joinPoint) {

  23. System.out.println("带租客看房");

  24. System.out.println("谈价格");

  25. try {

  26. joinPoint.proceed();

  27. } catch (Throwable throwable) {

  28. throwable.printStackTrace();

  29. }

  30. System.out.println("交钥匙");

  31. }

  32. }

运行测试代码,结果仍然正确:

AOP切面编程

使用 XML 配置

注解是很强大的东西,但基于 XML 的开发我们仍然需要了解,我们先来了解一下 AOP 中可以配置的元素:

AOP 配置元素

用途

备注

aop:advisor

定义 AOP 的通知其

一种很古老的方式,很很少使用

aop:aspect

定义一个切面

——

aop:before

定义前置通知

——

aop:after

定义后置通知

——

aop:around

定义环绕通知

——

aop:after-returning

定义返回通知

——

aop:after-throwing

定义异常通知

——

aop:config

顶层的 AOP 配置元素

AOP 的配置是以它为开始的

aop:declare-parents

给通知引入新的额外接口,增强功能

——

aop:pointcut

定义切点

——

有了之前通过注解来编写的经验,并且有了上面的表,我们将上面的例子改写成 XML 配置很容易(去掉所有的注解):

  1. <!-- 装配 Bean-->

  2. <bean name="landlord" class="pojo.Landlord"/>

  3. <bean id="broker" class="aspect.Broker"/>

  4. <!-- 配置AOP -->

  5. <aop:config>

  6. <!-- where:在哪些地方(包.类.方法)做增加 -->

  7. <aop:pointcut id="landlordPoint"

  8. expression="execution(* pojo.Landlord.service())"/>

  9. <!-- what:做什么增强 -->

  10. <aop:aspect id="logAspect" ref="broker">

  11. <!-- when:在什么时机(方法前/后/前后) -->

  12. <aop:around pointcut-ref="landlordPoint" method="around"/>

  13. </aop:aspect>

  14. </aop:config>

运行测试程序,看到正确结果:

AOP切面编程

参考文章

https://www.cnblogs.com/xuyatao/p/8485851.html https://www.cnblogs.com/wmyskxz/p/8835243.html

推荐阅读

1、Spring控制反转与依赖注入

2、Java的Hashmap

3、Java内存模型(JMM)

AOP切面编程

本文分享自微信公众号 - 爱编码(ilovecode)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
kenx kenx
3年前
SpringBoot Aop 详解和多种使用场景
前言aop面向切面编程,是编程中一个很重要的思想本篇文章主要介绍的是SpringBoot切面Aop的使用和案例什么是aopAOP(AspectOrientedProgramming):面向切面编程,面向切面编程(也叫面向方面编程),是目前软件开发中的一个热点,也是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
AOP相关概念
1.AOP(面向切面编程)在软件业,AOP为AspectOrientedProgramming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,在软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型.利用AOP
Stella981 Stella981
3年前
69道Spring面试题和答案
目录Spring概述依赖注入SpringbeansSpring注解Spring数据访问Spring面向切面编程(AOP)SpringMVCSpring 概述1. 什么是spring?Spring 是个java企业
Easter79 Easter79
3年前
Spring常用知识点总结
1\.Spring有哪些优点?  轻量级:Spring在大小和透明性方面绝对属于轻量级的,基础版本的Spring框架大约只有2MB。  控制反转(IOC):Spring使用控制反转技术实现了松耦合。依赖被注入到对象,而不是创建或寻找依赖对象。  面向切面编程(AOP): Spring支持面向切面编程,同时把应用的业务逻辑与系统的服务分离开来
Easter79 Easter79
3年前
Spring的AOP逐层深入——AOP的基本原理(六)
什么是AOP    AOP(AspectOrientedProgramming),意思是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP基于IoC基础,是对OOP(ObjectOrientedProgramming,面向对象)的延续。同时,AOP实际是GOF设计模式的延续,设计模式孜孜不倦
Easter79 Easter79
3年前
Spring的基本应用(1):IDEA版本
一、Spring概述:1.什么是Spring?Spring是分层的JavaSE/EE应用fullstack(一站式)轻量级开源框架,以IoC(InverseOfControl:控制反转)和AoP(AspectOrientedPrograming,面向切面编程)为内核,提供了展现层SpringMVC和持久层Spring
Easter79 Easter79
3年前
Spring概念和Bean管理(配置文件)
Spring概念1.spring是开源的轻量级框架(免费,依赖少,可以直接使用)2.spring核心主要两部分:  (1)aop:面向切面编程,扩展功能不是修改源代码实现  (2)ioc:控制反转,  比如有个类,在类里面有个方法(不是静态方法),调用类里面的方法,需要创建类的对象,使用对象调用方法,创
Easter79 Easter79
3年前
Spring知识点提炼
1\.Spring框架的作用清理:Spring是轻量级的,基本的版本大小为2MB控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。面向切面的编程AOP:Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。容器:Spring包含并管理应用中对象