spring之面向切面:AOP(2)

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 Grafana 版,10个用户账号 1个月
简介: 【1月更文挑战第15天】一、基于注解的AOP1、技术说明2、准备工作3、创建切面类并配置4、各种通知5、切入点表达式语法6、重用切入点表达式7、获取通知的相关信息8、环绕通知9、切面的优先级二、基于XML的AOP1、准备工作2、实现

文章目录

前言

一、基于注解的AOP

1、技术说明

2、准备工作

3、创建切面类并配置

4、各种通知

5、切入点表达式语法

6、重用切入点表达式

7、获取通知的相关信息

8、环绕通知

9、切面的优先级

二、基于XML的AOP

1、准备工作

2、实现

总结


前言

一、基于注解的AOP

1、技术说明

2、准备工作

3、创建切面类并配置

4、各种通知

5、切入点表达式语法

6、重用切入点表达式

7、获取通知的相关信息

8、环绕通知

9、切面的优先级

二、基于XML的AOP

1、准备工作

2、实现


一、基于注解的AOP

1、技术说明

  • 动态代理分为JDK动态代理和cglib动态代理
  • 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理
  • JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口
  • cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类
  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
  • AspectJ:是AOP思想的一种实现。本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

2、准备工作

①添加依赖

在IOC所需依赖基础上再加入下面依赖即可:

<dependencies><!--spring context依赖--><!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.2</version></dependency><!--spring aop依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>6.0.2</version></dependency><!--spring aspects依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.2</version></dependency><!--junit5测试--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.1</version></dependency><!--log4j2的依赖--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.19.0</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>2.19.0</version></dependency></dependencies>

②准备被代理的目标资源

接口:

publicinterfaceCalculator {
intadd(inti, intj);
intsub(inti, intj);
intmul(inti, intj);
intdiv(inti, intj);
}

实现类:

@ComponentpublicclassCalculatorImplimplementsCalculator {
@Overridepublicintadd(inti, intj) {
intresult=i+j;
System.out.println("方法内部 result = "+result);
returnresult;
    }
@Overridepublicintsub(inti, intj) {
intresult=i-j;
System.out.println("方法内部 result = "+result);
returnresult;
    }
@Overridepublicintmul(inti, intj) {
intresult=i*j;
System.out.println("方法内部 result = "+result);
returnresult;
    }
@Overridepublicintdiv(inti, intj) {
intresult=i/j;
System.out.println("方法内部 result = "+result);
returnresult;
    }
}

3、创建切面类并配置

// @Aspect表示这个类是一个切面类@Aspect// @Component注解保证这个切面类能够放入IOC容器@ComponentpublicclassLogAspect {
@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
publicvoidbeforeMethod(JoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
Stringargs=Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
    }
