Spring AOP 编程

什么是 AOP

AOP(Aspect Oriented Programming 的缩写,翻译为面向方面或面向切面编程),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术

  • AOP 是 OOP 的延续和有益补充,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型
  • 在 Spring 中提供了 AOP 的丰富支持,允许通过分离应用的业务逻辑与系统级服务和事务管理进行内聚性的开发

利用 AOP 编程可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低(低耦合),提高程序的可重用性,同时提高开发的效率

AOP 与 OOP 的区别

AOP(面向切面编程) 和 OOP(面向对象编程) 在字面上虽然非常类似,但却是面向不同领域的两种编程思想,这两种编程思想在目标上有着本质的差异

  • OOP(面向对象编程):针对业务处理过程的实体及其属性和行为进行抽象封装为对象,以对象作为最基本的逻辑处理单元,并关注对象与对象之间的关系
  • AOP(面向切面编程):针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以切面作为最基本的逻辑处理单元,以获得逻辑过程中各部分之间低耦合性的隔离效果

AOP 绝对不会代替 OOP,而是与 OOP 整合起来,以此之长,补彼之短

横切关注点

在软件开发中,分布于应用中多处的功能被称为横切关注点

  • 通常,这些横切关注点从概念上是与应用的业务逻辑分离的(但往往直接嵌入到应用的业务逻辑之中),AOP 的目标正是将这些横切关注点与业务逻辑隔离开来
  • DI(依赖注入)有助于应用对象之间的解耦,而 AOP 可以实现横切关注点与他们所影响的对象之间的解耦
AOP 主要功能
  • 日志记录
  • 性能统计
  • 安全控制
  • 事务处理
  • 异常处理
  • 其他功能
AOP 横向抽取机制

AOP 通过横向抽取机制为这类无法通过纵向继承体系(OOP 的方式)进行抽象的重复性代码提供了解决方案

image-20210925144947305

AOP 就是将各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,让业务逻辑代码更加清晰

AOP 术语
连接点(Joinpoint)----插入代码的位置(类某个方法调用前、调用后、方法抛出异常后)

程序执行的某个特定位置(如类开始初始化前、类初始化后。类某个方法调用前、调用后、方法抛出异常后)。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就称为连接点(插入代码的位置)

注意:Spring AoP 仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强

切点(Pointcut)-----插入代码位置的查询条件

每个类一般都拥有多个连接点(一般一个方法就是一个连接点)。AOP需要定位到特定的连接点,而定位连接点的方式称为切点。连接点相当于数据库中的记录,而切点相当于查询条件,一个切点可以匹配多个连接点。(插入代码位置的查询条件)

注意:Spring AOP 中切点通过 Pointcut 接口定义,它使用类和方法作为连接点的查询条件。Spring AOP 的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点

增强(Advice)----插入到连接点上的代码

1.前置增强 2.后置增强 3.环绕增强 4.抛出异常增强

增强是织入到目标类连接点上的一段程序代码。在 Spring AOP 中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息-执行点的方位。结合执行点的方位信息和切点信息,AOP就可以找到特定的连接点。

因为增强既包括了用于添加到目标连接点上的一段执行逻辑,由包含用于定位连接点的方位信息,所以Spring所提供的增强接口都带有方位名

注意:增强有的教材也叫通知,是对 Advice 翻译不同而已

引介(Introduction)

引介是一种特殊的增强,它为类添加一些属性和方法。即使一个业务类原本没有实现某个接口,也可以通过AOP引介功能,动态地位该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

目标对象(Target)

它是增强逻辑的织入目标类。通过 AOP,业务逻辑类只需要实现非横切逻辑的代码,而性能监视、事务管理等横切逻辑则可以使用 AOP 动态织入到特定的连接点上。

织入(Weaving)

织入是将增强添加对目标类具体连接点上的过程

AOP 有三种织入方式:

  • 编译器织入:要求使用特殊的编译器
  • 类装载期织入:要求使用特殊的类装载器
  • 动态代理织入:在运行期为目标类添加增强生成子类的方式

注意:Spring AOP 采用动态代理织入方式

代理(Proxy)

一个类被 AOP 织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类。所以,可以采用调用原类相同的方式调用代理类。

切面(Aspect)

切面由切点和增强或引介组成。它既包括了横切逻辑的定义,也包括了连接点的定义。

切面 = 切点 + 增强(或引介)

注意:Spring AOP 负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中

AOP 框架

image-20210925145504988

JDK 动态代理与 CGLib 动态代理区别

