AspectJ框架

概述

  • AspectJ是一个优秀的面向切面编程的框架,他扩展了java语言,提供了强大的切面实现
  • 本身是java语言开发的,可以对java语言面向切面编程进行无缝扩展

AOP常见术语分析

  • 切面:那些重复的,公共的,通用的功能被称为切面,例如,日志,事务,权限等功能

  • 连接点:实际就是目标方法,因为在目标方法中要实现业务功能和切面功能的整合

  • 切入点(Pointcut):用来指定切入的位置,切入点可以是一个目标方法,可以是一个类中的所有方法,还可以是某个包下的所有类中的方法等

  • 目标对象:操作谁,谁就是目标对象,往往是业务接口的实现类对象

  • 通知(Advice):来指定切入的时机是在目标方法执行前,执行后,出错时,还是环绕目标方法来切入切面功能

AspectJ常见通知类型

  • 前置通知:@Before
  • 后置通知:@AfterReturning
  • 环绕通知:@Around
  • 最终通知:@After
  • 定义切入点:@Pointcut(了解)

AspectJ的切入点表达式

公式

  • 关键字:切入点表达式由execution关键字引出,后面括号内跟需要切入切面功能的业务方法的定位信息

  • 规范的公式:execution( 访问权限 方法返回值 方法声明(参数) 异常类型 )

  • 简化后的公式:execution( 方法返回值 方法声明(参数) )

示例

  • 切入点表达式及其定位的需要切入切面功能的业务方法
1. execution(public * *(..) ):任意的公共方法
2. execution(* set*(..) ):任何以set开始的方法
3. execution(* com.xyz.service.impl.*.*(..)):com.xyz.service.impl包下的任意类的任意方法
4. execution(* com.xyz.service..*.*(..)):com.xyz.service包及其子包下的任意类的任意方法
5. execution(* *..service.*.*(..)):service包下的任意类的任意方法,注意service包前可以有任意包的子包
6. execution(* *.service.*.*(..)):service包下的任意类的任意方法,注意:service包前只能有一个任意的包

@Before通知

图解

  • 重点想表达的是:前置通知最多获取到目标业务方法的方法签名等前置信息,获取不到目标方法的返回值,因为前置切面在目标方法前执行
  • 关于前置切面方法可以获取到的目标业务方法的信息,本文后面讨论JoinPoint类型的参数时会讨论

maven项目的pom.xml

  • 重点是添加spring-context和spring-aspects依赖
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId>
<artifactId>ch08-spring-aspectj</artifactId>
<version>1.0.0</version> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties> <dependencies>
<!-- junit测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency> <!-- 添加spring-context依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency> <!-- 添加spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.22</version>
</dependency>
</dependencies> <build>
<!-- 添加资源文件的指定-->
<resources>
<resource>
<!-- 目标目录1 -->
<directory>src/main/java</directory>
<includes>
<!-- 被包括的文件类型 -->
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<!-- 目标目录2 -->
<directory>src/main/resources</directory>
<includes>
<!-- 被包括的文件类型 -->
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build> </project>

业务接口

  • 业务接口:SomeService
package com.example.s01;

/**
* 定义业务接口
*/
public interface SomeService {
//定义业务功能
default String doSome(int orderNums){return null;}
}

业务实现类

  • 业务实现类:SomeServiceImpl
package com.example.s01;

/**
* 业务功能实现类
*/
public class SomeServiceImpl implements SomeService{
@Override
public String doSome(int orderNums) {
System.out.println("---- 业务功能 ----");
System.out.println("预定图书: " + orderNums + " 册");
return "预定成功!";
}
}

切面类

  • 切面类:SomeServiceAspect
package com.example.s01;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; /**
* 切面类
*/ //添加@Aspect注释,表明切面类交给AspectJ这一面向切面编程框架管理
@Aspect
public class SomeServiceAspect {
/**
* a. 切面功能由切面类中的切面方法负责完成
*
* b. 前置通知的切面方法规范
* 1.访问权限是public
* 2.方法的返回值是void
* 3.方法名称自定义
* 4.方法没有参数,如果有参数也只能是JoinPoint类型
* 5.必须使用注解:@Before,来声明切入的时机是前切和切入点的信息
* 参数:value,用来指定切入点表达式
*
* c.前切示例
* 目标方法(即业务实现类中的方法):public String doSome(int orderNums)
*/
@Before(value = "execution(public String com.example.s01.SomeServiceImpl.doSome(int))")
public void myBefore(){
System.out.println("前置通知: 查询图书是否有剩余");
}
}