@After("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
publicvoidafterMethod(JoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Logger-->后置通知,方法名:"+methodName);
    }
@AfterReturning(value="execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning="result")
publicvoidafterReturningMethod(JoinPointjoinPoint, Objectresult){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
    }
@AfterThrowing(value="execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing="ex")
publicvoidafterThrowingMethod(JoinPointjoinPoint, Throwableex){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
    }
@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
publicObjectaroundMethod(ProceedingJoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
Stringargs=Arrays.toString(joinPoint.getArgs());
Objectresult=null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
//目标对象(连接点)方法的执行result=joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwablethrowable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
        }
returnresult;
    }
}

在Spring的配置文件中配置:

<?xmlversion="1.0" encoding="UTF-8"?><beansxmlns="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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--基于注解的AOP的实现:1、将目标对象和切面交给IOC容器管理(注解+扫描)2、开启AspectJ的自动代理,为目标对象自动生成代理3、将切面类通过注解@Aspect标识--><context:component-scanbase-package="com.atguigu.aop.annotation"></context:component-scan><aop:aspectj-autoproxy/></beans>

执行测试:

publicclassCalculatorTest {
privateLoggerlogger=LoggerFactory.getLogger(CalculatorTest.class);
@TestpublicvoidtestAdd(){
ApplicationContextac=newClassPathXmlApplicationContext("beans.xml");
Calculatorcalculator=ac.getBean( Calculator.class);
intadd=calculator.add(1, 1);
logger.info("执行成功:"+add);
    }
}

4、各种通知

  • 前置通知:使用@Before注解标识,在被代理的目标方法执行
  • 返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝
  • 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命
  • 后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论
  • 环绕通知:使用@Around注解标识,使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

各种通知的执行顺序:

  • Spring版本5.3.x以前:
  • 前置通知
  • 目标操作
  • 后置通知
  • 返回通知或异常通知
  • Spring版本5.3.x以后:
  • 前置通知
  • 目标操作
  • 返回通知或异常通知
  • 后置通知

5、切入点表达式语法

①作用

②语法细节

  • 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
  • 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
  • 例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
  • 在包名的部分,使用“*…”表示包名任意、包的层次深度任意
  • 在类名的部分,类名部分整体用*号代替,表示类名任意
  • 在类名的部分,可以使用*号代替类名的一部分
  • 例如:*Service匹配所有名称以Service结尾的类或接口
  • 在方法名部分,可以使用*号表示方法名任意
  • 在方法名部分,可以使用*号代替方法名的一部分
  • 例如:*Operation匹配所有方法名以Operation结尾的方法
  • 在方法参数列表部分,使用(…)表示参数列表任意
  • 在方法参数列表部分,使用(int,…)表示参数列表以一个int类型的参数开头
  • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
  • 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
  • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
  • 例如:execution(public int Service.(…, int)) 正确
    例如:execution(
    int *…Service.(…, int)) 错误

6、重用切入点表达式

①声明

@Pointcut("execution(* com.atguigu.aop.annotation.*.*(..))")
publicvoidpointCut(){}

②在同一个切面中使用

@Before("pointCut()")
publicvoidbeforeMethod(JoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
Stringargs=Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

③在不同切面中使用

@Before("com.atguigu.aop.CommonPointCut.pointCut()")
publicvoidbeforeMethod(JoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
Stringargs=Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

7、获取通知的相关信息

①获取连接点信息

获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参

@Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
publicvoidbeforeMethod(JoinPointjoinPoint){
//获取连接点的签名信息StringmethodName=joinPoint.getSignature().getName();
//获取目标方法到的实参信息Stringargs=Arrays.toString(joinPoint.getArgs());
System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

②获取目标方法的返回值

@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值

@AfterReturning(value="execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning="result")
publicvoidafterReturningMethod(JoinPointjoinPoint, Objectresult){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}

③获取目标方法的异常

@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常

@AfterThrowing(value="execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing="ex")
publicvoidafterThrowingMethod(JoinPointjoinPoint, Throwableex){
StringmethodName=joinPoint.getSignature().getName();
System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}

8、环绕通知

@Around("execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))")
publicObjectaroundMethod(ProceedingJoinPointjoinPoint){
StringmethodName=joinPoint.getSignature().getName();
Stringargs=Arrays.toString(joinPoint.getArgs());
Objectresult=null;
try {
System.out.println("环绕通知-->目标对象方法执行之前");
//目标方法的执行,目标方法的返回值一定要返回给外界调用者result=joinPoint.proceed();
System.out.println("环绕通知-->目标对象方法返回值之后");
    } catch (Throwablethrowable) {
throwable.printStackTrace();
System.out.println("环绕通知-->目标对象方法出现异常时");
    } finally {
System.out.println("环绕通知-->目标对象方法执行完毕");
    }
returnresult;
}

9、切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

  • 优先级高的切面:外面
  • 优先级低的切面:里面

使用@Order注解可以控制切面的优先级:

  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低

二、基于XML的AOP

1、准备工作

参考基于注解的AOP环境

2、实现

<context:component-scanbase-package="com.atguigu.aop.xml"></context:component-scan><aop:config><!--配置切面类--><aop:aspectref="loggerAspect"><aop:pointcutid="pointCut"expression="execution(* com.atguigu.aop.xml.CalculatorImpl.*(..))"/><aop:beforemethod="beforeMethod"pointcut-ref="pointCut"></aop:before><aop:aftermethod="afterMethod"pointcut-ref="pointCut"></aop:after><aop:after-returningmethod="afterReturningMethod"returning="result"pointcut-ref="pointCut"></aop:after-returning><aop:after-throwingmethod="afterThrowingMethod"throwing="ex"pointcut-ref="pointCut"></aop:after-throwing><aop:aroundmethod="aroundMethod"pointcut-ref="pointCut"></aop:around></aop:aspect></aop:config>

总结

以上就是spring之面向切面:AOP(2)的相关知识点,希望对你有所帮助。

积跬步以至千里,积怠惰以至深渊。时代在这跟着你一起努力哦!

相关文章
|
1月前
|
监控 Java API
掌握 Spring Boot AOP:使用教程
Spring Boot 中的面向切面编程(AOP)为软件开发提供了一种创新方法,允许开发者将横切关注点与业务逻辑相分离。这不仅提高了代码的复用性和可维护性,而且还降低了程序内部组件之间的耦合度。下面,我们深入探讨如何在 Spring Boot 应用程序中实践 AOP,以及它为项目带来的种种益处。
|
5天前
|
运维 Java 程序员
Spring5深入浅出篇:基于注解实现的AOP
# Spring5 AOP 深入理解:注解实现 本文介绍了基于注解的AOP编程步骤,包括原始对象、额外功能、切点和组装切面。步骤1-3旨在构建切面,与传统AOP相似。示例代码展示了如何使用`@Around`定义切面和执行逻辑。配置中,通过`@Aspect`和`@Around`注解定义切点,并在Spring配置中启用AOP自动代理。 进一步讨论了切点复用,避免重复代码以提高代码维护性。通过`@Pointcut`定义通用切点表达式,然后在多个通知中引用。此外,解释了AOP底层实现的两种动态代理方式:JDK动态代理和Cglib字节码增强,默认使用JDK,可通过配置切换到Cglib
|
5天前
|
XML Java 数据格式
Spring使用AOP 的其他方式
Spring使用AOP 的其他方式
15 2
|
5天前
|
XML Java 数据格式
Spring 项目如何使用AOP
Spring 项目如何使用AOP
19 2
|
9天前
|
XML 监控 安全
18:面向切面编程-Java Spring
18:面向切面编程-Java Spring
26 5
|
10天前
|
Java 开发者 Spring
Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
【5月更文挑战第1天】Spring AOP的切点是通过使用AspectJ的切点表达式语言来定义的。
23 5
|
10天前
|
XML Java 数据格式
Spring AOP
【5月更文挑战第1天】Spring AOP
27 5
|
11天前
|
Java 编译器 开发者
Spring的AOP理解
Spring的AOP理解
|
11天前
|
XML Java 数据格式
如何在Spring AOP中定义和应用通知?
【4月更文挑战第30天】如何在Spring AOP中定义和应用通知?
16 0
|
11天前
|
安全 Java 开发者
在Spring框架中,IoC和AOP是如何实现的?
【4月更文挑战第30天】在Spring框架中,IoC和AOP是如何实现的?
21 0