一:什么是AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
总的来说,AOP就是一编程规范,目的是在不改变方法源代码的基础上对方法进行功能增强。
二:AOP快速入门
2.1导入AOP坐标
在pom.xml中导入aop坐标
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.31</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>
2.2定义dao接口和实现类
BookDao接口中:
package com.lcyy.dao; public interface BookDao { void read(); }
实现类:注意要在BookDaoImpl方法上加入注解@Repository表示为交给Spring的一个组件
import com.lcyy.dao.BookDao; import org.springframework.stereotype.Repository; @Repository public class BookDaoImpl implements BookDao { @Override public void read() { System.out.println("BookDao AOP 实现"); } }
2.3定义通知类
建立一个aop的包,在包下建立一个MyAdvice的通知类
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Component//放在普通类上,交给ioc容器管理 @Aspect//定义这个类为切面类 public class MyAdvice { //定义连接点 @Pointcut("execution(void com.lcyy.dao.BookDao.read())") private void pc(){} //定义通知 @Before("pc()") public void testAdvice(){ System.out.println("执行前的时间毫秒值:"); System.out.println(System.currentTimeMillis()); } }
注意:
1.在MyAdvice类上要加入注解:@Aspect表示定义为一个切面类,@Component表示交给ioc容器管理。
2.@Pointcut注解要求配置在方法上方,("execution(void com.lcyy.dao.BookDao.read())")为切入点表达式,execution为关键字,
void为返回值 com.lcyy.dao.BookDao.read()为com.lcyy包下的dao包下的BookDao接口中的read()方法。
3.@Before("pc()") 其中@Before为前置通知,表示为在方法执行前执行。关于通知的几种类型,下面会详细讲解。其中 "pc()" 表示将连接点和通知捆绑起来,注意方法名必须相同。
2.4在配置类中进行Spring注解包扫描和开启AOP功能
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @ComponentScan("com.lcyy") @EnableAspectJAutoProxy//开启aop public class SpringConfig { }
其中注解 @EnableAspectJAutoProxy 表示开启AOP通知,交给IOC容器管理
2.5定义测试类测试结果
import com.lcyy.config.SpringConfig; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class BookDaoTest { public static void main(String[] args) { //获取容器 ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao = ctx.getBean(BookDao.class); bookDao.read(); } }
测试结果:
三:AOP工作流程
- Spring容器启动
- 读取所有切面配置中的切入点
- 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建原始对象
- 匹配成功,创建原始对象(目标对象)的代理对象
- 获取bean执行方法
- 获取的bean是原始对象时,调用方法并执行,完成操作
- 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
验证AOP的代理对象,在刚才测试类中获取
import com.lcyy.config.SpringConfig; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class BookDaoTest { public static void main(String[] args) { //获取容器 ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); BookDao bookDao = ctx.getBean(BookDao.class); bookDao.read(); System.out.println(bookDao.getClass()); } }
测试结果:
其中:class com.sun.proxy.$Proxy19 就是为代理对象的类名
四:AOP的切入点表达式
4.1语法格式
描述方法一:执行com.lcyy.dao包下的BookDao接口中的无参数read方法
execution(void com.lcyy.dao.BookDao.read())
描述方法二:执行com.lcyy.dao包下的BookDaoImpl实现类中的无参数read方法
execution(void com.lcyy.dao.BookDaoImpl.read())
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
execution(public User com.itheima.service.UserService.findById(int))
其中:
- 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
- 访问修饰符:public,private等,可以省略
- 返回值:写返回值类型
- 包名:多级包使用点连接
- 类/接口名:
- 方法名:
- 参数:直接写参数的类型,多个类型用逗号隔开
- 异常名:方法定义中抛出指定异常,可以省略
4.2通配符
(*):单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
(..):多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
常用的通配符表达式为:
@Pointcut("execution(* com.lcyy.dao.*.*(..)")
表示任意的访问修饰符 在com.lcyy.dao包下的任意类中的所有方法(..)表示可以有形参也可以没有形参。
五:AOP通知类型
AOP通知共分为5种类型
- 前置通知:在切入点方法执行之前执行
- 后置通知:在切入点方法执行之后执行,无论切入点方法内部是否出现异常,后置通知都会执行。
- 环绕通知(重点):手动调用切入点方法并对其进行增强的通知方式。
- 最终通知:在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行。
- 异常通知:在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行。
5.1前置通知
- 名称:@Before
- 类型:==方法注解==
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
- 案例代码:
//前置通知 @Before("execution(void com.lcyy.dao.BookDao.write())") public void testAdvice2(){ System.out.println("我是前置通知"); }
5.2后置通知
- 名称:@AfterReturning
- 类型:==方法注解==
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
- 案例代码:
@AfterReturning("execution(void com.lcyy.dao.BookDao.write())") public void testAdvice4(){ System.out.println("我是后置通知"); }
5.3最终通知
- 名称:@After
- 类型:==方法注解==
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
- 案例代码:
@After("execution(void com.lcyy.dao.BookDao.write())") public void testAdvice3(){ System.out.println("我是最终通知"); }
5.4异常通知
- 名称:@AfterThrowing
- 类型:==方法注解==
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
@AfterThrowing("execution(void com.lcyy.dao.BookDao.write())") public void testAdvice5(){ System.out.println("我是异常通知"); }
在以上四个通知后,我们在测试类中测试以上通知
注意:书写了异常通知,却没有执行,是因为简单的代码实现没有异常。
测试异常通知:
在BookDaoImpl 的实现类中书写测试异常的代码
@Override public void write() { System.out.println("BookDao AOP 实现"); int a = 10/0; System.out.println("a = " + a); }
测试结果:
5.5环绕通知
- 名称:@Around(重点,常用)
- 类型:==方法注解==
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
- 案例代码:
@Around("execution(void com.lcyy.dao.BookDao.jiyi())") public Object testJiyi(ProceedingJoinPoint PJ) throws Throwable { System.out.println("我时前置通知"); Object Oj = PJ.proceed(); System.out.println("我是后置通知"); return Oj; }
测试结果:
注意:
- 环绕通知方法形参必须是ProceedingJoinPoint,表示正在执行的连接点,使用该对象的proceed()方法表示对原始对象方法进行调用,返回值为原始对象方法的返回值。
- 环绕通知方法的返回值建议写成Object类型,用于将原始对象方法的返回值进行返回,哪里使用代理对象就返回到哪里。
六:总结
AOP在spring中是一个很重要的概念,理解和运用AOP在以后的业务开发中有很重要的作用!