applicationContext.xml

  • 绑定业务功能和切面功能
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 创建业务功能对象 -->
<bean id="someServiceImpl" class="com.example.s01.SomeServiceImpl"/> <!-- 创建切面功能对象 -->
<bean id="someServiceAspect" class="com.example.s01.SomeServiceAspect"/> <!-- 绑定业务功能和切面功能-->
<aop:aspectj-autoproxy/> </beans>

测试

package com.example.test;

import com.example.s01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestBeforeAspect {
//测试前置切面功能
@Test
public void testBeforeAspect(){
//创建Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml"); //实际获取的是业务实现类对象的jdk动态代理对象
SomeService agent = (SomeService) ac.getBean("someServiceImpl"); //测试agent类型
System.out.println("agent类型: " + agent.getClass()); //代理对象调用业务功能(切面功能 + 被代理对象的传统业务功能)
String res = agent.doSome(10); //接住目标对象目标业务方法的返回值
System.out.println("业务执行结果: " + res);
}
}

测试输出

  • 这里特意输出了一下agent变量的类型,可见此时底层使用的是jdk动态代理来获取代理对象
  • 前置切面功能成功在业务功能前执行
agent类型: class com.sun.proxy.$Proxy10
前置通知: 查询图书是否有剩余
---- 业务功能 ----
预定图书: 10 册
业务执行结果: 预定成功! Process finished with exit code 0

扩展测试1

  • 下面所示的切面类中的myBefore切面方法中的切入点表达式,限定的目标方法的范围太小,
execution(public String com.example.s01.SomeServiceImpl.doSome(int))	//目标方法的限定范围太小

业务接口

  • SomeService新增方法show()
    //新增一个业务功能
default void show(){}

业务实现类

  • SomeServiceImpl对show()方法进行实现
    @Override
public void show() {
System.out.println("新增的show()方法被调用.....");
}

测试

    //测试切入点表达式对新增的业务方法是否起作用
@Test
public void testBeforeAspect02(){
//创建Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
//获取业务实现类的jdk动态代理对象
SomeService agent = (SomeService) ac.getBean("someServiceImpl");
//代理对象调用业务功能
agent.show();
}

测试输出

  • 可以看到由于切入点表达式划定的目标方法的范围并没有包括新增的业务方法,所以新增的show()方法并没有被成功的切入前置切面
新增的show()方法被调用.....

Process finished with exit code 0

修改切入点表达式

  • 将上述切面类(指:SomeServiceAspect)中的切面方法中的切入点表达式做如下修改,此时限定的范围为:com.example.s01包下的所有类的所有方法
execution(* com.example.s01.*.*(..))	//限定的范围不大不小,开发中常用
  • 再次做上述测试:testBeforeAspect02(),测试结果如下,此时前置切面的范围包括show()方法,前置切面成功切入
前置通知: 查询图书是否有剩余
新增的show()方法被调用..... Process finished with exit code 0

再次修改切入点表达式

  • 上述切入点表达式还可以做如下修改,指:com.example.s01包及其子包和当前路径下的所有类中的所有方法
execution(* com.example.s01..*(..))	//不常用,了解即可
  • 或者做如下修改,指:项目中的所有方法
execution(* *(..))	//限定范围太大,不常用,了解即可

扩展测试2

jdk动态代理

  • 如下是上述applicationContext.xml中绑定业务功能和切面功能的标签,使用此标签默认使用的是jdk动态代理,接住代理对象,需要用接口类型
    <!-- 绑定业务功能和切面功能-->
<aop:aspectj-autoproxy/>
    //使用业务接口的类型去接住动态代理对象
SomeService agent = (SomeService) ac.getBean("someServiceImpl");
  • 此时如果用实现类的类型SomeServiceImpl去接,则报错:类型转换错误。因为此时agent是jdk动态代理类型,不再是实现类的类型

CGLib子类代理

  • 将applicationContext.xml中的代理标签做如下修改,此时使用的是CGLib子类代理
    <!-- 绑定业务功能和切面功能-->
<aop:aspectj-autoproxy proxy-target-class="true"/>

