【04】Spring AOP

1. AOP

  • AOP:面向切面(方面)编程,通俗的理解就是:扩展功能不通过修改源代码实现
  • AOP:采用横向抽取机制,取代了传统 纵向 继成体系 重用代码(性能监视,事务管理,安全检查,缓存)

2. AOP实现机制 – 代理机制:

  • Spring 的 AOP 的底层用到两种代理机制:
    • JDK 的动态代理 :针对实现了接口的类产生代理.
    • Cglib 的动态代理 :针对没有实现接口的类产生代理. 应用的是底层的字节码增强的技术 生成当前类
      的子类对象.

3. AOP的相关概念

  1. PointCut(切入点)

    切入点实际上是用来定义横切逻辑规则的组件;
    所谓切入点是指我们要对哪些Joinpoint进行拦截的定义;
    【糯米藕】切藕的规则:距藕节3~4cm;

  2. Target(目标对象)

    代理的目标对象 (要增强的类)
    根据切入点(pointcut)的规则,找出来的需要被增强的类 / 对象。
    【糯米藕】根据切藕的规则,找出来的符号条件的藕;

  3. JoinPoint(连接点)

    所谓连接点是指那些被拦截到的点,在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。
    连接点是根据切入点(pointcut)的规则,在目标对象(Target)上要进行切面的那个位置,代码中体现为一个特定的方法;
    【糯米藕】具体的一节藕上,下刀的位置;

  4. Advice(通知/增强)

    所谓通知是指拦截到连接点之后所要做的事情就是通知;
    通知分为前置通知,后置通知,异常通知,最终通知,环绕通知 (切面要完成的任务)
    通知(Advice)可以看做是添加到目标对象(Target)上的新的功能;
    通知体现为类的方法;
    【糯米藕】米;

  5. Aspect(切面)

    切面(Aspect)是切入点和通知(引介)的结合
    代码中的切面是一个理解性的概念;
    【糯米藕】在藕上下刀,形成的横截面;

  6. Introduction(引介)

    扫描二维码关注公众号,回复: 6127366 查看本文章

    引介是一种特殊的通知(Advice),在不修改类的代码的前提下,Introduction可以在运行期间为类动态添加一些方法或者Field

  7. Weaving(织入)

    把增强的应用到目标过程。【把advice应用到target的过程】
    织入在开发过程中,需要进行xml或注解配置;
    【糯米藕】在藕上下刀,把米灌入藕的全过程;

  8. Proxy(代理)

    一个类被AOP织入增强后,就产生一个结果代理类
    【糯米藕】糯米藕;

4. Spring 使用 AspectJ 进行 AOP 的开发: XML 的方式

4.1 引入jar包

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>4.3.21.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
		<dependency>
			<groupId>aopalliance</groupId>
			<artifactId>aopalliance</artifactId>
			<version>1.0</version>
		</dependency>

4.2 引入 Spring 的配置文件

	<?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:p="http://www.springframework.org/schema/p"
		xmlns:context="http://www.springframework.org/schema/context"
		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/context http://www.springframework.org/schema/context/spring-context.xsd
			http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

4.3 编写目标类

	import com.hxzy.service.TeacherService;

	public class TeacherServiceImpl implements TeacherService{	
		@Override
		public void dianMing() {
			System.out.println("TeacherServiceImpl ... dianMing()");
			teacherDao.dianMing();
		}
	}

4.4 整合 Junit 单元测试

  • 引入 spring-test.jar
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>4.3.21.RELEASE</version>
	</dependency>

4.5 通知类型

  • 前置通知 : 在目标方法执行之前执行.
  • 后置通知 : 在目标方法执行之后执行
  • 环绕通知 : 在目标方法执行前和执行后执行
  • 异常抛出通知: 在目标方法执行出现 异常的时候 执行
  • 最终通知 : 无论目标方法是否出现异常 最终通知都会 执行.

4.6 切入点表达式

语法结构:
execution( 【方法修饰符】 方法返回值 方法所属类 匹配方法名 ( 方法中的形参表 ) 方法申明抛出的异常 )

其中 方法返回值、匹配方法名、 方法中的形参表 的部分时不能省略的,各部分都支持通配符 “*” 来匹配全部。

比较特殊的为形参表部分,其支持两种通配符

“*”:代表一个任意类型的参数;
  “…”:代表零个或多个任意类型的参数。
  
  例如:

()匹配一个无参方法

