不知道大家有没有这样的感觉,平时经常说aop,但是对aop中的一些概念还是模糊,总感觉很飘渺,今天来梳理下关于aop的知识。

一、概念

我们知道现在开发都是spring,讲的最多的也是springAOP,在说springAOP前,先了解下AOP是什么?

AOP是通过“预编译方式”和“运行期间动态代理”实现程序功能的统一维护的一种技术。AOP是一个概念,其实现技术有AspectJ和springAOP

,现在对AOP有个清楚的了解了,再来看下AOP中的一些概念。

  1. 切面(aspect),业务层面是程序中的标准代码/功能,不同于实际的业务逻辑,比如日志功能、事务等。代码层面切点+通知构成了一个切面;
  2. 连接点(joinPoint),程序运行过程中的某个特定点,比如方法执行、字段赋值、方法调用等;
  3. 切点/切入点(pointCut),一个匹配连接点的正则表达式。 每当任何连接点匹配一个切入点时,就执行与该切入点相关联的通知。可以把切入点看作是符合条件的连接点;
  4. 通知(advice),在一个连接点中,切面采取的行动,简单点说是对切点做什么事,主要有before、afterReturning、round等通知
  5. 织入(weaving),连接切面和目标对象来创建一个通知对象的过程,简单点说是把通知应用到连接点的过程;

通过一个图了解下AOP、Aspectj、SpringAOP的关系,

1、AspectJ

AspcetJ作为AOP的一种实现,是基于编译的方式实现的AOP,在程序运行期是不会做任何事情的,因为类和切面是直接编译在一起的。AspectJ 使用了三种不同类型的织入方式,使用的是编译期和类加载时进行织入

  1. Compile-time weaving:编译期织入。编译器将切面和应用的源代码编译在一个字节码文件中。
  2. Post-compile weaving:编译后织入。也称为二进制织入。将已有的字节码文件与切面编制在一起。
  3. Load-time weaving:加载时织入。与编译后织入一样,只是织入时间会推迟到类加载到jvm时。

2、springAOP

springAOP作为AOP的一种实现,基于动态代理的实现AOP,意味着实现目标对象的切面会创建一个代理类,代理类的实现有两种不同的模式,分为两种不同的代理,Spring AOP利用的是运行时织入,在springAOP中连接点是方法的执行。

  1. JDK动态代理;
  2. cglib动态代理;

另外,在springAOP的实现中,借用了AspectJ的一些功能,比如@AspceJ、@Before、@PonitCut这些注解,都是AspectJ中的注解。在使用springAOP的时候需要引入AspectJ的依赖,

<!--使用springAOP需要引入该依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>

下面在演示过程中会再次提及这块。

springAOP复用了AspectJ中的下面几个通知,这些注解都是AspectJ中的注解,对应这些注解springAOP分别自己的处理类

  1. 方法执行前,使用MethodBeforeAdvice接口,使用AspectJ中的注解@Before表示
  2. 方法执行后,使用AfterReturningAdvice接口,使用AspectJ中的注解@After表示
  3. 方法执行中,使用注解@Around表示
  4. 方法执行完返回方法返回值,使用注解@AfterReturning表示
  5. 方法抛出异常,使用注解@AfterThrowing表示

二、springAOP实践

实践出真知,有了上面的理论基础现在开始实践。

有一个service类执行save操作,要在saveUser方法执行的时候织入相应的通知。

UserService.java

package com.my.template.service;

import com.my.template.entity.User;
import org.springframework.stereotype.Service; /**
* @date 2022/8/9 15:28
*/
@Service
public class UserService implements Us{
@Override
public void saveUser(User user){
System.out.println("保存user对象到数据库:"+user);
}
}

该方法的调用是通过一个controller完成的,

UserController.java

package com.my.template.controller;

import com.my.template.entity.User;
import com.my.template.service.Us;
import com.my.template.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; /**
* @date 2022/8/9 15:35
*/
@RestController
public class UserController {
@Autowired
private Us us;
@RequestMapping("/saveUser")
public String saveUser(){ User user=new User();
user.setId("1");
user.setName("张三");
us.saveUser(user);
return "success";
} }

在不加任何切面及通知前调用该方法的打印结果是,

下面看使用AOP后的结果是什么样子的。

