spring切面编程AOP


一. AOP 概念

AOP

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

AOP核心概念

面向切面编程

(1)通知(增强)Advice

  通知定义了切面是什么以及何时使用,应该应用在某个方法被调用之前?之后?还是抛出异常时?等等。

(2)连接点 Join point

  连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时,抛出异常时,甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。

(3)切点 Pointcut

  切点有助于缩小切面所通知的连接点的范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”,切点会匹配通知所要织入的一个或多个连接点,一般常用正则表达式定义所匹配的类和方法名称来指定这些切点。

(4)切面 Aspect

  切面是通知和切点的结合。通知和切点定义了切面的全部内容——它是什么,在何时何处完成其功能。

(5)引入 Introduction

  引入允许我们向现有的类添加新方法或属性,从而无需修改这些现有类的情况下,让他们具有新的行为和状态。

(6)织入 Weaving

  在过去我常常把织入与引入的概念混淆,我是这样来辨别的,“引入”我把它看做是一个定义,也就是一个名词,而“织入”我把它看做是一个动作,一个动词,也就是切面在指定的连接点被织入到目标对象中。

总结

  通知包含了需要用于多个应用对象的横切行为;连接点是程序执行过程中能够应用通知的所有点;切点定义了通知被应用的具体位置(在哪些连接点)。其中关键的概念是切点定义了哪些连接点会得到通知(增强)。创建切点来定义切面所织入的连接点是AOP框架的基本功能。

  另外,Spring是基于动态代理的,所以Spring只支持方法连接点,而像AspectJ和JBoss除了方法切点,它们还提供字段和构造器接入点。如果需要方法拦截之外的连接点拦截功能,则可以利用AspectJ来补充SpringAOP的功能。

Spring对AOP的支持

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了

2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

1、定义普通业务组件

2、定义切入点,一个切入点可能横切多个业务组件

3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

二. 基于AspectJ的AOP简单实现:

首先,在 applicationContext.xml 核心配置文件中需要用到 AOP 命名空间,和 两个架包

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>5.0.8.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>aopalliance</groupId>
			<artifactId>aopalliance</artifactId>
			<version>1.0</version>
		</dependency>

注释在类上

@Aspect     声明该类为 切面类
@Order(2)   当同一个目标方法有多个切面的时,哪个切面类先执行,取决于在切面类上的注解@order(值小的先执行)

注解的这个方法在AOP里叫:通知(advice)

@Before 前置通知(在目标方法执行之前执行)

@After 后置通知(在目标方法执行之后执行)无论有没有异常抛出都会执行

@AfterReturning 返回通知(在目标方法返回结果之后执行)

@AfterThrowing 异常通知(在目标方法跑出异常之后执行)

@Around 环绕通知(围绕着方法执行)

1. 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: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-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	
	<!-- 使用注解驱动 自动注册bean  -->
	<context:component-scan base-package="cn.jq.springdemo"></context:component-scan>
	<!-- 使用注解驱动 , 让切面类里的方法上的注释起作用  -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	
</beans>

2.  定义一个操作类:

@Component
public class HelloAop {

	public int add(int a, int b) {
		return a+b;
	}
	public int div(int a, int b) {
		return a/b;
	}
}

3. 自定义两个横切关注点:

     打印当前时间:

@Component
@Aspect
@Order(1)
public class TimeAscept {
	//除法上加入切入点
	@Before("execution(public int cn.jq.springdemo.aop.HelloAop.div(int, int))")
	public void printTime() {
		System.out.println("CurrentTime = " + System.currentTimeMillis());
	}
}

  日志记录:

AspectJ 切入点表达式

execution(* cn.jq.springdemo.aop.HelloAop.*(..)) : 匹配HelloAop中声明的所有方法,

第一个 * 代表任意修饰符及任意返回值. 第二个 * 代表任意方法。 .. 代表匹配任意数量的参数。

@Component
@Aspect	//切面类
@Order(2) //值越小,优先级越高
public class LoggerAscept {

	Logger logger = Logger.getLogger(this.getClass());
	
	//定义切入点表达式
	@Pointcut("execution(* cn.jq.springdemo.aop.HelloAop.*(..))")
	public void joinPointExpression() {}
	
	@Before("joinPointExpression()")
	public void beforeMethod(org.aspectj.lang.JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().getName();	//获取方法名
		List<Object> args = Arrays.asList(joinPoint.getArgs()); //获取参数数组
		logger.info(methodName+"方法执行之前,参数:" + args);
	}
	
	@After("joinPointExpression()")
	public void afterMethod(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().getName();
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		logger.info(methodName+"方法执行之后,参数:" + args);
	}
	
	@AfterReturning(value="joinPointExpression()", returning="rs")
	public void afterReturningMethod(JoinPoint joinPoint, Object rs) {
		String methodName = joinPoint.getSignature().getName();
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		logger.info(methodName+"方法(无异常)执行之后,参数:" + args + ",结果:"+ rs);
	}
	
