java框架013——Spring AOP面向切面编程

一、Spring AOP简介

AOP的全称是Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。

在业务处理代码中,通常都会进行事务处理日志记录等操作。虽然使用OOP可以通过组合或者继承的方式来达到代码的重用,但有时为了实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率,更重要的是降低了代码的可维护性。

为了解决这一问题,AOP思想随之产生。AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码织入到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充。

类与切面的关系
在这里插入图片描述
AOP的使用,使开发人员在编写业务逻辑时可以专心于核心业务,而不用过多的关注于其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性。

二、AOP术语

在这里插入图片描述
连接点:可以被增强的方法
切入点:实际被增强的方法
通知(增强):实际增强的逻辑部分称为通知(增强)
前置通知
后置通知
环绕通知
异常通知
最终通知
切面:是动作,把通知应用到切入点过程

Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

三、JDK动态代理(基于接口实现)

3.1、先抛出问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.1、使用JDK动态代理解决问题(实际开发不常使用,学习了解原理)

JDK动态代理是通过java.lang.reflect.Proxy 类来实现的,我们可以调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。

newProxyInstance()方法
方法有三个参数:
第一参数:类加载器
第二参数:增强方法所在的类,这个类实现的接口,支持多个接口
第三参数:实现这个接口InvocationHandler,创建代理对象,写增强的部分。

步骤:
1、创建接口类
2、创建接口的实现类
3、使用Proxy创建接口代理对象
在这里插入图片描述
代理的作用:附加功能由动态代理来做,核心功能的业务处理由service来实现
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、AspectJ开发

4.1、AspectJ简介(不是spring组成部分)

AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。Spring 2.0以后,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。新版本的Spring框架,也建议使用AspectJ来开发AOP。

使用AspectJ实现AOP有两种方式:
一种是基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ。

4.2、AspectJ开发准备

下载AspectJ的jar包https://mvnrepository.com/artifact/org.aspectj/aspectjweaver
需要导入的jar包
在这里插入图片描述

4.3、通知的类型

按照通知在目标类方法的连接点位置,主要可以分为以下类型:

1、环绕通知:在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
2、前置通知:在目标方法执行前实施增强,可以应用于权限管理等功能。
3、返回通知:在目标方法成功执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
4、后置(最终)通知:在目标方法执行后实施增强,不论是否发生异常,该通知都要执行,该类通知可用于释放资源。
5、异常抛出通知:在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。

4.4、基于注解的AspectJ

4.4.1、AspectJ注解

AspectJ框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码。AspectJ的注解及其描述如下所示:
在这里插入图片描述

4.4.2、切入点表达式

在这里插入图片描述

execution(modifiers-pattern  ret-type-pattern  declaring-type-pattern name-pattern(param-pattern) throws-pattern)

在这里插入图片描述
(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强)
语法结构:,

execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))

举例1:com.dgut.dao.BookDao类里面的add进行增强
execution(* com.dgut.dao.BookDao.add(..))

举例2:com.dgut.dao.BookDao类里面的所有的方法进行增强
vexecution(* com.dgut.daa.BookDao.*(..))

举例3:对com.dgut.dao包里面所有类,类里面所有方法进行增强
execution(*com.dgut.dao.*.*(..))

4.4.、基于注解的AspectJ内容

获取方法的方法名
获取方法的参数
获取方法的返回结果
处理特定异常
环绕通知用法
重用切入点表达式
切面的优先级------->@Order(2) // 第二个切面,后使用

4.4.、实例(实现接口情况)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

MyAspect .java

package com.dgut.spring.aop.aspectj;

import java.util.Arrays;

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.core.annotation.Order;
import org.springframework.stereotype.Component;

//创建一个切面
@Component
@Aspect//声明这是一个切面类
@Order(2) // 第二个切面,后使用
public class MyAspect {

	// 重用切入点表达式
	@Pointcut("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.*(..))")
	public void myPointCut() {}

