Spring5学习笔记——AOP

AOP

概念

  • 什么是 AOP?
    AOP,面向切面编程,可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率,即在不修改源代码的情况下,实现在主干功能中添加新的功能。

底层原理

AOP 的底层使用动态代理实现,分两种情况:
(1)有接口时,使用 JDK 动态代理,创建接口实现类的代理对象。
在这里插入图片描述

(2)没有接口时,使用 CGLIB 动态代理,创建当前类的子类的代理对象。
在这里插入图片描述
演示 JDK 动态代理

BaseInterface.java

package com.mcc.spring5.aop;

public interface BaseInterface {
	public void showInfo();
}

BaseDemo.java

package com.mcc.spring5.aop;

public class BaseDemo implements BaseInterface{
	//基本功能
	@Override
	public void showInfo() {
		System.out.println("BaseDemo 中提供的基本功能");
	}
}

实现动态代理 BaseDemoProxy.java、myProxyHandler.java

package com.mcc.spring5.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//BaseDemo的代理类,用来拓展BaseDemo实现的BaseInterface的功能
public class BaseDemoProxy {
	public static void main(String[] args) {
		Class<?>[] interfaces = {BaseInterface.class};
		//通过代理类 Proxy.newProxyInstance() 方法生成代理对象 bInter
		BaseInterface bInter =(BaseInterface) Proxy.newProxyInstance(BaseDemoProxy.class.getClassLoader(), interfaces, new myProxyHandler(new BaseDemo()));
		bInter.showInfo();
	}
}