	@AfterThrowing(value="joinPointExpression()",throwing="ex")
	public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) {
		String methodName = joinPoint.getSignature().getName();
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		logger.info(methodName+"方法执行时发生异常啦,参数:" + args + ",异常信息:" + ex.getMessage());
	}
}

4. 测试类:

public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
	
		HelloAop helloAop = (HelloAop) context.getBean("helloAop");
		helloAop.add(2, 2);
		helloAop.div(10, 0);
	}

----结果--

2018-08-25 13:35:25,442 INFO [cn.jq.springdemo.aop.LoggerAscept.beforeMethod(LoggerAscept.java:32)] add方法执行之前,参数:[2, 2]  
   2018-08-25 13:35:25,456 INFO [cn.jq.springdemo.aop.LoggerAscept.afterMethod(LoggerAscept.java:39)] add方法执行之后,参数:[2, 2]  
   2018-08-25 13:35:25,456 INFO [cn.jq.springdemo.aop.LoggerAscept.afterReturningMethod(LoggerAscept.java:46)] add方法(无异常)执行之后,参数:[2, 2],结果:4  
   CurrentTime = 1535175325456
2018-08-25 13:35:25,456 INFO [cn.jq.springdemo.aop.LoggerAscept.beforeMethod(LoggerAscept.java:32)] div方法执行之前,参数:[10, 0]  
   2018-08-25 13:35:25,457 INFO [cn.jq.springdemo.aop.LoggerAscept.afterMethod(LoggerAscept.java:39)] div方法执行之后,参数:[10, 0]  
   2018-08-25 13:35:25,457 INFO [cn.jq.springdemo.aop.LoggerAscept.afterThrowingMethod(LoggerAscept.java:53)] div方法执行时发生异常啦,参数:[10, 0],异常信息:/ by zero  
   Exception in thread "main" java.lang.ArithmeticException: / by zero
	at cn.jq.springdemo.aop.HelloAop.div(HelloAop.java:12)
	at cn.jq.springdemo.aop.HelloAop$$FastClassBySpringCGLIB$$c897cdd6.invoke(<generated>)

此时,AOP 的四种通知  简单实现。

环绕通知:就是把前面4中通知全给整合在一起。使用它必须要求: 

1、必须要带参数 ProceedingJoinPoint 类型的参数,这个参数可以直接调用原来的目标方法。

2、环绕通知方法必须有返回值,这个反正值就是目标方法的返回值。

@Component
@Aspect	//横切关注点
@Order(2) //值越小,优先级越高
public class LoggerAscept {

	Logger logger = Logger.getLogger(this.getClass());
	
	//定义切入点表达式
	@Pointcut("execution(* cn.jq.springdemo.aop.HelloAop.*(..))")
	public void joinPointExpression() {}
	
	@Around("joinPointExpression()")
	public Object aroundMethod(ProceedingJoinPoint pjp) {
		Object rs = null;
		String methodName = pjp.getSignature().getName();
		try {
			//前置通知
			logger.info(methodName+"方法执行之前");
			rs = pjp.proceed(); //在这里执行目标方法
			//返回通知
			logger.info(methodName+"方法(无异常)执行之后,结果:"+ rs);
		} catch (Throwable e) {
			//异常通知
			logger.info(methodName+"方法执行时发生异常啦,异常信息:" + e.getMessage());
			e.printStackTrace();
		}
		//后置通知
		logger.info(methodName+"方法执行之后, 结果" + rs);
		return rs;
	}

}
----结果一样---

基于 xml 配置文件的 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: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-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	
	<!-- 使用注解驱动 自动注册bean  -->
	<context:component-scan base-package="cn.jq.springdemo"></context:component-scan>
	<!-- 使用注解驱动AOP , 让切面类里的方法上的注释起作用  -->
	<!-- <aop:aspectj-autoproxy></aop:aspectj-autoproxy> -->
	
	<!-- 使用xml配置AOP  -->
	<aop:config>
		<!-- 配置切面点表达式 -->
		<aop:pointcut expression="execution(* cn.jq.springdemo.aop.HelloAop.*(..))" id="pointCutLogger"/>
		<aop:pointcut expression="execution(public int cn.jq.springdemo.aop.HelloAop.div(int, int))" id="pointCutTime"/>
		<!-- 配置切面和通知 -->
		<aop:aspect ref="loggerAscept" order="2">
			<aop:before method="beforeMethod" pointcut-ref="pointCutLogger"/>
			<aop:after method="afterMethod" pointcut-ref="pointCutLogger"/>		<!-- 注意方法名 参数名一致 -->
			<aop:after-returning method="afterReturningMethod" pointcut-ref="pointCutLogger" returning="rs"/>
			<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointCutLogger" throwing="ex"/>
		</aop:aspect>
		<aop:aspect ref="timeAscept" order="1">
			<aop:before method="printTime" pointcut-ref="pointCutTime"/>
		</aop:aspect>
	</aop:config>
	
</beans>

猜你喜欢

转载自blog.csdn.net/qq_42402854/article/details/82049893
今日推荐