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. 互联网公司实行目标管理(OKR)五点原则和基础

    下面从公司文化.组织架构.管理者.落地执行和区别绩效考核五个方面,讲述了如何在公司落地目标管理(OKR),这些是实施OKR的基础,也是原则,虽然写得比较简单,其实是我过去两年多不断观察.实践和摸索的总 ...

  2. 关于VHDL中case语句多执行语句的书写方式(转载stackoverflow.com并做翻译汇总)

    很多国内的教材对于case语句的讲解非常单一,比如: 1 [标号:]CASE 多值表达式 IS 2 WHEN 选择值 => 被赋值变量 <=赋值变量: 3 WHEN 选择值 => 被 ...

  3. Acwing787.归并排序

    Acwing787.归并排序 归并模板 归并排序,合二为一 题目链接:Acwing787.归并排序 #include<iostream> using namespace std; cons ...

  4. DirectX11 With Windows SDK--06 使用ImGui

    前言 Dear ImGui是一个开源GUI框架.除了UI部分外,本身还支持简单的键鼠交互.目前项目内置的是V1.87版本,大概半年时间会更新一次版本,并且对源码有小幅度调整. 注意:直接下载源码使用会 ...

  5. SAP 下拉框(选择屏幕)

    一.选择屏幕下拉框. DATA: g_vrmid TYPE vrm_id, "id of value set gt_vlist TYPE vrm_values, "internal ...

  6. nginx 出现An error occurred错误

    原因是我nginx中conf文件的配置里面 location中的 这一块内容是 #注释的那两行 所以报错出现这个错误. 后来将这两行注释掉,改成这两个就好了. root html; index ind ...

  7. 使用纯 CSS 实现超酷炫的粘性气泡效果

    最近,在 CodePen 上看到这样一个非常有意思的效果: 这个效果的核心难点在于气泡的一种特殊融合效果. 其源代码在:CodePen Demo -- Goey footer,作者主要使用的是 SVG ...

  8. 不是第七代的 Win 7

    贡献者:历史上的今天 Windows 7 是由微软公司(Microsoft)2009 年 10 月 22 日发布的桌面端操作系统,它影响了每个行业的方方面面,以至于很多人仍然在日常生活和工作中使用它. ...

  9. static关键字和代码块

    static关键字 static修饰的变量称为静态变量/共享变量/类变量 用于修饰类的成员,如成员变量.成员方法以及代码块等,内static修饰的成员具备一些特殊性 1.静态变量 在java类中使用s ...

  10. Win10系统下基于Docker构建Appium容器连接Android模拟器Genymotion完成移动端Python自动化测试

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_196 Python自动化,大概也许或者是今年最具热度的话题之一了.七月流火,招聘市场上对于Python自动化的追捧热度仍未消减,那 ...