测试

  • 由于底层使用的是子类来扩展业务实现类(是父类,被扩展),可以用父类型去接代理对象(子类型),因为父类指向子类型,子类型重写了父类型中的方法,调用时还是调用子类中扩展后的方法
    //测试代理对象的类型
@Test
public void testBeforeAspect03(){
//创建Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
//CGLib子类代理,用实现类(父类)的类型来接
SomeServiceImpl agent = (SomeServiceImpl) ac.getBean("someServiceImpl");
//调用业务功能
agent.show();
}

测试输出

  • 成功使用父类型接住代理对象(子类),使得切面功能在业务功能前调用
前置通知: 查询图书是否有剩余
新增的show()方法被调用..... Process finished with exit code 0

注意

  • 当使用CGLib子类代理时也可以用业务接口来接住子类代理对象,因为子类代理对象的父类(也就是被扩展的类)是接口的实现类,可以用接口指向实现类,自然也可以指向实现类的子类

小结

  • 综上所述,不管使用JDK动态代理还是CGLib子类代理,使用业务接口的类型去接住代理对象总是可以的

基于注解的@Before

业务实现类

  • 添加@Service注解
/**
* 业务功能实现类
*/
@Service
public class SomeServiceImpl implements SomeService{
//......
}

切面类

  • 添加@Component注解
//切面类交给Aspectj框架管理
@Aspect
@Component
public class SomeServiceAspect {
//......
}

applicationContext.xml

  • 添加包扫描
    <!-- 添加包扫描 -->
<context:component-scan base-package="com.example.s01"/>

测试

    //测试代理对象的类型
@Test
public void testBeforeAspect03(){
//创建Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
//CGLib子类代理,用实现类(父类)的类型来接
SomeServiceImpl agent = (SomeServiceImpl) ac.getBean("someServiceImpl");
//调用业务功能
agent.show();
}

测试输出

前置通知: 查询图书是否有剩余
新增的show()方法被调用..... Process finished with exit code 0

前置通知的JoinPoint参数

切面类

  • 为上述切面类中的切面方法传入JoinPoint参数
    @Before(value = "execution(* com.example.s01.*.*(..))")
public void myBefore(JoinPoint joinPoint){
System.out.println("目标方法签名: " + joinPoint.getSignature());
System.out.println("目标方法参数: " + Arrays.toString(joinPoint.getArgs()));
System.out.println("前置通知: 查询图书是否有剩余");
}

测试

    //测试前置切面功能
@Test
public void testBeforeAspect(){
//创建Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
//获取业务实现类的CGLib动态代理对象
SomeService agent = (SomeService) ac.getBean("someServiceImpl");
//测试agent类型
System.out.println("agent类型: " + agent.getClass());
//代理对象调用业务功能
String res = agent.doSome(10); //接住目标对象目标业务方法的返回值
System.out.println("业务执行结果: " + res);
}

测试输出

  • 此时的代理标签使用的是CGLib子类代理
  • 前置通知成功在目标业务方法前执行
  • 成功获取到目标业务方法的方法签名和参数
agent类型: class com.example.s01.SomeServiceImpl$$EnhancerBySpringCGLIB$$36b23096
目标方法签名: String com.example.s01.SomeServiceImpl.doSome(int)
目标方法参数: [10]
前置通知: 查询图书是否有剩余
---- 业务功能 ----
预定图书: 10 册
业务执行结果: 预定成功!

