Spring 学习笔记 (四):AOP

AOP:Aspect Oriented Programming 面向切面编程

简介:

  • 实现机制:采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。
  • 使用场合:适用于具有横切逻辑的场合,如访问控制、事务管理、性能监控、日志、异常处理等
  • 使用目的:在不改变原程序的基础上,为代码段增加新的功能,对代码段进行增强处理

术语/概念:aop concepts

  1. Aspect(切面):在实际应用中,切面通常是指封装的用于横向插入系统功能(如事务、日志等)的类。该类要被 Spring 容器识别为切面,需要在配置文件中通过 元素指定。
  2. Joinpoin(t连接点):在程序执行过程中的某个阶段点,它实际上是对象的一个操作,例如方法的调用或异常的抛出。在 Spring AOP 中,连接点就是指方法的调用。
  3. Pointcut(切入点):是指切面与程序流程的交叉点,即那些需要处理的连接点,如图 3-2 所示。通常在程序中,切入点指的是类或者方法名,如某个通知要应用到所有以 add 开头的方法中,那么所有满足这一规则的方法都是切入点。
  4. Advice(通知/增强处理):AOP 框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法,它是切面的具体实现。
  5. Target Object(目标对象):是指所有被通知的对象,也被称为被增强对象。如果 AOP 框架采用的是动态的 AOP 实现,那么该对象就是一个被代理对象。
  6. Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
  7. Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

AOP 框架:

  • Spring AOP:使用纯 Java 实现,不需要专门的类加载器和编译器,在运行时通过代理方式向目标类织入增强的代码。
  • AspectJ:一个基于 Java 语言的 AOP 框架,Spring 2.0 开始支持,AspectJ 扩展了 Java 语言,提供一个专门的编译器,在编译时提供横向代码的织入。

动态代理:proxying mechanisms

  • JDK 动态代理:使用 JDK 动态代理的对象必须实现一个或多个接口
    • 实现 InvocationHandler,重写 invoke() 方法
    • 声明目标类接口
    • 创建代理方法:通过 Java 反射类 Proxy.newProxyInstance() 方法创建,入参
      1. 当前类的类加载器
      2. 被代理对象实现的所有接口
      3. 代理类本身(this)
  • CGLIB 代理:Code Generation Library,采用字节码技术,对没有实现接口的类进行代理
    • 实现 MethodInterceptor,重写 intercept 方法
    • 创建代理方法:通过 CGLIB 核心类 Enhancer 创建,过程如下
      1. 创建动态对象 Enhancer
      2. 确定目标对象:确定要增强的类,设置 Enhancer 父类
      3. 添加 Enhancer 回调函数:代理类本身 (this)
      4. 通过 Enhancer 创建代理类并返回

Spring AOP:

  • Spring 通知类型:Spring 中的通知按照在目标类方法的连接点位置,可以分为以下 5 种类型:
    1. 环绕通知:org.aopalliance.intercept.MethodInterceptor,在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
    2. 前置通知:org.springframework.aop.MethodBeforeAdvice,在目标方法执行前实施增强,可以应用于权限管理等功能。
    3. 后置通知:org.springframework.aop.AfterReturningAdvice,在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
    4. 异常通知:org.springframework.aop.ThrowsAdvice,在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
    5. 引介通知:org.springframework.aop.IntroductionInterceptor,在目标类中添加一些新的方法和属性,可以应用于修改老版本程序(增强类)。
  • ProxyFactoryBean:创建 AOP 代理的基本方式,是 FactoryBean 接口的实现类,常用属性如下
    1. target:代理的目标对象
    2. proxyInterfaces:代理要实现的接口
    3. proxyTargetClass:指定代理方式
    4. interceptorNames:指定切面
    5. singleton:返回的代理是否为单例
    6. optimize:true 时, 强制使用 CGLIB

AspectJ

  • 基于 XML 的声明使用 AspectJaop:config 子元素
  • 基于注解的声明式 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
<?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>
posted @ 2021-03-16 18:41  Uyiefiz  阅读(119)  评论(0)    收藏  举报