面向方面编程 (AOP) 通过提供另一种思考程序结构的方式来补充面向对象编程 (OOP)。OOP 中模块化的关键单元是类,而 AOP 中模块化的单元是方面。

AOP 概念

  • Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;
  • Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;
  • Pointcut:切入点,即一组连接点的集合;
  • Advice:增强,指特定连接点上执行的动作;
  • Introduction:引介,指为一个已有的Java对象动态地增加新的接口;
  • Weaving:织入,指将切面整合到程序的执行流程中;
  • Interceptor:拦截器,是一种实现增强的方式;
  • Target Object:目标对象,即真正执行业务的核心逻辑对象;
  • AOP Proxy:AOP代理,是客户端持有的增强后的对象引用。

AOP 代理

Spring AOP 默认为 AOP 代理使用标准 JDK 动态代理。这使得任何接口(或一组接口)都可以被代理。

Spring AOP 也可以使用 CGLIB 代理。这是代理类而不是接口所必需的。默认情况下,如果业务对象未实现接口,则使用 CGLIB。由于对接口而不是类进行编程是一种很好的做法,因此业务类通常实现一个或多个业务接口。在那些(希望很少见)需要建议未在接口上声明的方法或需要将代理对象作为具体类型传递给方法的情况下,可以 强制使用 CGLIB

@AspectJ 支持

@AspectJ 指的是一种将切面声明为带有注解的常规 Java 类的风格。

启用@AspectJ 支持

要在 Spring 配置中使用 @AspectJ 方面,您需要启用 Spring 支持以基于 @AspectJ 方面配置 Spring AOP,并根据这些方面是否建议它们来自动代理 bean。通过自动代理,我们的意思是,如果 Spring 确定一个 bean 由一个或多个方面通知,它会自动为该 bean 生成一个代理来拦截方法调用并确保根据需要运行通知。

可以使用 XML 或 Java 样式的配置启用 @AspectJ 支持。

使用 Java 配置启用 @AspectJ 支持

要使用 Java 启用 @AspectJ 支持@Configuration,请添加@EnableAspectJAutoProxy 注释,如以下示例所示:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig { }
通过 XML 配置启用 @AspectJ 支持

要使用基于 XML 的配置启用 @AspectJ 支持,请使用该aop:aspectj-autoproxy 元素,如以下示例所示:

<aop:aspectj-autoproxy/>

声明一个方面

作用是把当前类标识为一个切面供容器读取。

这两个示例中的第一个显示了应用程序上下文中的常规 bean 定义,它指向具有@Aspect注释的 bean 类:

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of the aspect here -->
</bean>

两个示例中的第二个显示了NotVeryUsefulAspect类定义

package org.xyz;
import org.aspectj.lang.annotation.Aspect; @Aspect
public class NotVeryUsefulAspect { }

请注意, @Aspect注释不足以在类路径中进行自动检测。为此,您需要添加一个单独的@Component注释

声明切入点

切入点确定连接点,从而使我们能够控制建议何时运行。

以下示例定义了一个名为的切入点anyOldTransfer,它与任何名为 transfer的方法的执行相匹配:

@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature

execution函数用于匹配方法执行的连接点,语法为:

execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选))

参数部分允许使用通配符:

* 匹配任意字符,但只能匹配一个元素

… 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用

+ 必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类

示例中的* transfer(…)解读为:

方法修饰符 无

返回类型 *匹配任意数量字符,表示返回类型不限

方法名 transfer表示匹配名称为transfer的方法

参数 (…)表示匹配任意数量和类型的输入参数

异常模式 不限

声明通知

Advice 与切入点表达式相关联,并在切入点匹配的方法执行之前、之后或周围运行。切入点表达式可以是对命名切入点的简单引用,也可以是就地声明的切入点表达式。

Spring AOP 包括以下类型的通知:

  • @Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;
  • @After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;
  • @AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;
  • @AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;
  • @Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。

实例化切面

默认情况下,应用程序上下文中的每个方面都有一个实例。

例子

我们定义一个LoggingAspect