//增强的功能类
class myProxyHandler implements InvocationHandler{
	private Object obj;
	//通过有参构造器传入要代理的类的对象
	myProxyHandler(Object obj){
		this.obj = obj;
	}
	/**
	 * proxy - 代理对象,相当于 BInter 对象
	 * method - 当前正在执行的方法
	 * args - 传入的参数
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//获取当前正在执行的方法名
		String methodName = method.getName();
		//在 showInfo() 方法之前增加功能
		System.out.println(methodName + " 方法执行之前增加的功能");
		//执行当前方法,传入method方法所在类的对象,即被代理对象obj,以及参数
		method.invoke(obj, args);
		//在 showInfo() 方法之后增加功能
		System.out.println(methodName + " 方法执行之后增加的功能");
		return null;
	}	
}

相关术语

  1. 连接点
    类里面可以被增强的方法
  2. 切入点
    类里面实际被增强的方法
  3. 通知(增强)
    实际增强的逻辑部分
    通知的分类:
    (1)前置通知
    (2)后置通知
    (3)环绕通知
    (4)异常通知
    (5)最终通知
  4. 切面
    是一个动作,指将通知加入到切入点的过程

AOP 操作的准备工作

  1. Spring 框架一般基于 AspectJ 实现 AOP 操作
    AspectJ 不是 Spring 的组成部分,而是独立的 AOP 框架,常把 AspectJ 和 Spirng 框架一起使用,进行 AOP 操作
  2. 基于 AspectJ 实现 AOP 操作
    (1)基于 xml 配置文件实现
    (2)基于注解方式实现(常用)
  3. 在项目工程里面引入 AOP 相关依赖
    在这里插入图片描述
    其中,前3个包是 AspectJ 依赖的包,后2个包是 Spring 依赖的包,aop 包在使用注解进行 IOC 操作时已经引入。

切入点表达式

  1. 切入点表达式作用:指定对某个类里面的某个方法进行增强
  2. 语法结构: execution(权限修饰符 返回值类型 类的全路径 方法名称(参数列表))
    其中,返回值类型可以省略,可以用 * 代表任意,参数列表可以使用..表示所有参数
  3. 举例:
    (1)对 com.mcc.spring5.aop.AOPDemo 类里面的 show() 方法进行增强
             execution(* com.mcc.spring5.aop.AOPDemo.show(..))
    (2)对 com.mcc.spring5.aop.AOPDemo 类里面的所有的方法进行增强
             execution(* com.mcc.spring5.aop.AOPDemo.*(..))
    (3)对 com.mcc.spring5.aop 包里面所有类,类里面的所有方法进行增强
             execution(* com.mcc.spring5.aop.*.*(..))

AspectJ 注解

步骤:

  1. 创建(被增强)类 User,在类里面定义方法
  2. 创建增强类 UserProxy,编写增强逻辑的方法
  3. 在配置文件中进行配置:
    (1)引入名称空间:context、aop
    (2)开启组件扫描<context:component-scan base-package=""></context:component-scan>
    (3)开启 AOP 生成代理对象,即扫描 @Aspect 注解<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  4. 使用注解@Component生成被增强类和增强类对象
  5. 在增强类上使用@Aspect注解,表示该类为代理类
  6. 在代理类中的增强方法上使用注解表示通知的类型
注解(以下注解都需使用 value=“切入点表达式” 指定对哪个类中的哪个方法进行增强)类型
@Before(value="切入点表达式")前置通知
@AfterReturning后置通知
@Around环绕通知
@AfterThrowing异常通知
@After最终通知
  1. 在测试类中生成被增强类的对象,测试方法
    注意: 在环绕通知 @Around 中,如果要调用原始方法,可以传入参数 ProceedingJoinPoint pjp,pjp 代表原始方法,pjp.proceed()、pjp.proceed(Object[] args) 分别代表执行无参或有参的原始方法。

代码:

Spring 配置文件 aopAspectJ.xml

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

	<!-- 开启组件扫描,创建被增强类(User)和增强类(UserProxy)对象 -->
	<context:component-scan base-package="com.mcc.spring5.aop.aspectj"></context:component-scan>
	
	<!-- 开启 AOP 生成代理对象,即扫描 @Aspect 注解 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

被增强类 User.java

package com.mcc.spring5.aop.aspectj;

import org.springframework.stereotype.Component;

//User 类是原有类,当做被增强类
@Component//创建User对象
public class User {
	public void showInfo() {
		System.out.println("User 中的 showInfo 方法");
	}
}

增强类 UserProxy.java

package com.mcc.spring5.aop.aspectj;

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.springframework.stereotype.Component;

//UserProxy 类是增强类,用来增强其他类代码逻辑
@Component//创建UserProxy对象
@Aspect//配置该类为代理类
public class UserProxy {
	//编写增强逻辑
	//前置通知
	@Before(value="execution(* com.mcc.spring5.aop.aspectj.User.showInfo(..))")//配置切入点表达式,指定对哪个类中的哪个方法进行增强
	public void beforeMethod() {
		System.out.println("Before 前置通知");
	}
	
	//后置通知
	@AfterReturning(value="execution(* com.mcc.spring5.aop.aspectj.User.showInfo(..))")
	public void afterReturnningMethod() {
		System.out.println("AfterReturning 后置通知");
	}
	
	//环绕通知
	@Around(value="execution(* com.mcc.spring5.aop.aspectj.User.showInfo(..))")
	public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {//参数代表当前正在执行的方法
		System.out.println("Around 环绕通知——showInfo 方法之前");
		proceedingJoinPoint.proceed();//执行当前方法
		System.out.println("Around 环绕通知——showInfo 方法之后");
	}
	
	//异常通知
	@AfterThrowing(value="execution(* com.mcc.spring5.aop.aspectj.User.showInfo(..))")
	public void exceptionMethod() {
		System.out.println("AfterThrowing 异常通知");
	}
	
	//最终通知
	@After(value="execution(* com.mcc.spring5.aop.aspectj.User.showInfo(..))")
	public void finalMethod() {
		System.out.println("After 最终通知");
	}	
}

测试类 TestAspectJ.java

package com.mcc.test;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mcc.spring5.aop.aspectj.User;

public class TestAspectJ {
	@Test
	public void testApsectJ() {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aopAspectJ.xml");
		User user = context.getBean("user", User.class);
		user.showInfo();
		/*
		Around 环绕通知——showInfo 方法之前
		Before 前置通知
		User 中的 showInfo 方法
		Around 环绕通知——showInfo 方法之后
		After 最终通知
		AfterReturning 后置通知
		*/
		context.close();
	}
}

