作者:狮子也疯狂
专栏:《spring开发》
坚持做好每一步,幸运之神自然会驾凌在你的身上
目录
- 一. 🦁 前言
- 二. 🦁 常见概念
- 2.1 常见术语
- 2.2 AOP入门
- Ⅰ. 🐇 功能场景
- Ⅱ. 🐇 实现过程
- 2.3 通知类型
- Ⅰ. 🐇 编写通知方法
- Ⅱ. 🐇 编写切面
- Ⅲ. 🐇 测试
- 三. 🦁 切点表达式
- 3.1 使用语法
- 四. 🦁 总结
一. 🦁 前言
继上两篇文章,狮子总结了Spring中IOC的底层原理、基本使用以及注解使用。今天来细说一下Spring的另外一个重要原理——面向切面编程(Aspect Oriented Programming),即我们常说的AOP。它是实现功能统一维护的一种技术,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
二. 🦁 常见概念
AOP 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。
SpringAOP是基于动态代理的,如果要代理的对象实现了某个接口,那么SpringAOP就会使用JDK动态代理去创建代理对象
;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。
2.1 常见术语
名称 | 说明 |
---|---|
连接点(Joinpoint) | 指能被拦截到的点,在Spring中只有方法能被拦截 |
切点(Pointcut) | 指要对哪些连接点进行拦截,即被增强的方法。 |
通知(Advice) | 指拦截后要做的事情,即切点被拦截后执行的方法 |
切面(Aspect) | 切点+通知称为切面 |
目标(Target) | 被代理的对象 |
代理(Proxy) | 代理对象 |
织入(Weaving) | 生成代理对象的过程 |
2.2 AOP入门
现在来简单了解一下AOP的使用,SpringAOP中已经集成了AspectJ框架(应该是Java生态中最为完整的AOP框架了),我们使用该框架来实现一个简易的AOP功能。
Ⅰ. 🐇 功能场景
持久层的每个方法结束后都可以打印一条日志
Ⅱ. 🐇 实现过程
- 创建maven项目,并且引入以下依赖:
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.12</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
- 编写连接点(该处省略持久层接口)
@Repository
public class UserDaoImpl implements UserDao{
public void add(){
System.out.println("用户新增");
|
|
}
public void delete(){
System.out.println("用户删除");
}
public void update(){
System.out.println("用户修改");
}
}
- 编写通知类
public class MyAspectJAdvice {
// 后置通知
public void myAfterReturning() {
System.out.println("打印日志...");
}
}
- 配置切面
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan basepackage="com.jackie">
</context:component-scan>
<!-- 通知对象 -->
<bean id="myAspectJAdvice"
class="com.jackie.advice.MyAspectAdvice"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspectJAdvice">
<!-- 配置切点 -->
<aop:pointcut id="myPointcut"
expression="execution(*com.jackie.dao.UserDao.*(..))"/>
<!-- 配置通知 -->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
- 测试
public class UserDaoTest {
@Test
public void testAdd(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.add();
}
@Test
public void testDelete(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.delete();
}
@Test
public void testUpdate(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.update();
}
}
通过调用就会发现,每个方法都会打印语句:打印日志...
。这就是AOP的使用入门。
2.3 通知类型
我们通过上面的案例会发现,这里写了一个后置通知,那么有哪些类型的通知呢?我们来看看:
通知类型 | 描述 |
---|---|
前置通知 | 在方法执行前添加功能 |
后置通知 | 在方法正常执行后添加功能 |
异常通知 | 在方法抛出异常后添加功能 |
最终通知 | 无论方法是否抛出异常,都会执行该通知 |
环绕通知 | 在方法执行前后添加功能 |
我们通过案例来看看这些通知类型的实现以及使用
Ⅰ. 🐇 编写通知方法
通过构造MyAspectAdvice通知类,在里面编写通知方法。
// 通知类
public class MyAspectAdvice {
// 后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.println("切点方法名:" + joinPoint.getSignature().getName());
System.out.println("目标对象:" + joinPoint.getTarget());
System.out.println("打印日志" + joinPoint.getSignature().getName() + "方法被执行了!");
}
// 前置通知
public void myBefore() {
System.out.println("前置通知...");
}
// 异常通知
public void myAfterThrowing(Exception ex) {
System.out.println("异常通知...");
System.err.println(ex.getMessage());
}
// 最终通知
public void myAfter() {
System.out.println("最终通知");
}
// 环绕通知
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前");
Object obj = proceedingJoinPoint.proceed(); // 执行方法
System.out.println("环绕后");
return obj;
}
}
Ⅱ. 🐇 编写切面
<!-- 配置AOP -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspectJAdvice">
<!-- 配置切点 -->
<aop:pointcut id="myPointcut" expression="execution(* com.jackie.dao.UserDao.*(..))"/>
<!-- 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointcut"></aop:before>
<!-- 后置通知 -->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"/>
<!-- 异常通知 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="ex"/>
<!-- 最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
<!-- 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
</aop:aspect>
</aop:config>
Ⅲ. 🐇 测试
在这里就不阐述了,跟上面的案例是一样的。
三. 🦁 切点表达式
我们上面的几个案例中都用到了切点表达式,即:
使用AspectJ需要使用切点表达式配置切点的位置。
3.1 使用语法
访问修饰符 返回值 包名.类名.方法名(参数列表)
遵循以下习惯:
- 访问修饰符可以省略、返回值使用 * 代表任意类型、包名使用 * 表示任意包,多级包结构要写多个 * ,使用
*..
表示任意包结构- 类名和方法名都可以用 * 实现通配。
- 参数列表
基本数据类型直接写类型
引用类型写 包名.类名
*
表示匹配一个任意类型参数
..
表示匹配任意类型任意个数的参数- 全通配:
* *..*.*(..)
四. 🦁 总结
今天总结了AOP的基本使用方法。通过使用AspectJ框架来实现AOP的基本用法以及介绍了该框架的几种通知类型。今天的分享到这里,希望您喜欢!