1、使用springAOP的XML方式

前面知道springAOP提供了@Before、@After、@Round等注解的,这些注解是复用AspectJ的,下面看,如何使用XML的方式,定义一个通知类,

Log.java

package com.my.template.aop;

import org.springframework.stereotype.Component;
/**
* @date 2022/8/10 14:13
*/
@Component
public class Log { /**
* 方法执行前
*/
public void before(){ System.out.println("执行方法前");
}
/**方法执行后
*/
public void after(){
System.out.println("执行方法后");
}
}

然后使用xml的方式,配置如下,

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"
default-lazy-init="true"> <description>Spring公共配置</description>
<!--AOP配置-->
<aop:config>
<!--定义一个切点-->
<aop:pointcut id="p" expression="execution(* com.my.template.service.UserService.*(..))"/>
<!--定义一个切面-->
<aop:aspect id="a" ref="log">
<!--前置通知-->
<aop:before method="before" pointcut-ref="p"></aop:before>
<!--后置通知-->
<aop:after-returning method="after" pointcut-ref="p"></aop:after-returning>
</aop:aspect>
</aop:config>
</beans>

上面使用<aop:config></aop:config>标签进行aop的配置,在该标签内使用<aop:pointcut></aop:ponitcut>声明一个切点,又使用<aop:aspect></aop:aspect>定义了切面,切面中的ref是对通知类的引用,这里使用的是spring容器的中的bean,前面说到前面中包含了通知,所以下面定义了前置和后置通知,分别指定了通知类中的不同方法,下面看具体的测试方法,我这里使用的是springboot环境进行的测试,所以在启动类上加了导入配置文件的,

BootServer.java

package com.my.template;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.ImportResource; /**
* 启动类
* @date 2022/6/3 21:32
*/
@SpringBootApplication()
@ImportResource(value = {"applicationContext.xml"})
public class BootServer {
public static void main(String[] args) {
try {
SpringApplication.run(BootServer.class);
}catch (Exception e){
e.printStackTrace();
} }
}

看下启动结果,

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.aspectj.AspectJPointcutAdvisor#0':
Cannot create inner bean '(inner bean)#14c01636' of type [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice] while setting constructor argument;
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#14c01636':
Lookup method resolution failed; nested exception is java.lang.IllegalStateException:
Failed to introspect Class [org.springframework.aop.aspectj.AbstractAspectJAdvice] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:389) ~[spring-beans-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.

可以看到启动报错,提示无法创建“org.springframework.aop.aspectj.AspectJMethodBeforeAdvice”,那就是说在applicationContext.xml中配置的,

<aop:aspect id="a" ref="log">
<!--前置通知-->
<aop:before method="before" pointcut-ref="p"></aop:before>
<aop:after-returning method="after" pointcut-ref="p"></aop:after-returning>
</aop:aspect>

前缀通知失败,也就是说<aop:before>标签会被解析为AspectJ中的某些类,看下AspectJMethodBeforeAdvice

其类上注释有“Spring AOP advice that wraps an AspectJ before method”,也就是说这个类会包裹一个AspectJ的@Before通知的方法,看其父类AbstractAspectJAdvice,在该类中有对Aspect中类的引用,

所以这里需要引入AspectJ的依赖,也就是前边提到的,

<!--使用springAOP需要引入该依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>

再次启动项目无报错,测试结果如下,

2022-08-12 20:00:56.293  INFO 20708 --- [nio-9099-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 4 ms
执行方法前
保存user对象到数据库:User{name='张三', id='1'}
执行方法后

也就是说明配置的AOP成功了。有的小伙伴会说,使用springAOP必须要引入AspectJ的依赖吗,不是的。

2、不依赖AspectJ使用springAOP

前边,我们演示了使用XML的方式配置AOP,细心的小伙伴发现了这种方式依赖了AspectJ,那么有没有不使用AspectJ的,有的,springAOP提供了MethodBeforeAdvice、AfterReturningAdvice、MethodInterceptor、ThrowsAdvice可以分别对应@Before、@AfterReturning、@Around、@AfterThrowing,下面看不使用AspectJ怎么使用springAOP,

LogBeforeAdvice.java

package com.my.template.aop;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component; import java.lang.reflect.Method;
/**
* @date 2022/8/9 15:59
*/
@Component
public class LogBeforeAdvice implements MethodBeforeAdvice {
@Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("执行:"+o.getClass().getName()+"的"+method.getName()+"方法");
}
}

另外一个,LogAfterAdvice.java

package com.my.template.aop;

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component; import java.lang.reflect.Method;
/**
* @date 2022/8/9 16:07
*/
@Component
public class LogAfterAdvice implements AfterReturningAdvice {
@Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target)
throws Throwable {
System.out.println("执行结束:"+target.getClass().getName()+"方法"+method.getName());
}
}

