AOP相关概念

在学习AOP实现原理之前,先了解下AOP相关基础知识。

AOP面向切面编程,它可以通过预编译方式或者基于动态代理对我们编写的代码进行拦截(也叫增强处理),在方法执行前后可以做一些操作,一般我们会看到以下几个概念:

连接点(JointPoint): AOP进行切入的位置称为连接点,一般指程序中的某个方法,对该方法进行拦截

通知(Advice): 在某个连接点执行的操作称为通知,也就是被拦截方法执行前后需要执行的操作称为通知,一共有五种

  • 前置通知:作用于被拦截方法执行之前
  • 后置通知:作用于被拦截方法执行之后进行的操作,无论被拦截方法是否抛出异常都会执行
  • 环绕通知:作用于被拦截方法执行之前和执行之后
  • 返回通知:作用于被拦截方法正常执行完毕返回时,如果抛出异常将不会执行
  • 异常通知:作用于被拦截方法抛出异常时

切点(Pointcut): 切点作用在于让程序知道需要在哪个连接点(方法)上执行通知,所以它也可以是一个表达式,匹配所有需要拦截的方法。

切面(Aspect): 切点通知共同组成了切面,其中切点定义了需要在哪些连接点上执行通知,通知里面定义了具体需要进行的操作

织入(Weaving):将切面连接到应用程序类型或者对象上,创建一个被通知的对象(advised object)的过程称为织入,换句话说织入就是将切面应用到目标对象的过程,它可以在编译期时(使用AspectJ)、加载时或者在运行时实现,Spring AOP是在运行时基于动态代理实现的。

Advisor:它是对切面的封装,使用了@AspectJ注解的类会被封装成Advisor。

Spring AOP和AspectJ区别

Spring AOP

Spring AOP是基于动态代理实现拦截功能的,默认使用JDK动态代理实现,当然这需要目标对象实现接口,如果目标对象没有实现接口,则使用CGLIB生成。

AspectJ

AspectJ提供了三种方式实现AOP:

  • 编译时织入:在编译期间将代码进行织入到目标类的class文件中。

  • 编译后织入:在编译后将代码织入到目标类的class文件中。

  • 加载时织入:在JVM加载class文件的时候进行织入。

Spring AOP的应用

了解了AOP相关知识后我们来实现一个需求:

  1. 自定义一个日志注解MyLogger
  2. 对使用了MyLogger注解的方法进行拦截,在方法的执行前后分别进行一些操作(环绕通知):
    • 方法执行前打印方法传入的参数
    • 方法执行后打印方法的返回值

自定义注解

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyLogger { }

定义切面Aspect

这里使用注解@Aspect来标记这是一个切面,切面是切点和通知的集合,分别使用注解@Pointcut和@Around实现。

@Slf4j
@Aspect // 使用注解定义切面
@Component
@EnableAspectJAutoProxy // 开启AOP
public class MyLogAspect { }
切点Pointcut

使用表达式@annotation(com.demo.mybatis.annotation.MyLogger)匹配所有使用了@MyLogger注解的方法。

    /**
* 定义切点,匹配所有使用了@MyLogger注解的方法
*/
@Pointcut("@annotation(com.example.annotation.MyLogger)") // 这里传入MyLogger的全路径
public void logPoiontcut() { }
通知Advice

定义一个logAroudAdvice方法,使用@Around注解标记这是一个环绕通知,logPoiontcut()引用了切点,表示通知要作用于哪些连接点上,该方法需要传入一个ProceedingJoinPoint类型参数(连接点):

    /**
* 通知Advice,这里使用了环绕通知
* @param joinPoint 连接点
* @return
* @throws Throwable
*/
@Around("logPoiontcut()") // 引用切点
public Object logAroudAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法执行前的日志打印
printBeforeLog(joinPoint);
// 执行方法
Object returnValue = joinPoint.proceed();
// 方法执行后的日志打印
printAfterLog(returnValue);
return returnValue;
}

完整的切面如下:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; @Slf4j
@Aspect // 使用注解定义切面
@Component
@EnableAspectJAutoProxy
public class MyLogAspect { /**
* 定义切点,匹配所有使用了@MyLogger注解的方法
*/
@Pointcut("@annotation(com.example.annotation.MyLogger)")
public void logPoiontcut() {
} /**
* 通知Advice,这里使用了环绕通知
* @param joinPoint 连接点
* @return
* @throws Throwable
*/
@Around("logPoiontcut()") // 引用切点
public Object logAroudAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法执行前的日志打印
printBeforeLog(joinPoint);
// 执行方法
Object returnValue = joinPoint.proceed();
// 方法执行后的日志打印
printAfterLog(returnValue);
return returnValue;
} /**
* 方法执行前的日志打印
* @param joinPoint
*/
public void printBeforeLog(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 获取方法
Method method = methodSignature.getMethod();
log.info("开始执行方法:{}", method.getName());
// 获取参数
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return;
}
// 获取参数名称
String[] parameterNames = methodSignature.getParameterNames();
StringBuilder parameterBuilder = new StringBuilder();
for (int i = 0; i < args.length; i++) {
parameterBuilder.append(parameterNames[i]).append(":").append(args[i]);
if (i < parameterNames.length - 1) {
parameterBuilder.append(",");
}
}
log.info("方法参数【{}】", parameterBuilder.toString());
} /**
* 方法执行后的日志打印
* @param returnValue
*/
public void printAfterLog(Object returnValue) {
log.info("方法返回值【{}】", returnValue == null ? null : returnValue.toString());
}
}

测试

定义一个用于计算的Service,实现一个两数相加的方法addTwoNum,并使用@MyLogger注解,对方法进行拦截,在方法执行前后打印相关日志

