Spring 学习笔记 (四):AOP
AOP:Aspect Oriented Programming 面向切面编程
简介:
- 实现机制:采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。
- 使用场合:适用于具有横切逻辑的场合,如访问控制、事务管理、性能监控、日志、异常处理等
- 使用目的:在不改变原程序的基础上,为代码段增加新的功能,对代码段进行增强处理
术语/概念:aop concepts
- Aspect(切面):在实际应用中,切面通常是指封装的用于横向插入系统功能(如事务、日志等)的类。该类要被 Spring 容器识别为切面,需要在配置文件中通过
元素指定。 - Joinpoin(t连接点):在程序执行过程中的某个阶段点,它实际上是对象的一个操作,例如方法的调用或异常的抛出。在 Spring AOP 中,连接点就是指方法的调用。
- Pointcut(切入点):是指切面与程序流程的交叉点,即那些需要处理的连接点,如图 3-2 所示。通常在程序中,切入点指的是类或者方法名,如某个通知要应用到所有以 add 开头的方法中,那么所有满足这一规则的方法都是切入点。
- Advice(通知/增强处理):AOP 框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法,它是切面的具体实现。
- Target Object(目标对象):是指所有被通知的对象,也被称为被增强对象。如果 AOP 框架采用的是动态的 AOP 实现,那么该对象就是一个被代理对象。
- Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
- Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。
AOP 框架:
- Spring AOP:使用纯 Java 实现,不需要专门的类加载器和编译器,在运行时通过代理方式向目标类织入增强的代码。
- AspectJ:一个基于 Java 语言的 AOP 框架,Spring 2.0 开始支持,AspectJ 扩展了 Java 语言,提供一个专门的编译器,在编译时提供横向代码的织入。
动态代理:proxying mechanisms
- JDK 动态代理:使用 JDK 动态代理的对象必须实现一个或多个接口
- 实现 InvocationHandler,重写 invoke() 方法
- 声明目标类接口
- 创建代理方法:通过 Java 反射类 Proxy.newProxyInstance() 方法创建,入参
- 当前类的类加载器
- 被代理对象实现的所有接口
- 代理类本身(this)
- CGLIB 代理:Code Generation Library,采用字节码技术,对没有实现接口的类进行代理
- 实现 MethodInterceptor,重写 intercept 方法
- 创建代理方法:通过 CGLIB 核心类 Enhancer 创建,过程如下
- 创建动态对象 Enhancer
- 确定目标对象:确定要增强的类,设置 Enhancer 父类
- 添加 Enhancer 回调函数:代理类本身 (this)
- 通过 Enhancer 创建代理类并返回
Spring AOP:
- Spring 通知类型:Spring 中的通知按照在目标类方法的连接点位置,可以分为以下 5 种类型:
- 环绕通知:org.aopalliance.intercept.MethodInterceptor,在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
- 前置通知:org.springframework.aop.MethodBeforeAdvice,在目标方法执行前实施增强,可以应用于权限管理等功能。
- 后置通知:org.springframework.aop.AfterReturningAdvice,在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
- 异常通知:org.springframework.aop.ThrowsAdvice,在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
- 引介通知:org.springframework.aop.IntroductionInterceptor,在目标类中添加一些新的方法和属性,可以应用于修改老版本程序(增强类)。
- ProxyFactoryBean:创建 AOP 代理的基本方式,是 FactoryBean 接口的实现类,常用属性如下
- target:代理的目标对象
- proxyInterfaces:代理要实现的接口
- proxyTargetClass:指定代理方式
- interceptorNames:指定切面
- singleton:返回的代理是否为单例
- optimize:true 时, 强制使用 CGLIB
AspectJ
- 基于 XML 的声明使用 AspectJ:aop:config 子元素
- aop:pointcut
- aop:advisor
- aop:aspect:配置切面
- aop:pointcut:配置切入点:切入点表达式(基本格式)
- 配置通知
- 常用属性
- pointcut:指定切入点表达式
- pointcut-ref:指定一个已存在的切入点
- method:指定一个方法名
- throwing:指定一个形参名
- returning:指定一个形参名
- 增强类型:Spring AOP types of advice
- aop:before:前置增强
- aop:after-returning:后置返回增强
- aop:aop-throwing:异常抛出增强
- aop:after:最终增强
- aop:around:环绕增强,入参 ProceedingJointPoint
- 常用属性
- 基于注解的声明式 AspectJ
- @Aspect:定义切面
- @Pointcut:定义切入点表达式。作为切入点签名的方法必须定义为返回 void 类型并且方法体为空的普通方法。 使用切入点签名引用对应的切入点。
- @Before:定义前置通知,相当于 BeforeAdvice
- @AfterReturning:定义后置通知,相当于 AfterReturningAdvice
- @Around:定义环绕通知,相当于 MethodInterceptor
- @AfterThrowing:定义异常通知来处理程序中未处理的异常,相当于 ThrowAdvice
- @After:定义最终 final 通知
- @DeclareParents:定义引介通知,相当于 IntroductionInterceptor
XML 配置:
- 基于代理类的 AOP 实现
- 配置 bean:目标类 + 切面(切面类需要至少实现 spring aop 的 5 种通知类型接口之一)
- 配置 bean:使用 ProxyFactoryBean 类定义代理对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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-3.2.xsd">
<!-- 1 目标类 -->
<bean id="userDao" class="cn.test.dao.UserDaoImpl" />
<!-- 2 切面类 -->
<bean id="myAspect" class="cn.test.factorybean.MyAspect" />
<!-- 3 使用Spring代理工厂定义一个名称为userDaoProxy的代理对象 -->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 3.1 指定代理实现的接口-->
<property name="proxyInterfaces" value="cn.test.dao.UserDao" />
<!-- 3.2 指定目标对象 -->
<property name="target" ref="userDao" />
<!-- 3.3 指定切面,织入环绕通知 -->
<property name="interceptorNames" value="myAspect" />
<!-- 3.4 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理 -->
<property name="proxyTargetClass" value="true" />
</bean>
</beans>
- 基于 XML 的声明使用 AspectJ
- 配置 bean:目标类 + 切面
- 导入 aop 命名空间:配置切面
<?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-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<!-- 1 目标类 -->
<bean id="userDao" class="cn.test.dao.UserDaoImpl" />
<!-- 2 切面 -->
<bean id="myAspect" class="cn.test.aspectj.xml.MyAspect" />
<!-- 3 aop编程 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 3.1 配置切入点,通知最后增强哪些方法 -->
<aop:pointcut expression="execution(* cn.test.dao.*.*(..))" id="myPointCut" />
<!-- 3.2 关联通知Advice和切入点pointCut -->
<!-- 3.2.1 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointCut" />
<!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值 returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal" />
<!-- 3.2.3 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut" />
<!-- 3.2.4 抛出通知:用于处理程序发生异常-->
<!-- * 注意:如果程序没有异常,将不会执行增强 -->
<!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e" />
<!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>
- 基于注解的声明式 AspectJ
- 引入 context 约束信息:使用 context:component-scan/ 元素设置需要扫描的包
- 导入 aop 命名空间:使用 aop:aspectj-autoproxy 启动 Spring 对基于注解的声明式 AspectJ 的支持
<?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
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!-- 指定需要扫描的包,使注解生效 -->
<context:component-scan base-package="cn.test" />
<!-- 启动基于注解的声明式AspectJ支持 -->
<aop:aspectj-autoproxy />
</beans>

浙公网安备 33010602011771号