相同切入点的抽取

步骤:

  1. 定义一个方法 method()
  2. 在该方法上使用注解@Pointcut(value="切入点表达式")抽取相同的切入点
  3. 在增强方法的通知注解中(以前置通知为例),传入参数@Before(value="method()")
@Component//创建UserProxy对象
@Aspect//配置该类为代理类
public class UserProxy {
	
	//当多个增强逻辑的切入点相同时,可以将切入点抽取
	@Pointcut(value="execution(* com.mcc.spring5.aop.aspectj.User.showInfo(..))")//相同切入点的抽取
 	public void pointCut() {
		
	}
	
	//编写增强逻辑
	//前置通知
	@Before(value="pointCut()")//调用抽取的函数就相当于执行切入点表达式
	public void beforeMethod() {
		System.out.println("Before 前置通知");
	}
	
	//后置通知
	@AfterReturning(value="pointCut()")
	public void afterReturnningMethod() {
		System.out.println("AfterReturning 后置通知");
	}
}

设置代理类的优先级

当有多个代理类同时代理了某个相同类时,如 UserProxy 类和UserProxy2 类都代理了 User 类,此时可以通过在代理类上使用@Order(int i)注解,设置代理类的优先级,优先级高的类中的通知会先执行。

例:设置 UserProxy 类的优先级为 2,UserProxy2 类的优先级为 1

UserProxy.java

@Component//创建UserProxy对象
@Aspect//配置该类为代理类
@Order(2)//配置当前代理的优先级为2
public class UserProxy {
	...
}

UserProxy2.java

@Component
@Aspect
@Order(1)//配置当前代理的优先级为1
public class UserProxy2 {
	...
}

完全注解开发模式

使用代理类代替 xml 配置文件
实现方式:
(1)使用@Configuration声明配置类
(2)使用@ComponentScan(basePackages={""})开启组件扫描
(3)使用@EnableAspectJAutoProxy(proxyTargetClass=true)开启 AOP 生成代理对象,即扫描 @Aspect 注解

配置类 AoopConfig.java

package com.mcc.spring5.aop.aspectj;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

//AOP 配置类,代替 xml 配置文件
@Configuration//声明配置类
@ComponentScan(basePackages={"com.mcc.spring5.aop.aspectj"})//开启组件扫描
@EnableAspectJAutoProxy(proxyTargetClass=true)//开启 AOP 生成代理对象
public class AopConfig {
	
}

测试

@Test
public void testAopConfig() {
	AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
	User user = context.getBean("user", User.class);
	user.showInfo();
	context.close();
}

使用 xml 配置文件实现 AspectJ 注解

步骤:
(1)使用<bean id="" class=""></bean>标签创建被增强类和增强类对象
(2)使用<aop:config></aop:config>标签配置 AOP
(3)在 AOP 配置标签内,使用<aop:pointcut expression="" id=""/>标签配置切入点

  • expression 属性:切入点表达式
  • id 属性:为该切入点取一个名称

(4)在 AOP 配置标签内,使用<aop:aspect ref=""><aop:before method="" pointcut-ref=""/></aop:aspect>标签配置切面

  • <aop:before/>标签可根据需要添加的通知类型,自行选择
  • ref 属性:代理类对象
  • method 属性:要添加的新逻辑的方法名
  • pointcut-ref 属性:切入点的 id

配置文件 aopAspectJXML.xml