@Aspect
@Component
public class LoggingAspect {
// 在执行UserService的每个方法前执行:
@Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))")
public void doAccessCheck() {
System.err.println("[Before] do access check...");
} // 在执行MailService的每个方法前后执行:
@Around("execution(public * com.itranswarp.learnjava.service.MailService.*(..))")
public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
System.err.println("[Around] start " + pjp.getSignature());
Object retVal = pjp.proceed();
System.err.println("[Around] done " + pjp.getSignature());
return retVal;
}
}

观察doAccessCheck()方法,我们定义了一个@Before注解,后面的字符串是告诉AspectJ应该在何处执行该方法,这里写的意思是:执行UserService的每个public方法前执行doAccessCheck()代码。

再观察doLogging()方法,我们定义了一个@Around注解,它和@Before不同,@Around可以决定是否执行目标方法,因此,我们在doLogging()内部先打印日志,再调用方法,最后打印日志后返回结果。

LoggingAspect类的声明处,除了用@Component表示它本身也是一个Bean外,我们再加上@Aspect注解,表示它的@Before标注的方法需要注入到UserService的每个public方法执行前,@Around标注的方法需要注入到MailService的每个public方法执行前后。

紧接着,我们需要给@Configuration类加上一个@EnableAspectJAutoProxy注解:

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {
...
}

使用注解装配AOP

在实际项目中,这种写法其实很少使用execution(* xxx.Xyz.*(..))语法来定义应该如何装配AOP。

因为,基本能实现无差别全覆盖,即某个包下面的所有Bean的所有方法都会被方法拦截。

我们在使用AOP时,要注意到虽然Spring容器可以把指定的方法通过AOP规则装配到指定的Bean的指定方法前后,但是,如果自动装配时,因为不恰当的范围,容易导致意想不到的结果,即很多不需要AOP代理的Bean也被自动代理了,并且,后续新增的Bean,如果不清楚现有的AOP装配规则,容易被强迫装配。

使用AOP时,被装配的Bean最好自己能清清楚楚地知道自己被安排了。例如,Spring提供的@Transactional就是一个非常好的例子。如果我们自己写的Bean希望在一个数据库事务中被调用,就标注上@Transactional

@Component
public class UserService {
// 有事务:
@Transactional
public User createUser(String name) {
...
} // 无事务:
public boolean isValidName(String name) {
...
} // 有事务:
@Transactional
public void updateUser(User user) {
...
}
}

因此,装配AOP的时候,使用注解是最好的方式。

@Target(METHOD)
@Retention(RUNTIME)
public @interface MetricTime {
String value();
}

在需要被监控的关键方法上标注该注解:

@Component
public class UserService {
// 监控register()方法性能:
@MetricTime("register")
public User register(String password, String name) {
...
}
...
}

然后,我们定义MetricAspect

@Aspect
@Component
public class MetricAspect {
@Around("@annotation(metricTime)")
public Object metric(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable {
String name = metricTime.value();
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long t = System.currentTimeMillis() - start;
// 写入日志或发送至JMX:
System.err.println("[Metrics] " + name + ": " + t + "ms");
}
}
}

注意metric()方法标注了@Around("@annotation(metricTime)"),它的意思是,符合条件的目标方法是带有@MetricTime注解的方法,因为metric()方法参数类型是MetricTime(注意参数名是metricTime不是MetricTime),我们通过它获取性能监控的名称。

有了@MetricTime注解,再配合MetricAspect,任何Bean,只要方法标注了@MetricTime注解,就可以自动实现性能监控。