(…)匹配一个可接受任意数量参数和类型的方法

(*)匹配一个接受一个任意类型参数的方法

(*,Integer)匹配一个接受两个参数的方法,第一个可以为任意类型,第二个必须为Integer。

分类 示例 描述
通过方法签名定义切入点 execution(public * * (..)) 匹配所有目标类的public方法,第一个*为返回类型,第二个*为方法名
execution(* save* (..)) 匹配所有目标类以save开头的方法,第一个*代表返回类型
execution( * *product(*,String)) 匹配目标类所有以product结尾的方法,并且其方法的参数表第一个参数可为任意类型,第二个参数必须为String
通过类定义切入点 execution(* aop_part.Demo1.service.*(..)) 匹配service接口及其实现子类中的所有方法
通过包定义切入点 execution(* aop_part.*(..)) 匹配aop_part包下的所有类的所有方法,但不包括子包
execution(* aop_part..*(..)) 匹配aop_part包下的所有类的所有方法,包括子包。(当".."出现再类名中时,后面必须跟“*”,表示包、子孙包下的所有类)
execution(* aop_part..*.*service.find*(..)) 匹配aop_part包及其子包下的所有后缀名为service的类中,所有方法名必须以find为前缀的方法
通过方法形参定义切入点 execution(*foo(String,int)) 匹配所有方法名为foo,且有两个参数,其中,第一个的类型为String,第二个的类型为int
execution(* foo(String,..)) 匹配所有方法名为foo,且至少含有一个参数,并且第一个参数为String的方法(后面可以有任意个类型不限的形参)