Spring 10: AspectJ框架 + @Before前置通知的更多相关文章

  1. iOS 10 UserNotification框架解析 – 本地通知

    iOS 10以前的通知比较杂乱,把本地通知和远程通知分开了,诞生了许多功能类似的API,很容易让初学者犯迷糊.而iOS 10的通知把API做了统一,利用独立的UserNotifications.fra ...

  2. 在Spring整合aspectj实现aop的两种方式

    -----------------------------基于XML配置方案目标对象接口1 public interface IUserService { public void add(); pub ...

  3. Spring学习之旅(八)Spring 基于AspectJ注解配置的AOP编程工作原理初探

    由小编的上篇博文可以一窥基于AspectJ注解配置的AOP编程实现. 本文一下未贴出的相关代码示例请关注小编的上篇博文<Spring学习之旅(七)基于XML配置与基于AspectJ注解配置的AO ...

  4. spring的AspectJ基于XML和注解(前置、后置、环绕、抛出异常、最终通知)

    1.概念 (1)AspectJ是一个基于Java语言的AOP框架 (2)Spring2.0以后新增了对AspectJ切入点表达式的支持 (3)AspectJ是AspectJ1.5的新增功能,通过JDK ...

  5. 10 Spring框架 AOP (三) Spring对AspectJ的整合

    上两节我们讲了Spring对AOP的实现,但是在我们的开发中我们不太使用Spring自身的对AOP的实现,而是使用AspectJ,AspectJ是一个面向切面的框架,它扩展了Java语言.Aspect ...

  6. [转载] Spring框架——AOP前置、后置、环绕、异常通知

    通知类型: 步骤: 1. 定义接口 2. 编写对象(被代理对象=目标对象) 3. 编写通知(前置通知目标方法调用前调用) 4. 在beans.xml文件配置 4.1 配置 被代理对象=目标对象 4.2 ...

  7. [原创]java WEB学习笔记106:Spring学习---AOP的通知 :前置通知,后置通知,返回通知,异常通知,环绕通知

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  8. [Spring框架]Spring AOP基础入门总结二:Spring基于AspectJ的AOP的开发.

    前言: 在上一篇中: [Spring框架]Spring AOP基础入门总结一. 中 我们已经知道了一个Spring AOP程序是如何开发的, 在这里呢我们将基于AspectJ来进行AOP 的总结和学习 ...

  9. Spring AOP前置通知和后置通知

    Spring AOP AspectJ:Java社区里最完整最流行的AOP框架 在Spring2.0以上的版本中,可以使用基于AspectJ注解或基于XML配置的AOP 在Spring中启用Aspect ...

随机推荐

  1. atcoder abc 244

    atcoder abc 244 D - swap hats 给定两个 R,G,B 的排列 进行刚好 \(10^{18}\) 次操作,每一次选择两个交换 问最后能否相同 刚好 \(10^{18}\) 次 ...

  2. BUUCTF-FLAG

    FLAG 16进制打开没看到有什么东西,使用binwalk分离也没看到其他文件,猜测是否使用lsb隐写方式. StegSolve打开 可以看到是压缩包的文件头,save bin保存为zip文件解压 提 ...

  3. 用Typescript 的方式封装Vue3的表单绑定,支持防抖等功能。

    Vue3 的父子组件传值.绑定表单数据.UI库的二次封装.防抖等,想来大家都很熟悉了,本篇介绍一种使用 Typescript 的方式进行统一的封装的方法. 基础使用方法 Vue3对于表单的绑定提供了一 ...

  4. SAP Web Dynpro-集成消息

    您可以使用消息管理器将消息集成到消息日志中. 您可以使用Web Dynpro代码向导打开消息管理器. 您可以从工具栏中打开Web Dynpro代码向导. 当您的ABAP工作台处于更改模式或编辑视图或控 ...

  5. Linux yum的实现和配置

    使用yum或dnf解决rpm包的依赖关系. YUM:Yellowdog Update Modifier.是rpm的前端程序 作用:解决软件包之间的依赖关系 yum工作原理: yum 服务器存放rpm包 ...

  6. HashMap的实现原理?如何保证HashMap线程安全?

    A:HashMap简单说就是它根据建的hashcode值存储数据的,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历的顺序是不确定的. B:HashMap基于哈希表,底层结构由数组来实 ...

  7. Nginx通过bat文件快速启动停止

    新建文本文件NginxRun.bat.(名字无所谓,后缀名得是bat) 将以下代码复制到bat文件中即可. @echo off ::进入D盘 d: ::进入nginx目录 这里是自己的nginx目录 ...

  8. Ant Design Vue 走马灯实现单页多张图片轮播

    最近的项目有个需求是,这种单页多图一次滚动一张图片的轮播效果,项目组件库是antd 然而用了antd的走马灯是这样子的 我们可以看到官网给的api是没有这种功能,百度上也多是在css上动刀,那样也就毕 ...

  9. OneOS下调试支持的几种方式

    方法论 当我们遇到问题,应该怎么办?这不仅应用于程序开发,也是我们在生活中遇到问题的时候,应该想的事儿,怎么办!趁着此次机会,我好好想了七秒钟. 先问是不是问题,如果不是就不用解决了 如果确实是问题, ...

  10. Java.稀疏数组

    package array; public class demo06 { public static void main(String[] args) { //创建一个二维数组 11*11 int[] ...