2.2 Spring中的AOP
AOP(Aspect Crient Programming),即面向切面编程。将类(可以是不同的类)方法中的通用重复行为(与业务无关的重复代码)封装到一个可重用模块,并将其命名为Aspect,即切面。
作用:在不改变原有的逻辑的基础上,增加一些额外的功能(动态代理实现方法增强)。
优势:
-
减少重复代码
-
提高开发效率
-
方便维护
2.2.1 AOP相关术语
-
Aspect(切面):切入点和通知的结合
-
Joinpoint(连接点):在spring中指可以被拦截的方法,比如业务层接口的所有方法
-
Pointcut(切入点):指需要被增强的方法(即被拦截的方法),所有的切入点都是连接点,但连接点不一定是切入点
-
Advice(通知/增强):对被拦截的连接点(即切入点)采取的行为(即具体要对切入点方法增强的内容)。通知类型有前置通知、后置通知、异常通知、最终通知和环绕通知
-
Introduction(引介):引介是一种特殊的通知,在不修改类代码的情况下,可以在运行期间为类动态地添加一些方法或字段
-
Target object(目标对象):被增强方法的所属对象,即被代理的目标对象
-
AOP Proxy(代理):AOP框架为实现aspect(增强)而创建的对象,在Spring中,一个AOP代理就是一个JDK
-
Waving(织入):指把方法增强应用到目标对象来创建代理对象的过程
2.2.2 基于XML的AOP
比如有一个业务类AccountServiceImpl(目标对象),它实现了三个方法(连接点);另外有一个Logger类(代理),里面有一个日志打印方法(目标对象方法要增强的内容),计划该日志打印方法再切入点方法(即AccountServiceImpl的三个方法)执行前执行。
在这个例子中,AccountServiceImpl为目标对象,Logger为AOP代理。AccountServiceImpl的三个方法是三个连接点,由于这三个方法都要方法增强(即在方法执行之前先打印日志),所以这三个方法都是切入点。Logger类中的日志打印方法为前置通知,它提供了调用AccountServiceImpl中三个方法时的公共代码。
/**
* 业务类
*/
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount() {
System.out.println("执行了保存");
}
@Override
public void updateAccount(int i) {
System.out.println("执行了更新:" + i);
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
/**
* 日志工具类,它里面提供了公共代码
*/
public class Logger {
/**
* 计划让其再切入点方法(业务层方法)执行之前执行
*/
public void printLog(){
System.out.println("printLog():开始记录日志!");
}
}
配置bean.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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置service对象,作为AOP中目标对象 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置logger对象,作为AOP代理对象 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!-- 配置AOP
1 使用aop:config标签表明开始AOP配置
2 使用aop:aspect标签表明配置切面
3 在aop:aspect标签内部使用对应标签来配置通知的类型
aop:before,表示配置前置通知
切入点表达式写法:
关键字:execution
表达式:访问修饰符 返回值 包名.包名.类名.方法名(参数列表)
比如:public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
1)访问修饰符可省略
2)报名可以使用通配符,有几级包,就要写几个“*.”,或者使用“*..”表示当前包及其子包
3)类名和方法名都可以使用“*”实现通配
4)参数列表:可以直接写数据类型来实现匹配;或者使用“*”表示任意一个参数;或者使用“..”实现全通配
5)全通配写法:* *..*.*(..)
6)实际开发中切入点表达式的通常写法:切到业务层实现类下的所有方法:* com.itheima.service.impl.*.*(..)
-->
<aop:config>
<!-- 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 建立通知方法和切入点方法之间的关联 -->
<aop:before method="printLog" pointcut="execution(* *..*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>
执行程序输出:
printLog():开始记录日志!
执行了保存
printLog():开始记录日志!
执行了更新:1
printLog():开始记录日志!
执行了删除
三个方法在执行之前都打印了日志数据。
2.2.3 基于注解的AOP
@Component("logger")
@Aspect // 表示当前类为切面类
public class Logger {
// 切入点表达式
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt(){}
/**
* 计划让其再切入点方法(业务层方法)执行之前执行
*/
@Before("pt()") // 前置通知
public void printLog(){
System.out.println("printLog():开始记录日志!");
}
}
这里还可以有后置通知(@After-Returning)、异常通知(@After-Throwing)、最终通知(@After)和环绕通知(@Around)。
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">>
<!--告知spring在创建IoC容器时要扫描的包,配置所需要的标签不是在beans的约束中,
而是以一个context的名称空间中 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
或者不用XML,完全通过注解的形式:
@Configuration // 指定当前类为配置类
@ComponentScan(basePackages = "com.itheima") // 指定spring容器创建时,要扫描的包
@EnableAspectJAutoProxy // 配置spring开启注解AOP的支持
public class SpringConfiguration {
}