4.7 编写一个切面类

	import java.util.Arrays;
	
	import org.apache.log4j.Logger;
	import org.aspectj.lang.JoinPoint;
	import org.aspectj.lang.ProceedingJoinPoint;
	
	/**
	 * 增强 Advice
	 * 
	 * @author Administrator
	 */
	public class Aspect01 {
	
		Logger logger = Logger.getLogger(Aspect01.class);
	
		/**
		 * 前置增强
		 */
		public void before(JoinPoint jp) {
			logger.debug("前置增强:before");
	//		jp.getTarget() : 获得类名 , 获得目标对象(Target)
	//		jp.getSignature().getName() : 获得方法名
	//		jp.getArgs() : 获得参数数组
			logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入参:"
					+ Arrays.toString(jp.getArgs()));
		}
	
		/**
		 * 后置增强
		 */
		public void afterReturn(JoinPoint jp, Object returnValue) {
			logger.debug("后置增强:afterReturn ... ");
	//		jp.getTarget() : 获得类名
	//		jp.getSignature().getName() : 获得方法名
	//		jp.getArgs() : 获得参数数组
			logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法," + "方法入参:"
					+ Arrays.toString(jp.getArgs()) + " ,方法返回值:" + returnValue);
		}
	
		/**
		 * 环绕增强
		 * 
		 * @return
		 */
		public Object around(ProceedingJoinPoint jp) {
			logger.debug("环绕增强 begin");
	//		jp.getTarget() : 获得类名 , 获得目标对象(Target)
	//		jp.getSignature().getName() : 获得方法名
	//		jp.getArgs() : 获得参数数组
			logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入参:"
					+ Arrays.toString(jp.getArgs()));
	
			// 在环绕增强中,可以使用连接点的对象jp,直接调用目标方法,并得到返回值。
			Object returnValue = null; // 返回值
			try {
				returnValue = jp.proceed();
				System.out.println("返回值是:" + returnValue);
			} catch (Throwable e) {
				e.printStackTrace();
			}
			logger.debug("环绕增强 end ");
			return returnValue;
		}
	
		/**
		 * 异常增强
		 * @param jp
		 * @param e
		 */
		public void afterThrowing(JoinPoint jp, RuntimeException e) {
			logger.info(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法发生异常,异常信息是:" + e.getMessage());
		}
	
		/**
		 * 最终增强
		 * @return
		 */
		public void after(JoinPoint jp) {
			logger.debug("最终增强 ");
			logger.debug(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法结束执行。");
		}
	}

4.8 配置完成增强

	<!-- 配置切面类(增强) -->
	<bean id="aspect01" class="com.hxzy.aop.Aspect01"></bean>
		
	<!-- 进行 aop 的配置 -->
	<aop:config>
		<!-- 切入点:指明哪些类的哪些方法需要进行增强,是一个规则  -->
		<!-- [方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数) -->
	    <aop:pointcut expression="execution( * com.hxzy.service..*.*(..))"  id="pointcut"/>
		<!-- 配置切面 , 织入 -->
		<aop:aspect ref="aspect01">
			<!-- 前置增强 -->
			<aop:before method="before" pointcut-ref="pointcut"/>	
			<!-- 后置增强 -->
			<aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="returnValue"/>
			<!-- 环绕增强 -->
			<aop:around method="around" pointcut-ref="pointcut"/>
			<!-- 异常增强 -->
			<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
			<!-- 最终增强 -->
			<aop:after method="after" pointcut-ref="pointcut"/>
		</aop:aspect>
	</aop:config>

5. Spring 使用 AspectJ 进行 AOP 的开发: 注解配置

5.1 Spring配置文件中开启aop注解的自动代理

	<!-- 扫描带有注解的增强,需要引入aop的命名空间 -->
	<aop:aspectj-autoproxy />

5.2 AspectJ的AOP注解

  • 通知类型
    • @Before : 前置增强
    • @AfterReturning : 后置增强
    • @Around : 环绕增强
    • @AfterThrowing : 异常抛出增强
    • @After : 最终增强
  • 切入点
    • @Pointcut

5.3 增强类中使用注解获得连接点信息

	
	import java.util.Arrays;
	import org.apache.log4j.Logger;
	import org.aspectj.lang.JoinPoint;
	import org.aspectj.lang.ProceedingJoinPoint;
	import org.aspectj.lang.annotation.After;
	import org.aspectj.lang.annotation.AfterReturning;
	import org.aspectj.lang.annotation.AfterThrowing;
	import org.aspectj.lang.annotation.Around;
	import org.aspectj.lang.annotation.Aspect;
	import org.aspectj.lang.annotation.Before;
	import org.aspectj.lang.annotation.Pointcut;
	import org.springframework.stereotype.Component;
	
	/**
	 * 增强 Advice
	 * @author Administrator
	 */
	@Component // 注册bean , 配置切面 
	@Aspect //配置切面类
	public class Aspect02 {
	
		Logger logger = Logger.getLogger(Aspect02.class);
		
		// 切入点
		@Pointcut(value = "execution( * com.hxzy.service.impl.*.*(..))")
		private void anyMethod() {}
	
		/**
		 * 前置增强
		 */
	//	@Before(value="execution( * com.hxzy.service.impl.*.*(..))")
	//	@Before("execution( * com.hxzy.service.impl.*.*(..))")
		@Before("anyMethod()")
		public void before(JoinPoint jp) {
			logger.debug("前置增强:before");
			logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入参:"
					+ Arrays.toString(jp.getArgs()));
		}
	
		/**
		 * 后置增强
		 */
		@AfterReturning(pointcut = "anyMethod()",returning = "returnValue")
		public void afterReturn(JoinPoint jp, Object returnValue) {
			logger.debug("后置增强:afterReturn ... ");
			logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法," + "方法入参:"
					+ Arrays.toString(jp.getArgs()) + " ,方法返回值:" + returnValue);
		}
	
		/**
		 * 环绕增强
		 * @return
		 */
		@Around("anyMethod()")
		public Object around(ProceedingJoinPoint jp) {
			logger.debug("环绕增强 begin");
			logger.debug("调用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入参:"
					+ Arrays.toString(jp.getArgs()));
	
			// 在环绕增强中,可以使用连接点的对象jp,直接调用目标方法,并得到返回值。
			Object returnValue = null; // 返回值
			try {
				returnValue = jp.proceed();
				System.out.println("返回值是:" + returnValue);
			} catch (Throwable e) {
				e.printStackTrace();
			}
			logger.debug("环绕增强 end ");
			return returnValue;
		}
	
		/**
		 * 异常增强
		 * @param jp
		 * @param e
		 */
		@AfterThrowing(pointcut = "anyMethod()" , throwing = "e")
		public void afterThrowing(JoinPoint jp, RuntimeException e) {
			logger.info(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法发生异常,异常信息是:" + e.getMessage());
		}
	
		/**
		 * 最终增强
		 * @return
		 */
		@After("anyMethod()")
		public void after(JoinPoint jp) {
			logger.debug("最终增强 ");
			logger.debug(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法结束执行。");
		}
	}

猜你喜欢

转载自blog.csdn.net/Spectre_win/article/details/89556435
今日推荐