什么是AOP:
AOP:全称是Aspect Oriented Programming 即:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
简单地说它就是把我们程序的重复代码抽出来,在需要执行的时候,使用动态代理技术,在不修改源码的基础上,对我们已有方法进行增强。
AOP的优势:
减少重复代码
提高开发效率
维护方便
AOP的实现方式:
使用动态代理技术
Spring中的AOP:
AOP相关术语:
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。
Pointcut(切入点):
所谓切入点是指的是我们要对哪些Joinpoint进行拦截的定义
Advice(通知/增强):
所谓通知是指拦截到Joinpoint之后要做的事情就是通知
通知的类型:前置通知、后置通知、异常通知、最终通知、环绕通知
Target(目标对象):
代理的目标对象
Weaving(织入):
织入是指把增强应用到目标对象来创建新的对象的过程
Proxy(代理):
一个类被AOP织入增强后,就产生了一个结果代理类
Aspect(切面):
是切入点和增强的结合
学习Spring 中的AOP要明确的事:
开发阶段(我们做的):
编写核心业务代码(开发主线)
把公共代码抽取出来,制作成通知(开发阶段最后再做)
在配置文件中,声明切入点和通知的关系,即切面
运行阶段(Spring做的):
Spring框架监控切入点方法的执行,一旦监控到切入点的方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
Spring基于xml的AOP配置:
在maven依赖关系配置文件中添加dependency依赖和spring-context的依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
aspectjweaver用于解析 <aop:before>中 pointcut="execution()" 中的表达式
创建好业务层接口(IAccountService)
package com.leon.service;
//账户的业务层接口
public interface IAccountService {
void saveAccount();
void updateAccount(int i);
int deleteAccount();
}
业务层接口实现类(AccountService) 模拟实现IAccountService接口的方法
package com.leon.service.impl;
import com.leon.service.IAccountService;
//账户的业务层实现类
public class AccountService implements IAccountService {
public void saveAccount() {
System.out.println("执行了保存");
}
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
通知类(Logger) 添加一个计划要在切入点方法执行之前执行的通知方法
package com.leon.utils;
//用于记录日志的工具类,它里面提供了公共的代码
public class Logger {
//用于打印日志,计划在切入点方法之前执行
public void printLog(){
System.out.println("Logger类中的printLogger方法开始记录日志了");
}
}
创建bean.xml并引入带有aop声明的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
http://www.springframework.org/schema/aop/spring-aop.xsd">
配置IOC和AOP:
<!--配置spring的IOC,把service对象配置进来 -->
<bean id="accountService" class="com.leon.service.impl.AccountService"></bean>
<!--spring中基于xml的AOP配置步骤
1、把通知bean也交给spring来管理(就是把通知的bean也加进来)
2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面
id:给切面提供一个唯一的标识
ref:指定通知类bean的ID
4、在aop:aspect标签的内部使用对应的标签来配置通知的类型
我们现在的示例是让printLog方法在切入点方法执行之前执行,所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点的表达式,建立通知与切入点的关系
切入点表达式的写法:execution(表达式)
表达式:访问修饰符 返回值 包名.包名....类名.方法名(参数列表)
例如:public void com.leon.service.impl.AccountService.saveAccount()
-->
<!--1.配置通知类的bean -->
<bean id="logger" class="com.leon.utils.Logger"></bean>
<!--2、AOP的配置 -->
<aop:config>
<!--3、配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,并建立通知方法和切入点方法的关联 -->
<aop:before method="printLog" pointcut="execution(public void com.leon.service.impl.AccountService.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
创建测试类测试一下是否使用了代理增强方法
//测试AOP的配置
public class AOPTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService", IAccountService.class);
as.saveAccount();
as.updateAccount(1);
as.deleteAccount();
}
}
控制台输出结果:
Logger类中的printLogger方法开始记录日志了
执行了保存
执行了更新1
执行了删除
测试结束,可以看到,只有我们AOP配置的保存方法在执行之前添加了增强方法
pointcut 声明切入点的表达式拓展:
切入点表达式的写法:execution(表达式) 表达式:访问修饰符 返回值 包名.包名....类名.方法名(参数列表) 例如:public void com.leon.service.impl.AccountService.saveAccount() 访问修饰符可以省略: void com.leon.service.impl.AccountService.saveAccount() 返回值可以使用通配符,表示任意返回值: * com.leon.service.impl.AccountService.saveAccount() 包名可以使用通配符表示任意包,有几级包,就要写几个* * *.*.*.*.AccountService.saveAccount() 包名可以使用..表示当前包及其子包: * *..AccountService.saveAccount() 类名和方法名都可以使用*来实现通配: * *..*.*() 参数列表可以直接写数据类型,也可以使用..表示有无参数都行,也可以使用*表示一个任意类型的参数: * *..*.*(int/Java.lang.String) 表达式全通配写法: * *..*.*(..) 在实际开发中表达式的通常写法: 只增强业务层实现类中的方法: * com.leon.service.impl.*(..)
复用pointcut的配置:
<aop:config>
<!--配置切入点表达式,id属性用于指定表达式的唯一标识,expression用于指定表达式内容
写在<aop:aspect>内部只能当前页面使用,写在<aop:aspect>外面(上面,不然读取不到)则所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(public void com.leon.service.impl.AccountService.saveAccount())"></aop:pointcut>
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>