Spring5学习笔记——AOP

Spring5

  • AOP
    • 概念
    • 底层原理
    • 相关术语
    • AOP 操作的准备工作
    • 切入点表达式
    • AspectJ 注解
    • 相同切入点的抽取
    • 设置代理类的优先级
    • 完全注解开发模式
    • 使用 xml 配置文件实现 AspectJ 注解

AOP

概念

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

底层原理

AOP 的底层使用动态代理实现,分两种情况:
(1)有接口时,使用 JDK 动态代理,创建接口实现类的代理对象。
Spring5学习笔记——AOP_第1张图片

(2)没有接口时,使用 CGLIB 动态代理,创建当前类的子类的代理对象。
Spring5学习笔记——AOP_第2张图片
演示 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 相关依赖
    Spring5学习笔记——AOP_第3张图片
    其中,前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)开启组件扫描
    (3)开启 AOP 生成代理对象,即扫描 @Aspect 注解
  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


<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">

	
	<context:component-scan base-package="com.mcc.spring5.aop.aspectj">context:component-scan>
	
	
	<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)使用标签创建被增强类和增强类对象
(2)使用标签配置 AOP
(3)在 AOP 配置标签内,使用标签配置切入点

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

(4)在 AOP 配置标签内,使用标签配置切面

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

配置文件 aopAspectJXML.xml


<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:config>
		
		<aop:pointcut expression="execution(* com.mcc.spring5.aop.aspectjXML.Book.showInfo(..))" id="showInfo"/>
		
		<aop:aspect ref="bookProxy">
			
			<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();
	}
}

你可能感兴趣的:(笔记,Spring5,aop)