栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring AOP原理分析(一)-- AOP相关概念

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring AOP原理分析(一)-- AOP相关概念

目录

一、AOP概述

二、AOP使用示例

三、AOP相关术语

四、总结


一、AOP概述

我们知道,使用面向对象编程(OOP)有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志、权限校验等,我们只有在每个对象里面引用公共行为,这样程序中就产生了大量的重复代码,程序就不便于维护了,所以就有了对面向对象编程的不从,即面向切面编程(AOP),AOP关注的方向是横向的,而OOP关注的是纵向。

AOP是Aspect Oriented Programming的缩写,意为:面向切面编程。AOP是指在程序运行期间动态地将某段代码切入到指定方法指定位置进行运行的编程方式。可以说AOP是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。

我们举一个比较容易理解的例子。假如我们需要监控系统中某些重要方法的执行时间,应该怎么实现?

  • (一)、方案一

    直接在每个方法执行前、后输出系统当前时间,直接相减就得到方法的执行时间。如下图:

    缺点:每个方法执行前、后都要写一遍同样的统计逻辑,而且统计执行时间跟具体的业务逻辑没什么关系,但是这样的话,直接将代码编写在一起,耦合度太高,且代码重复。

    那么有没有一种简便的方法,不需要每个方法都编写一次,其实AOP就提供了这样的功能,也就是下面的方案二。

  • (二)、方案二

    直接切入到每个方法的执行前、后,拦截到这些方法的执行,然后执行增强功能。如下图:

    这样接口就只需要关心具体的业务,而不需要关注其他非该接口的逻辑或处理,把额外的不涉及业务的代码放到切面类中进行处理,降低了它们之间的耦合度。

    二、AOP使用示例

    下面我们就通过AOP来实现前面举的例子----统计方法的执行时间。

    首先定义两个业务层接口,代码也比较简单:

    package com.wsh.service;
    
    public interface UserService {
    
    	void addUser();
    
    }
    
    @Service("userServiceImpl")
    public class UserServiceImpl implements UserService {
    
    	@Override
    	public void addUser() {
    		try {
    			TimeUnit.SECONDS.sleep(2);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println("新增用户...");
    	}
    }
    
    public interface ProductService {
    	void addProduct();
    }
    
    @Service("productServiceImpl")
    public class ProductServiceImpl implements ProductService {
    	@Override
    	public void addProduct() {
    		try {
    			TimeUnit.SECONDS.sleep(1);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println("添加商品...");
    	}
    }

    下面定义AOP配置类,主要是开启AOP注解支持、扫描组件类的功能:

    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan("com.wsh")
    public class AopConfig {
    
    }

    然后我们定义切面类,使用@Aspect注解标识:

    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.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    // @Aspect:告诉Spring这是一个切面类
    @Aspect
    @Component
    public class SimpleAspect {
    
    	// 定义切入点表达式
    	@Pointcut("execution(* com.wsh.service..*.*(..))")
    	private void pointcut() {
    	}
    
    	// 环绕通知
    	@Around("pointcut()")
    	public Object around(ProceedingJoinPoint proceedingJoinPoint) throws InterruptedException {
    		String methodName = proceedingJoinPoint.getSignature().getName();
    		System.out.println("执行" + methodName + "的环绕通知(@Around)...");
    		try {
    			long startTime = System.currentTimeMillis();
    			Object result = proceedingJoinPoint.proceed();
    			long endTime = System.currentTimeMillis();
    			System.out.println(methodName + "()方法耗时: " + (endTime - startTime) + "毫秒");
    			return result;
    		} catch (Throwable throwable) {
    			throwable.printStackTrace();
    			return null;
    		}
    	}
    
    	// 前置通知
    	@Before("pointcut()")
    	public void before(JoinPoint joinPoint) {
    		String methodName = joinPoint.getSignature().getName();
    		System.out.println("执行" + methodName + "的前置通知(@Before)...");
    	}
    
    	// 后置通知
    	@After("pointcut()")
    	public void after(JoinPoint joinPoint) {
    		String methodName = joinPoint.getSignature().getName();
    		System.out.println("执行" + methodName + "的后置通知(@After)...");
    	}
    
    	// 返回通知
    	@AfterReturning("pointcut()")
    	public void afterReturning(JoinPoint joinPoint) {
    		String methodName = joinPoint.getSignature().getName();
    		System.out.println("执行" + methodName + "的后置返回通知(@AfterReturning)...");
    	}
    
    }

    使用@Pointcut注解定义所有需要织入的连接点,也就是方法执行的地方。这里我们使用@Around环绕通知功能,@Around是在方法执行前、执行后执行。

    接着定义一个测试控制层接口:

    import com.wsh.service.ProductService;
    import com.wsh.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component("simpleController")
    public class SimpleController {
    	@Autowired
    	private UserService userService;
    	@Autowired
    	private ProductService productService;
    
    	public void test() {
    		userService.addUser();
    		System.out.println("==================");
    		productService.addProduct();
    	}
    
    }

    测试类:

    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class Test {
    	public static void main(String[] args) {
    		AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AopConfig.class);
    		SimpleController simpleController = (SimpleController) annotationConfigApplicationContext.getBean("simpleController");
    		simpleController.test();
    	}
    
    }

     输出结果如下:

    执行addUser的环绕通知(@Around)...
    执行addUser的前置通知(@Before)...
    新增用户...
    addUser()方法耗时: 2013毫秒
    执行addUser的后置通知(@After)...
    执行addUser的后置返回通知(@AfterReturning)...
    ==================
    执行addProduct的环绕通知(@Around)...
    执行addProduct的前置通知(@Before)...
    添加商品...
    addProduct()方法耗时: 1014毫秒
    执行addProduct的后置通知(@After)...
    执行addProduct的后置返回通知(@AfterReturning)...

    从输出结果可以看到,我们成功监控到方法的执行时间,AOP切面就像拦截器一样,拦截了切入点指定的方法的执行,并执行切面的增强功能:统计方法执行时间,以上就是关于Spring AOP一个简单的使用示例。

    三、AOP相关术语

    前面大概了解了Spring AOP的使用,下面描述一些AOP相关概念和术语:

    Spring官网对AOP相关术语的描述如下地址:Core Technologieshttps://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-introduction-defn

  • Aspect(切面):切面是跨越多个类的横切关注点的模块化,在 Spring AOP 中,切面是使用@Aspect注解注释的类,在 Aspect 中可以包含多个 Pointcut 以及相关的 Advice 定义。
  • Joinpoint(连接点):程序执行过程中的一个点,例如方法的执行或异常的处理。Joinpoint是所有可能被织入 Advice(增强) 的候选的点,在 Spring AOP中, 则可以认为所有方法执行点都是Joinpoint。
  • Advice(通知/增强):通知定义了将要织入到具体连接点(某个方法)的具体逻辑,通过 @Before、@After、@Around 来区别在 JointPoint 之前、之后还是环绕执行的代码。
  • Pointcut(切入点):表示一组Joinpoint,这些Joinpoint或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。通过Pointcut,我们就可以确定哪些 Jointpoint 可以被织入 Advice。
  • Target(目标对象):被一个或多个切面横切的对象,符合切入点表达式所指定的条件,被织入 Advice 的对象。
  • AOP proxy(代理对象):由 AOP 框架创建的对象,用于实现方面契约(建议方法执行等)。在 Spring framework 中,AOP 代理是 JDK 动态代理或 CGLIB 代理。
  • Weaving(织入):指的是将 Advice (增强)连接到 Pointcut 指定的 Joinpoint 处的过程。

    Spring AOP 包括以下类型的Advice(通知/增强):

  • Before advice(前置通知@Before):在目标方法执行之前运行的通知,但不能阻止执行流继续到连接点(除非它抛出异常);
  • After returning advice(返回后通知@AfterReturning):在目标方法正常返回之后运行的通知(例如,如果一个方法返回而没有抛出异常);After throwing advice(抛出通知后通知@AfterThrowing):如果目标方法通过抛出异常退出,则运行After throwing通知;After (finally) advice(最终通知@After):在目标方法运行结束之后运行,不管目标方法退出的方式(正常或异常返回)都将运行的通知;Around advice(环绕通知@Around):环绕目标方法的通知,例如方法调用。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续连接点还是通过返回自己的返回值或抛出异常来缩短方法执行;

    对照我们前面统计方法执行时间例子来说:

  • Jointpoint(连接点)就是“UserServiceImpl# addUser()” 或 “ProductServiceImpl#addProduct()” 每次被调用时所处的程序执行点;
  • Pointcut(切入点)就是用于指定 “连接点” 的一个表达式,当然这个表达式还可以指定很多其他的接口,表达式常见的格式为:“execution(* com.wsh.service..*.*(..))”;Aspect(切面)是定义通知、切入点的地方,使用@Aspect注解进行标识;Advice(通知)就是我们在“UserServiceImpl# addUser()” 或 “ProductServiceImpl#addProduct()”织入的统计方法执行时间的逻辑;
  • Weaving(织入)就是指将统计方法执行时间的逻辑加到 “UserServiceImpl# addUser()” 或 “ProductServiceImpl#addProduct()”的过程;Target(目标对象)就是定义了 “UserServiceImpl# addUser()” 或 “ProductServiceImpl#addProduct()”的对象实例;

    四、总结

    通过前面的总结,我们大致了解了Spring AOP的概念、相关术语以及使用方法。在实际项目中,我们可以使用AOP进行一些不涉及业务逻辑的功能,比如日志记录、性能统计、权限控制、事务处理、异常处理等等,将这些代码从业务逻辑中抽取出来,将它们独立到AOP切面中,这样可以降低各模块之间的耦合,并且改变改变这些行为的时候也不会影响业务逻辑的代码。

    Spring AOP主要是使用动态代理的技术实现的,主要是JDK动态代理、Cglib动态代理,本篇文章先熟悉一下AOP相关术语,下篇文章我们开始介绍AOP原理部分的内容。

  • 转载请注明:文章转载自 www.mshxw.com
    本文地址:https://www.mshxw.com/it/703523.html
    我们一直用心在做
    关于我们 文章归档 网站地图 联系我们

    版权所有 (c)2021-2022 MSHXW.COM

    ICP备案号:晋ICP备2021003244-6号