Spring系列之面向切面编程-15的更多相关文章

  1. Spring框架系列(4) - 深入浅出Spring核心之面向切面编程(AOP)

    在Spring基础 - Spring简单例子引入Spring的核心中向你展示了AOP的基础含义,同时以此发散了一些AOP相关知识点; 本节将在此基础上进一步解读AOP的含义以及AOP的使用方式.@pd ...

  2. Spring AOP:面向切面编程,AspectJ,是基于spring 的xml文件的方法

    导包等不在赘述: 建立一个接口:ArithmeticCalculator,没有实例化的方法: package com.atguigu.spring.aop.impl.panpan; public in ...

  3. Spring AOP:面向切面编程,AspectJ,是基于注解的方法

    面向切面编程的术语: 切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象 通知(Advice): 切面必须要完成的工作 目标(Target): 被通知的对象 代理(Pr ...

  4. Spring:AOP面向切面编程

    AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果. AOP是软件开发思想阶段性的产物,我们比较熟悉面向过程O ...

  5. Spring框架 AOP面向切面编程(转)

    一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址:http://www.cnbl ...

  6. Spring AOP(面向切面编程)

    一.AOP简介 1.AOP概念:Aspect Oriented Programming 面向切面编程 2.作用:本质上来说是一种简化代码的方式 继承机制 封装方法 动态代理 …… 3.情景举例 ①数学 ...

  7. Spring的AOP面向切面编程

    什么是AOP? 1.AOP概念介绍 所谓AOP,即Aspect orientied program,就是面向方面(切面)的编程. 功能: 让关注点代码与业务代码分离! 关注点: 重复代码就叫做关注点: ...

  8. Spring中的面向切面编程(AOP)简介

    一.什么是AOP AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面 ...

  9. Spring实战Day7面向切面编程术语介绍

    #### 面向切面编程 为什么需要切面? 有些功能需要在应用中的多个地方使用到,但是我们又不想在着每个地方都调用他们 切面术语 通知(advice):切面需要完成的工作 通知的类型(什么时间完成工作) ...

  10. 程序员笔记|Spring IoC、面向切面编程、事务管理等Spring基本概念详解

    一.Spring IoC 1.1 重要概念 1)控制反转(Inversion of control) 控制反转是一种通过描述(在java中通过xml或者注解)并通过第三方去产生或获取特定对象的方式. ...

随机推荐

  1. 读后笔记 -- Python 全栈测试开发 Chapter10:接口的设计与开发

    10.1 Django 框架 1. 几个主流的框架: 1)适合初学者的接口框架:Django,Flask 2)针对底层定义:Twisted 3)实现高并发:Tornado 2. install // ...

  2. Java Swing 禁止黏贴动作

    碰到一个需求,不让复制黏贴. 可考的方法有:1 重写JTextField paste 函数    2 删除组件ActionMap中与CTRL + V按键相关的操作 因为JTextField 已经被封装 ...

  3. leetcode -- 旋转矩阵相关问题

    给定一个 n × n 的二维矩阵表示一个图像. 将图像顺时针旋转 90 度. 说明: 你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵.请不要使用另一个矩阵来旋转图像. 示例 1: 给定 m ...

  4. Django Cannot assign "A1": "B1" must be a "C1" instance.

    Django Cannot assign "A1": "B1" must be a "C1" instance. 原因:使用了外键 说明:如 ...

  5. 画ERA5气压层剖面图(含地形)

    气象上一般使用气压垂直坐标系,在不同的气压层绘制变量.ERA5再分析数据的最低气压层是1000 hPa.实际上,由于地形起伏,一些位置的地面气压低于1000 hPa,一些位置的地面气压高于1000 h ...

  6. jmeter-时间处理

    ${__time(,)} 1486091280955 //无格式化参数,返回当前毫秒时间,默认13位.一般用来做时间戳 ${__time(/1000,)} //为取10位的时间戳的函数表达式(时间精确 ...

  7. C语言-链表流星雨(EsayX)

    刷B站看到的,做个玩玩.IDE:Visual Studio 2022.依赖EsayX图形库 1-效果 2-程序 /* 链表流星雨单文件版本 依赖EsayX图形库 */ #include <std ...

  8. Oracle 会话锁死

    需要管理员用户下执行(sys/sysdba) --先查锁 select * from v$lock where lmode > 0 and type in ('TM','TX'); --查用户名 ...

  9. 【C学习笔记】day1-1 打印100~200之间的素数

    #include<stdio.h> int sushu(int input) { int m = 0; for (int i = 1; i <= input; i++) { if ( ...

  10. vue remark重置 提交

    html: <el-table-column prop="remark" label="">                 <templat ...