	// 前置通知
	// 切CalculatorImpl类的add方法参数任意..,返回值任意*
	@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
	public void beforeAnnouncement(JoinPoint jp) {
		String methodName = jp.getSignature().getName();
		Object[] args = jp.getArgs();
		System.out.println("Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
	}

	// 返回通知
	// 切CalculatorImpl类的所有方法参数任意..,返回值为res和参数Object res同名,表示两个是一致的
	@AfterReturning(value = "myPointCut()", returning = "res")
	public void afterReturningAnnouncement(JoinPoint jp, Object res) {
		String methodName = jp.getSignature().getName();
		System.out.println("AfterReturning Announcement===> " + "要切入的方法名:" + methodName + "() ,返回结果:" + res);
	}

	// 后置(最终)通知
	// 切CalculatorImpl类的所有方法参数任意..,返回值任意*
	@After("myPointCut()")
	public void afterAnnouncement(JoinPoint jp) {
		String methodName = jp.getSignature().getName();
		System.out.println("After Announcement===> " + "要切入的方法名:" + methodName + "()");
	}

	// 异常抛出通知
	// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
	@AfterThrowing(value = "myPointCut()", throwing = "ex")
	public void exceptionAnnouncement(JoinPoint jp, Exception ex) {
		String methodName = jp.getSignature().getName();
		System.out.println("Exception Announcement===> " + "要切入的方法名:" + methodName + "()" + " throwing Exception:" + ex);
	}

	// 环绕通知
	// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
	@Around("myPointCut()")
	public Object aroundAnnouncement(ProceedingJoinPoint pjp) {
		Object res = null;

		try {
			// 前置通知
			System.out.println("前置通知");
			res = pjp.proceed();
			// 返回通知
			System.out.println("返回通知");
		} catch (Throwable e) {
			// 异常抛出通知
			System.out.println("异常抛出通知");
			e.printStackTrace();
		} finally {
			// 后置(最终)通知
			System.out.println("后置(最终)通知");
		}
		return res;
	}

}

OtherAspect .java

package com.dgut.spring.aop.aspectj;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//创建一个切面
@Component
@Aspect
@Order(1) // 第一个切面,先使用
public class OtherAspect {
	// 前置通知
	// 切CalculatorImpl类的add方法参数任意..,返回值任意*
	@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
	public void beforeAnnouncement(JoinPoint jp) {
		String methodName = jp.getSignature().getName();
		Object[] args = jp.getArgs();
		System.out.println("<===Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
	}
}

在这里插入图片描述
在这里插入图片描述

4.4.、实例(没有实现接口情况)

在这里插入图片描述
步骤:
(1)开启注解扫描
(2)使用注创建CalculatorImpl和MyAspect(OtherAspect 可选)对象
(3)在增强类上面添加注解@Aspect
(4)在spring配置文件中开启生成代理对象
(4)在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置.
在这里插入图片描述
在这里插入图片描述
MyAspect .java

package com.dgut.spring.aop.aspectj;

import java.util.Arrays;

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.core.annotation.Order;
import org.springframework.stereotype.Component;

//创建一个切面
@Component
@Aspect
@Order(2) // 第二个切面,后使用
public class MyAspect {

	// 重用切入点表达式
	@Pointcut("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.*(..))")
	public void myPointCut() {

	}

	// 前置通知
	// 切CalculatorImpl类的add方法参数任意..,返回值任意*
	@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
	public void beforeAnnouncement(JoinPoint jp) {
		String methodName = jp.getSignature().getName();
		Object[] args = jp.getArgs();
		System.out.println("Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
	}

	// 返回通知
	// 切CalculatorImpl类的所有方法参数任意..,返回值为res和参数Object res同名,表示两个是一致的
	@AfterReturning(value = "myPointCut()", returning = "res")
	public void afterReturningAnnouncement(JoinPoint jp, Object res) {
		String methodName = jp.getSignature().getName();
		System.out.println("AfterReturning Announcement===> " + "要切入的方法名:" + methodName + "() ,返回结果:" + res);
	}

	// 后置(最终)通知
	// 切CalculatorImpl类的所有方法参数任意..,返回值任意*
	@After("myPointCut()")
	public void afterAnnouncement(JoinPoint jp) {
		String methodName = jp.getSignature().getName();
		System.out.println("After Announcement===> " + "要切入的方法名:" + methodName + "()");
	}

	// 异常抛出通知
	// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
	@AfterThrowing(value = "myPointCut()", throwing = "ex")
	public void exceptionAnnouncement(JoinPoint jp, Exception ex) {
		String methodName = jp.getSignature().getName();
		System.out.println("Exception Announcement===> " + "要切入的方法名:" + methodName + "()" + " throwing Exception:" + ex);
	}

	// 环绕通知
	// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
	@Around("myPointCut()")
	public Object aroundAnnouncement(ProceedingJoinPoint pjp) {
		Object res = null;

		try {
			// 前置通知
			System.out.println("前置通知");
			res = pjp.proceed();
			// 返回通知
			System.out.println("返回通知");
		} catch (Throwable e) {
			// 异常抛出通知
			System.out.println("异常抛出通知");
			e.printStackTrace();
		} finally {
			// 后置(最终)通知
			System.out.println("后置(最终)通知");
		}
		return res;
	}

}

OtherAspect .java

package com.dgut.spring.aop.aspectj;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//创建一个切面
@Component
@Aspect
@Order(1) // 第一个切面,先使用
public class OtherAspect {
	// 前置通知
	// 切CalculatorImpl类的add方法参数任意..,返回值任意*
	@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
	public void beforeAnnouncement(JoinPoint jp) {
		String methodName = jp.getSignature().getName();
		Object[] args = jp.getArgs();
		System.out.println("<===Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
	}
}

在这里插入图片描述
在这里插入图片描述

4.5、基于XML的AspectJ(作为了解)

基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>元素内。
在这里插入图片描述

4.5.1、XML文件中常用元素的配置方式如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值