上面定义了两个通知类,下面看具体配置,applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"
default-lazy-init="true"> <description>Spring公共配置</description> <!--springAOP的配置-->
<aop:config>
<!--切点-->
<aop:pointcut id="p" expression="execution(* com.my.template.service.UserService.*(..))"/>
<!--通知-->
<aop:advisor advice-ref="logBeforeAdvice" pointcut-ref="p"></aop:advisor>
<aop:advisor advice-ref="logAfterAdvice" pointcut-ref="p"></aop:advisor>
</aop:config>
</beans>

看下测试结果,

2022-08-13 14:39:13.124  INFO 25116 --- [nio-9099-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 18 ms
执行:com.my.template.service.UserService的saveUser方法
保存user对象到数据库:User{name='张三', id='1'}
执行结束:com.my.template.service.UserService方法saveUser

看到上面的测试结果,大家明白了吧,同样可以不依赖AspectJ使用springAOP,但是有个不好的地方,那就是每次都需要实现相应的接口,也就是上面提到的MethodBeforeAdvice、AfterReturningAdvice、MethodInterceptor、ThrowsAdvice,不如使用AspectJ简单。

3、使用AspectJ注解

springAOP利用AspectJ的注解完成了其注解的功能,下面看下怎么使用,先定义一个切面(@Aspect),

LogAspect.java

package com.my.template.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component; /**
* @date 2022/8/11 14:12
@Component注解不要忘,该注解的意思是将该切面交给spring管理
*/
@Component
@Aspect
public class LogAspect {
/**切点
*/
@Pointcut("execution(* com.my.template.service.UserService.*(..))")
public void pointCut(){ }
/**前置通知
*/
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint){
System.out.println("方法执行前11");
}
/**后置通知
*/
@AfterReturning(value = "pointCut()")
public void after(JoinPoint joinPoint){
System.out.println("方法执行后");
}
}

上面是一个切面,看下怎么使用,如果是使用XML的方式,记得要在XML中配置如下,

applicationContext.xml

    <!-- 开启自动切面代理-->
<aop:aspectj-autoproxy/>

此配置相当于@EnableAspectJAutoProxy,在springboot的环境中这两个都不需要配置,这是为什么?后边会讲

下面看我的启动类,

启动类上既没有引入applicationContext.xml也没用加@EnableAspectJAutoProxy,但是测试结果是,

从结果来看AOP起作用了。

三、总结

本文主要分享了AOP、springAOP、AspectJ三者的关系,以及springAOP在使用注解的过程中其实是借用了AspectJ的注解。另外还存有一个问题,springboot下需要使用@EnableAspectJAutoProxy注解吗,下期分享,敬请期待!