@Slf4j
@Service
public class ComputeService { // 使用自定义日志注解对方法进行拦截
@MyLogger
public Integer addTwoNum(Integer value1, Integer value2) {
log.info("执行addTwoNum方法");
return value1 + value2;
}
}

编写单元测试:

	@Autowired
private ComputeService computeService; @Test
public void testAddTwoNum {
computeService.addTwoNum(1, 2);
}

由于ComputeService没有实现接口,可以看到Spring默认使用了CGLIB生成对象:

日志输出如下,可以看到方法执行前后打印了相关日志:

开始执行方法:addTwoNum
方法参数【value1:1,value2:2】
执行addTwoNum方法
方法返回值【3】

参考

Spring官方文档

【 FatalFlower】AspectJ 简介

【Spring】AOP实现原理(一):AOP基础知识的更多相关文章

  1. Spring学习记录5——数据库事务基础知识

    何为数据库事务 “一荣共荣,一损共损”这句话很能体现事务的思想,很多复杂的事务要分步进行,但它们组成了一个整体,要么整体生效,要么整体失效.这种思想反映到数据库上,就是多条SQL语句,要么全部成功,要 ...

  2. 手写spring事务框架, 揭秘AOP实现原理。

    AOP面向切面编程:主要是通过切面类来提高代码的复用,降低业务代码的耦合性,从而提高开发效率.主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等. AOP实现原理:aop是通过cgli ...

  3. spring5 源码深度解析----- Spring事务 是怎么通过AOP实现的?(100%理解Spring事务)

    此篇文章需要有SpringAOP基础,知道AOP底层原理可以更好的理解Spring的事务处理. 自定义标签 对于Spring中事务功能的代码分析,我们首先从配置文件开始人手,在配置文件中有这样一个配置 ...

  4. JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架

    1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...

  5. Spring 基础知识(一)基本概念 DI、IOC、AOP

    DI(依赖注入) 和IOC(控制反转)都是一种设计思想,要理解他们,让我们从coding中的一些痛点入手. 依赖注入 Dependency Injection : 如果A类要使用B类的一个方法,首先必 ...

  6. springmvc 运行原理 Spring ioc的实现原理 Mybatis工作流程 spring AOP实现原理

    SpringMVC的工作原理图: SpringMVC流程 . 用户发送请求至前端控制器DispatcherServlet. . DispatcherServlet收到请求调用HandlerMappin ...

  7. Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现

    我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理.@pdai Spring框架系列 ...

  8. 【Spring】Spring AOP实现原理

    Spring AOP实现原理 在之前的一文中介绍过Spring AOP的功能使用,但是没有深究AOP的实现原理,今天正好看到几篇好文,于是就自己整理了一下AOP实现的几种方式,同时把代理模式相关知识也 ...

  9. Spring核心框架 - AOP的原理及源码解析

    一.AOP的体系结构 如下图所示:(引自AOP联盟) 层次3语言和开发环境:基础是指待增加对象或者目标对象:切面通常包括对于基础的增加应用:配置是指AOP体系中提供的配置环境或者编织配置,通过该配置A ...

随机推荐

  1. 12-factors

    12-factors 官方网址 The Twelve-Factor App 简介 如今,软件通常会作为一种服务来交付,它们被称为网络应用程序,或软件即服务(SaaS).12-Factor 为构建如下的 ...

  2. python的字典及相关操作

    一.什么是字典 字典是Python中最强大的数据类型之一,也是Python语言中唯一的映射类型.映射类型对象里哈希值(键,key)和指向的对象(值,value)是一对多的的关系,通常被认为是可变的哈希 ...

  3. HTML5+JavaScript+MUI 毕业答辩常见问题

    问题1:HTML5前端开发环境有哪些? HTML5网页前端开发,实际也是HTML网页开发,HTML5知识万维网的核心语言.标准通用标记语言下的一个应用超文本标记语言(HTML)的第五次重大修改的第五次 ...

  4. 2021.08.01 P4311 数字序列(左偏树)

    2021.08.01 P4311 数字序列(左偏树) [P4331 BalticOI 2004]Sequence 数字序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 重点: 1 ...

  5. 初学Java时没有理解的一些概念

    背景 之前学Java属于赶鸭子上架,草草学习基础语法便直接做课程作业,许多概念问题仍不清楚,故在此梳理一下,主要参考廖雪峰和互联网资料. Java运行方式与JVM Java是介于编译型语言(C++)和 ...

  6. 国产化之 .NET Core 操作达梦数据库DM8的两种方式

    背景 某个项目需要实现基础软件全部国产化,其中操作系统指定银河麒麟,数据库使用达梦V8,CPU平台的范围包括x64.龙芯.飞腾.鲲鹏等.考虑到这些基础产品对.NET的支持,最终选择了.NET Core ...

  7. JavaCV的摄像头实战之七:推流(带声音)

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<JavaCV的摄像头实战> ...

  8. @Resource与构造函数踩坑

    (虽然解决了需求,但我还是没搞懂为什么构造函数结束后,调用userMapper注入还是为空!) 首先,我有一个没有问题的userMapper类,用于处理user的数据库处理. 其次,我在另一个类里面使 ...

  9. FreeRTOS --(16)资源管理之临界区

    转载自 https://blog.csdn.net/zhoutaopower/article/details/107387427 临界区的概念在任何的 SoC 都存在,比如,针对一个寄存器,基本操作为 ...

  10. Halo 开源项目学习(七):缓存机制

    基本介绍 我们知道,频繁操作数据库会降低服务器的系统性能,因此通常需要将频繁访问.更新的数据存入到缓存.Halo 项目也引入了缓存机制,且设置了多种实现方式,如自定义缓存.Redis.LevelDB ...