JDK 的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现 JDK 的动态代理;CGLib 是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理

增强

Spring AOP 框架使用增强接口定义横切逻辑,同时由于 Spring AOP 只支持方法连接点,所以增强既包含横切逻辑,还包含部分连接点信息

image-20210925145743440

切面类型
StaticMethodMatcherPointcutAdvisor(静态切面)

StaticMethodMatcherPointcutAdvisor 代表一个静态方法匹配切面,它通过StaticMethodMatcherPointcut 定义切点,通过类过滤和方法名匹配定义切点(开发者需要继承该类切面,并提供匹配类过滤和方法匹配的规则)

DynamicMethodMatcherPointcut(动态切面)

DynamicMethodMatcherPointcut抽象类,它将isRuntime()方法设置为final 且返回 true,这样其继承子类就一定是一个动态的切点。该抽象类默认匹配所有的类和方法,因此实现动态切面需要继承该类。

切面机制

Spring 采用的机制是在创建代理时对目标类的每个连接点使用静态切点检查,如果仅通过静态切点检查就可以知道连接点是不匹配的,则在运行时就不再进行动态检查;如果静态切点检查是匹配的,在运行时才进行动态切点检查

  • 由于动态切点检查会对性能造成很大的影响,应当尽量避免在运行时每次都对目标类的各个方法进行动态检查
  • 在动态切点类定义静态切点检查的方法可以防止不必要的动态检查操作,可以极大地提高运行效率
  • 动态切面的配置和静态切面的配置没有太大区别,使用DefaultPointcutAdvisor 定义切面
切面注解

开启 AOP 注解

<aop:aspectj-autoproxy />
@Aspect 注解(声明切面类)

该注解作用于切面类上,一般采用 Spring 提供的自动扫描组件的方式注册 Bean,所以注解切面类上还要配置 @Component;否则就要把该切面类注册到 Spring 的 XML 配置文件中

@Before 注解(前置增强)

用于注解实现前置增强,作用于在切面类方法上

@Before("execution(返回类型 类全名.方法名(形参列表))")
public void 方法名( ) {
       //方法实现
       …
}
@After 注解(后置增强)

用于注解实现后置增强,作用于在切面类方法上

@After("execution(返回类型 类全名.方法名(形参列表))")
public void 方法名( ) {
       //方法实现
       …
}
@Around 注解(环绕增强)

用于注解实现环绕增强,作用于在切面类方法上

@Around("execution(返回类型 类全名.方法名(形参列表))")
public void 方法名(ProceedingJoinPoint jp) {
       //方法前执行增强代码
      Object obj = jp.processed(); 
      //方法后执行增强代码
      return obj;
}
@AfterThrowing 注解(抛出异常增强)

用于注解实现增强,作用于在切面类方法上

@AfterThrowing (“execution(返回类型 类全名.方法名(形参列表))”)
public void 方法名( ) {
       //方法实现
       …
}
切点表达式

execution( 返回类型 类全名.方法名(形参列表))

表达式通配符如下:

image-20210925150315817

切点表达式举例:

execution(* com.lovo.bean.Dog.b*())
execution(* com.lovo.*.Dog.b*(..))
execution(* com.lovo..Dog.b*(..))
execution(* com lovo..Dog.b*(*))
execution(* com.lovo.bean.Dog+.b*(..))

Spring 声明式事务管理

@Transactional 注解

作为使用基于 XML 配置声明式事务配置方法的补充, 你可以使用一种基于注解的方法. 直接在Java 代码中声明事务 语义声明使得声明更加靠近生效的代码. 这不存在过度危险的耦合, 因为不管怎么说开发代码的就意味着这样被事务化地使用

举例:

// 我们想要支持事务的服务类
@Transactional
Public class Service implements IService {
    Foo getFoo(String fooName);
    Foo getFoo(String fooName, String barName);
    void insertFoo(Foo foo);
    void updateFoo(Foo foo);
}

Spring 集成 JUnit 单元测试

@RunWith(SpringJUnit4ClassRunner.class)                   //使用 Junit 4 单元测试框架
@ContextConfiguration("classpath:applicationContext.xml") //加载 ioc 容器
@Transactional(transactionManager = "transactionManger")  //声明式事务管理
@Rollback(value = false)                                  //不对事务进行回滚(默认true)
public class UserDaoTest extends AbstractTransactionalJUnit4SpringContextTests {
	@Test
	public void test() {
		//测试代码
	}
}
posted @ 2021-10-10 19:40  追こするれい的人  阅读(43)  评论(0编辑  收藏  举报