梳理清楚springAOP,轻松面向切面编程的更多相关文章

  1. spring-AOP(面向切面编程)-xml方式配置

    AOP是针对面向对象编程的一种补充,有时使用面向对象不能很好完成一些额外的功能业务时,可以采用AOP来进行补充. AOP术语: 切面(Aspect) 切面是用于编写切面逻辑的一个类,这个类很类似于JD ...

  2. spring-AOP(面向切面编程)-注解方式配置

    项目结构: 切面类: package edu.nf.ch12.service.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj ...

  3. JavaWeb_(Spring框架)SpringAOP面向切面编程

    SpringAOP:面向切面编程(面向fifter编程) 通俗易懂术语:所有纵向重复的代码,我们提取成横向的代码 以下文章内容参考知乎:从0带你学习SpringAOP,彻底的理解AOP思想 传送门 1 ...

  4. SpringAOP 面向切面编程

    AOP的相关概念 AOP:全称是 Aspect Oriented Programming 即:面向切面编程. 简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改 ...

  5. Spring-AOP面向切面编程

    AOP是面向切面编程,区别于oop,面向对象,一个是横向的,一个是纵向. 主要解决代码分散和混乱的问题. 1.概念: 切面:实现AOP共有的类 通知:切面类中实现切面功能的方法 连接点:程序被通知的特 ...

  6. SpringAOP面向切面编程

    Spring中三大核心思想之一AOP(面向切面编程): 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的 ...

  7. 了解并使用springAOP(面向切面编程)

    Aop是干嘛的为什么要使用它 在业务系统中,总有一些散落,渗透到系统的各处且不得不处理的事情,这些穿插在既定业务中的操作就是所谓的“横切逻辑”,也称切面, 我们怎样才不受这些附加要求的干扰,专心于真正 ...

  8. spring入门(四)【面向切面编程】

    开发过程中很多时候会用到日志.事务等操作,这些操作如果要写在业务代码中会相当麻烦,这时就会用到面向切面编程(AOP),AOP作为一种编程思想,和OOP有着不同的侧重点,面向对象侧重于万事万物皆对象,而 ...

  9. 浅谈Spring AOP 面向切面编程 最通俗易懂的画图理解AOP、AOP通知执行顺序~

    简介 我们都知道,Spring 框架作为后端主流框架之一,最有特点的三部分就是IOC控制反转.依赖注入.以及AOP切面.当然AOP作为一个Spring 的重要组成模块,当然IOC是不依赖于Spring ...

随机推荐

  1. 安装Suberversion[SVN]到CentOS(YUM)

    运行环境 系统版本:CentOS Linux release 7.3.1611 (Core) 软件版本:Suberversion-1.7.14 硬件要求:无 安装过程 1.安装YUM-EPEL源 Su ...

  2. 【Azure 应用服务】NodeJS Express + MSAL 应用实现AAD登录并获取AccessToken -- cca.acquireTokenByCode(tokenRequest)

    问题描述 在上一篇博文 "[Azure 应用服务]NodeJS Express + MSAL 应用实现AAD集成登录并部署在App Service Linux环境中的实现步骤"中, ...

  3. JZOJ 5409 Fantasy & NOI 2010 超级钢琴 题解

    其实早在 2020-12-26 的比赛我们就做过 5409. Fantasy 这可是紫题啊 题目大意 给你一个序列,求长度在 \([L,R]\) 区间内的 \(k\) 个连续子序列的最大和 题解 如此 ...

  4. Amazon 消息订阅对接

    亚马逊的api 谁用谁知道...... 除了坑还是坑 头疼一周整出来,分享给铁汁们 amazon 的订阅思维,我只能说外国人脑回路有点长 下面就讲讲具体流程步骤: 第一步: 参照官方教程:设置通知(A ...

  5. 在Visual Studio 2019中使用scanf报错C4996解决办法

    错误警告信息 错误C4996 'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To ...

  6. windiws下安装Composer

    1.先下载Composer-Setup.exe,下载地址:下载Composer .会自动搜索php.exe的安装路径,如果没有,就手动找到php路径下的php.exe. 2.在PHP目录下,打开php ...

  7. 【Redis】Redis Cluster初始化及PING消息的发送

    Cluster消息类型定义 #define CLUSTERMSG_TYPE_PING 0 /* Ping消息类型,节点间进行通信交换信息的消息 */ #define CLUSTERMSG_TYPE_P ...

  8. 过年了,基于Vue做一个消息通知组件

    前言 今天除夕,在这里祝大家新年快乐!!!今天在这个特别的日子里我们做一个消息通知组件,好,我们开始行动起来吧!!!项目一览 效果很简单,就是这种的小卡片似的效果. 我们先开始写UI页面,可自定义消息 ...

  9. java web 三层架构设计

    界面层(表示层):用户看得到的,可以通过此与服务器交互 业务逻辑层:处理业务逻辑. 数据访问层:操作数据存储文件

  10. RPA供应链管制单修改机器人

    背景:供应链环节中,研发物料时而因为市场缺货等原因无法采购,资材部需登入系统修改物料管制单. 操作流程:登录PDM系统中读取数据.登录ERP系统中更新数据. 人工操作:每日耗时3.5小时,出现一定比例 ...