<?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.xsd
						http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
	
	<!-- 创建被增强类对象和增强类对象 -->
	<bean id="book" class="com.mcc.spring5.aop.aspectjXML.Book"></bean>
	<bean id="bookProxy" class="com.mcc.spring5.aop.aspectjXML.BookProxy"></bean>
	
	<!-- 配置 AOP 增强类、增强方法 -->
	<aop:config>
		<!-- 配置切入点 -->
		<aop:pointcut expression="execution(* com.mcc.spring5.aop.aspectjXML.Book.showInfo(..))" id="showInfo"/>
		<!-- 配置切面 -->
		<aop:aspect ref="bookProxy"><!-- ref:表示代理类对象 -->
			<!-- 前置通知,method:代理类中的方法名,pointcut-ref:切入点id -->
			<aop:before method="beforeMethod" pointcut-ref="showInfo"/>
		</aop:aspect>
	</aop:config>
	
</beans>

被增强类 Book.java

package com.mcc.spring5.aop.aspectjXML;

public class Book {
	public void showInfo() {
		System.out.println("Book 的 showInfo 方法");
	}
}

增强类 BookProxy.java

package com.mcc.spring5.aop.aspectjXML;

public class BookProxy {
	//增强的前置通知
	public void beforeMethod() {
		System.out.println("Before 方法");
	}
}

测试类 TestAspectJXML.java

package com.mcc.test;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mcc.spring5.aop.aspectjXML.Book;

public class TestAspectJXML {
	@Test
	public void testAopConfig() {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aopAspectJXML.xml");
		Book book = context.getBean("book", Book.class);
		book.showInfo();
		/*
		Before 方法
		Book 的 showInfo 方法
		*/
		context.close();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOPSpring框架中的一个重要模块,它提供了面向切面编程(AOP)的支持。AOP是一种编程思想,它可以在不改变原有代码的情况下,通过在程序运行时动态地将代码“织入”到现有代码中,从而实现对原有代码的增强。 Spring AOP提供了基于注解的AOP实现,使得开发者可以通过注解的方式来定义切面、切点和通知等相关内容,从而简化了AOP的使用。 下面是一个基于注解的AOP实现的例子: 1. 定义切面类 ```java @Aspect @Component public class LogAspect { @Pointcut("@annotation(Log)") public void logPointcut() {} @Before("logPointcut()") public void beforeLog(JoinPoint joinPoint) { // 前置通知 System.out.println("执行方法:" + joinPoint.getSignature().getName()); } @AfterReturning("logPointcut()") public void afterLog(JoinPoint joinPoint) { // 后置通知 System.out.println("方法执行完成:" + joinPoint.getSignature().getName()); } @AfterThrowing(pointcut = "logPointcut()", throwing = "ex") public void afterThrowingLog(JoinPoint joinPoint, Exception ex) { // 异常通知 System.out.println("方法执行异常:" + joinPoint.getSignature().getName() + ",异常信息:" + ex.getMessage()); } } ``` 2. 定义业务逻辑类 ```java @Service public class UserService { @Log public void addUser(User user) { // 添加用户 System.out.println("添加用户:" + user.getName()); } @Log public void deleteUser(String userId) { // 删除用户 System.out.println("删除用户:" + userId); throw new RuntimeException("删除用户异常"); } } ``` 3. 在配置文件中开启AOP ```xml <aop:aspectj-autoproxy/> <context:component-scan base-package="com.example"/> ``` 在这个例子中,我们定义了一个切面类LogAspect,其中通过@Aspect注解定义了一个切面,通过@Pointcut注解定义了一个切点,通过@Before、@AfterReturning和@AfterThrowing注解分别定义了前置通知、后置通知和异常通知。 在业务逻辑类中,我们通过@Log注解标注了需要增强的方法。 最后,在配置文件中,我们通过<aop:aspectj-autoproxy/>开启了AOP功能,并通过<context:component-scan>扫描了指定包下的所有组件。 这样,当我们调用UserService中的方法时,就会触发LogAspect中定义的通知,从而实现对原有代码的增强。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值