首页 > 其他分享 >[火眼速查] Spring 速查指南(四)

[火眼速查] Spring 速查指南(四)

时间:2024-07-24 13:26:14浏览次数:10  
标签:连接点 Spring AOP 注解 速查 方法 public 火眼

简介

Spring 是一款开源的 J2EE 框架,它有许多项目,为 Java 应用开发提供了一整套的工具,其中最核心的就是 Spring Framework 和 Spring Boot 项目。

文本是一个系列文章的第一篇,下面就这两个项目的核心内容做一些速查整理,同时辅以生产源码,便于理解。

相关文章

面向切面编程(AOP)

面向切面编程(Aspect-oriented Programming,AOP)与面向对象编程(Object-oriented Programming,OOP)的思考模式不一样,面向对象编程的关注点是类,而面向切面编程的关注点是切面。AOP 可以将一些功能(如事务管理、日志记录等)模块化,与业务功能解耦,实现代码复用,提高代码的可扩展性和可维护性。

AspectJ 是一个很成熟的 AOP 实现,而 Spring AOP 指的是 Spring 框架提供的 AOP 能力,Spring 的 IoC 容器并不强依赖于 AOP,但使用 AOP 能够让 Spring IoC 容器提供强大的中间件解决方案。

AOP 的实现分为静态和动态两种,静态 AOP 在编译期对源代码进行修改,生成的字节码已经被代理,如比较成熟的 AspectJ 框架。而动态 AOP 则在运行时生成动态代理对象,Spring AOP 就采用了这种方式。

Spring AOP 主要提供了:

  • 声明式服务,其中最重要的就是事务管理
  • 让用户自定义切面,与面向对象编程互补

AOP 核心概念

AOP 中有几个核心概念:

  • 切面(Aspect):横跨多个类的模块化的关注点,简单理解就是对多个类的相同的功能抽取成模块化,包含了切入点和通知。Spring AOP 实现的方式有使用普通类(基于模式的方法,schema-based)和使用注解方式(@AspectJ 风格)。
  • 连接点(Join point):程序执行中特定的点,例如方法调用或抛出异常。在 Spring AOP 中,连接点只是方法调用。
  • 通知(Advice):通知就是切面在连接点上的操作。
  • 切入点(Pointcut):切入点代表了一组连接点,通过切入点表达式来匹配。
  • 简介(Introduction):给被增强的对象引入一个接口和实现类,给对象注入新的成员变量或成员方法,也就是 AspectJ 的 inter-type declaration。
  • 目标对象(Target object):目标对象就是被切面增强的对象,由于 Spring AOP 使用运行时代理实现,这个对象总是被代理对象。
  • AOP 代理(AOP proxy):AOP 代理是由 AOP 框架创建的实现切面增强的对象,在 Spring AOP 中使用 JDK 动态代理或 CGLIB 代理。
  • 织入(Weaving):织入是将切面和目标对象连接起来创建代理对象的过程,一般可以在编译时和运行时。Spring AOP 框架采用了运行时织入。

概括来说就是,切面通过切入点找到目标对象上的连接点,并把通知(增强代码)织入生成新的 AOP 代理对象。我们调用目标对象的方法,其实是调用 AOP 代理对象的方法,这样就可以在不改变原目标对象的前提下,增强它的功能。

如果觉得上面的描述不好理解,在 Spring AOP 中,我们可以做如下简化理解。

  • 切面:一个添加了特定注解的 Bean,包含了切入点和通知。
  • 连接点:就是我们需要增强的方法。
  • 通知:我们需要对目标方法增强的功能。
  • 切入点:找到连接点方法的表达式。

而实现的方式就是生成一个代理对象,替我们去操作目标对象。

例如,普通调用一个对象的方法像这样。
普通调用

当有代理时,调用就变成了这样。
通过代理调用

通知就是对连接点(目标对象方法)的增强,Spring AOP 提供了以下几种通知类型:

  • 前置通知(Before advice):在连接点前执行,但不能终止后续代码的执行(除非抛出异常)。
  • 返回通知(After returning advice):在连接点正常完成执行并返回后执行(未抛出异常的情况下)。
  • 异常通知(After throwing advice):方法执行抛出异常时执行。
  • 后置通知(After advice):在连接点之后执行,不管是正常完成还是抛出异常。
  • 环绕通知(Around advice):在连接点的调用前后执行,这是最强大的通知,它可以选择继续执行连接点或者终止(直接返回结果或抛出异常)。

虽然环绕通知可以替代其他通知,但是 Spring 官方还是建议使用最小化通知,以避免产生不必要的错误。

Spring AOP 由纯 Java 实现,只支持方法调用连接点。字段拦截并未实现,如果需要字段拦截能力,可以使用其他 AOP 框架如 AspectJ。

AOP 的关键就是由切入点来匹配连接点,对于 Spring AOP 来说,就是找到特定的方法调用,来增强这个方法。

@AspectJ 注解方式

Spring AOP 支持 @AspectJ 的注解方式,可以使用 @AspectJ 提供的注解来使用 AOP,但仍是由 Spring AOP 来实现的。要启用的话先使用注解 @EnableAspectJAutoProxy 开启。

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

声明切面

开启后就可以使用 @Aspect 注解标记类来声明切面,在 Spring 中,切面类可以是一个 Bean。

声明切入点

在 Spring AOP 中,连接点只能是方法调用,因此切入点就是找到需要增强的方法。在切面类中使用 @Pointcut 注解来声明切入点。

@Pointcut("execution(* transfer(..))") // 切入点表达式
private void anyOldTransfer() {} // 切入点签名

我们主要就是通过切入点表达式来匹配要增强的方法。Spring AOP 支持如下的 AspectJ 切入点代码(AspectJ pointcut designators,PCD):

  • execution:用于匹配方法执行的连接点,这是 Spring AOP 最主要的 PCD。
  • within:限制匹配的连接点在特定的类型中。
  • this:限制匹配的连接点的代理对象是某个类型的实例。
  • target:限制匹配的连接点的目标对象是某个类型的实例。
  • args:限制匹配的连接点的参数是某个类型的实例。
  • @target:限制匹配的连接点的目标对象的类具有某个类型的注解。
  • @args:限制匹配的连接点的运行时实际参数具有某个类型的注解。
  • @within:限制匹配的连接点具有指定的注解。
  • @annotation:限制匹配的连接点上的注解
  • bean:Spring AOP 额外提供的可以用于限制连接点的 Bean 名称,支持通配符(*)。

其他一些 AspectJ 的 PCD 则不被 Spring AOP 所支持。

语法

最常用的 execution 语法如下所示:

execution(修饰符? 返回类型 类型声明? 方法名(参数) 异常?)

其中,返回类型、方法名、参数是必填的,其他可选,返回类型、类型声明和方法名中可以使用通配符(*),参数 () 代表没有参数,而 (..) 则代表任意参数。类型名都要写全限定名称。

// 任意 public 方法
execution(public * *(..))

// 以 set 开头的任意方法
execution(* set*(..))

// AccountService 接口里定义的任意方法
execution(* com.xyz.service.AccountService.*(..))

// com.xyz.service 包下的任意方法
execution(* com.xyz.service.*.*(..))

// com.xyz.service 包及其子包下的任意方法
execution(* com.xyz.service..*.*(..))

within 则更简单,可以限制类型。

// com.xyz.service.* 包下的任意方法
within(com.xyz.service.*)

// com.xyz.service 包及其子包下的任意方法
within(com.xyz.service..*)

this 则匹配代理对象。

// 代理对象实现 AccountService 接口的任意方法
this(com.xyz.service.AccountService)

target 则匹配目标对象。

// 目标对象实现 AccountService 接口的任意方法
target(com.xyz.service.AccountService)

args 匹配方法的参数类型。

// 匹配单个参数且运行时传递过来是 Serializable 类型的方法
args(java.io.Serializable)

和 execution 语法 execution(* *(java.io.Serializable)) 不同的是,args 匹配的是运行时的类型,而 execution 匹配的是签名中的类型。

@target 匹配目标对象的注解。

// 目标对象具有 Transactional 注解的任意方法
@target(org.springframework.transaction.annotation.Transactional)

@args 匹配运行时的方法注解。

// 匹配单个参数且运行时传递过来具有 Classified 注解的方法
@args(com.xyz.security.Classified)

@within 匹配类上的注解。

// 匹配具有 Transactional 注解的类的任意方法
@within(org.springframework.transaction.annotation.Transactional)

@target / @within 和 target / within 两对很相似,主要的区别就是 @within / within 匹配的类型,如果有继承的子类(没有覆盖方法)也会匹配, 而 @target / target 匹配的是运行时的对象,不会考虑子类。

例如:

@A1
public class Person {

    public void say(String sentence) {
        System.out.println("Person says:" + sentence);
    }

    public void run() {
        System.out.println("Person runs." );
    }
}

public class Student extends Person {

    @Override
    public void run() {
        System.out.println("Student runs." );
    }

    public void learn() {
        System.out.println("Student learn." );
    }
}

有如下的切入点表达式:

// 匹配 Person 类的所有方法和 Student 类未覆盖的方法,不会匹配 Student 类覆盖的 run 和新加的 learn
@within(com.annotation.other.A1)

// 匹配 Person 类的所有方法,而 Student 类没有注解 A1,都不会被匹配
@target(com.annotation.other.A1)

@annotation 匹配连接点的注解,也就是方法上的注解。

// 具有 @Transactional 注解的任意方法
@annotation(org.springframework.transaction.annotation.Transactional)

Bean 可以匹配 Bean 名称。

// Bean 名为 tradeService 的任意方法
bean(tradeService)

// Bean 名称以 Service 结尾的任意方法
bean(*Service)

切入点表达式可以使用 &&||! 逻辑运算符组合。

详细可参考 AspectJ 编程指南

声明通知

通知可以和切入点表达式相结合,在切面类中添加通知。

前置通知

在切面类中使用 @Before 注解声明前置通知。

@Aspect
public class BeforeExample {

	@Before("execution(* com.xyz.dao.*.*(..))")
	public void doAccessCheck() {
		// ...
	}
}

可以在注解中直接写切入点表达式,或者先定义切入点再引用,这样可以复用切入点。

@Aspect
public class BeforeExample {

    @Pointcut("execution(* com.xyz.dao.*.*(..))")
	public void dataAccessOperation() {}

    // 使用全限定名,如果是当前类中的则可以省略包名
	@Before("dataAccessOperation()")
	public void doAccessCheck() {
		// ...
	}
}
返回通知

在切面类中使用 @AfterReturning 声明返回通知,返回通知只在方法正常返回时执行。

@Aspect
public class AfterReturningExample {

	@AfterReturning("execution(* com.xyz.dao.*.*(..))")
	public void doAccessCheck() {
		// ...
	}
}

如果需要使用方法的返回值,可以使用 returning 参数指定,在通知方法的参数中获取。参数的类型也会限制匹配的方法,下面的 Object 则不限制。

@Aspect
public class AfterReturningExample {

	@AfterReturning(
		pointcut="execution(* com.xyz.dao.*.*(..))",
		returning="retVal")
	public void doAccessCheck(Object retVal) {
		// ...
	}
}
异常通知

在切面类中使用 @AfterThrowing 注解声明异常通知,当方法抛出异常时执行。

@Aspect
public class AfterThrowingExample {

	@AfterThrowing("execution(* com.xyz.dao.*.*(..))")
	public void doRecoveryActions() {
		// ...
	}
}

如果要限制并获取异常对象,可以使用 throwing 参数。

@Aspect
public class AfterThrowingExample {

	@AfterThrowing(
		pointcut="execution(* com.xyz.dao.*.*(..))",
		throwing="ex")
	public void doRecoveryActions(DataAccessException ex) {
		// ...
	}
}

注:连接点方法抛出的异常才会匹配,通知方法抛出的不会。

后置通知

在切面类中使用 @After 注解声明后置通知,后置通知必须要处理正常返回和抛出异常的场景。

@Aspect
public class AfterFinallyExample {

	@After("execution(* com.xyz.dao.*.*(..))")
	public void doReleaseLock() {
		// ...
	}
}
环绕通知

在切面类中使用 @Around 注解声明环绕通知,环绕通知在方法的前后都可以插入代码,并可以决定是否要真正执行连接点方法。虽然环绕通知可以替代之前的通知,但官方建议是最小化使用,因为环绕通知有很多注意事项,一不小心就会遗漏。

@Aspect
public class AroundExample {

	@Around("execution(* com.xyz..service.*.*(..))")
	public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
		// 前置处理,然后调用连接点方法
		Object retVal = pjp.proceed();
		// 后置处理
		return retVal;
	}
}

环绕通知的处理方法应该声明返回值类型为 Object,它的返回值会返回给连接点方法的调用方,如果环绕通知方法的返回值声明为 void,则返回 null 给调用方。

环绕通知的处理方法的第一个参数必须是 ProceedingJoinPoint 类型。在通知方法体中,需要调用 ProceedingJoinPoint 对象的 proceed() 方法来调用原连接点方法。如果调用 proceed() 时不提供参数,则使用原调用者提供的参数。当然可以主动提供参数来替换,proceed() 也接受 Object[] 数组作为参数,作为连接点方法的参数。

通知参数
获取连接点信息

每个通知处理方法可以声明第一个参数为 org.aspectj.lang.JoinPoint 类型,来获取连接点的信息(环绕通知的第一个参数是 ProceedingJoinPoint,是 JoinPoint 的子类)。

JoinPoint 接口提供了一些有用的方法:

  • getArgs():可获取连接点方法的参数
  • getThis():可获取代理对象
  • getTarget():可获取目标对象
  • getSignature():可获取被增强方法的描述
  • toString():输出连接点方法的描述信息

这里可以查询详细的 API。

获取参数信息

如果使用了 args 切入点表达式,则可以在通知方法中获取对应的参数,这样就可以根据传递的参数来进行处理。

@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
public void validateAccount(Account account) {
	// ...
}

其他的切入点表达式也可以获得对应的对象作为参数,this 获取代理对象,target 获取目标对象,@within、@target、@annotation 和 @args 则获取对应的注解对象。

参数范型

如果参数中包含范型,如下面的接口:

public interface Sample<T> {
	void sampleGenericMethod(T param);
	void sampleGenericCollectionMethod(Collection<T> param);
}

则可以通过参数类型来限制指定的类型才能被匹配。

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
	// Advice implementation
}

但如果是范型集合,则无法生效,只能使用 Collection<?> 不限制类型,在通知方法中自行校验。

指定参数名

通知注解提供了 argNames 参数来指定通知函数的参数名。

@Before(
	value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)",
	argNames = "bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
	AuditCode code = auditable.value();
	// ...
}
通知执行顺序

当有多个通知匹配到同一个连接点方法时,Spring AOP 和 AspectJ 采用了相同的策略。在连接点方法前,优先级从高到低执行,在连接点方法后,优先级从低到高执行。而在同一个切面类中,优先级从高到低分别是:@Around, @Before, @After, @AfterReturning, @AfterThrowing。也就是说,对于同一个连接点方法,@After 总是在 @AfterReturning 和 @AfterThrowing 之后执行。对于相同的通知或者在不同的切面类中,执行顺序是不确定的,建议使用 @Order 注解指定优先级,切面类也可以实现 Ordered 接口,通过 getOrder() 方法来指定优先级。

简介

简介(AspectJ 中称为 inter-type declarations)使被增强对象实现一个指定的接口,并提供一个接口的实现来注入成员变量或成员方法。

使用 @DeclareParents 注解来声明简介,假设有一个接口 UsageTracked,和这个接口的实现类 DefaultUsageTracked,下面的声明表示给所有 service 下的实现类都添加了一个 UsageTracked 接口。

@Aspect
public class UsageTracking {

	@DeclareParents(value="com.xyz.service.*+", defaultImpl=DefaultUsageTracked.class)
	public static UsageTracked mixin;

	@Before("execution(* com.xyz..service.*.*(..)) && this(usageTracked)")
	public void recordUsage(UsageTracked usageTracked) {
		usageTracked.incrementUseCount();
	}

}

简介提供了一种将目标对象和接口的实现类组合生成新的代理对象的方法,可以将一个功能提取到接口中,并在不修改目标类的情况下“复用”代码。

基于模式(Schema-based)的方式

Spring 也提供了基于 XML 的声明方式,切入点表达式的语法和通知类型都和上面的一致。要使用这种方式,需要引入 spring-aop 的 XML 模式,参考这里

<aop:config>

	<aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">

		<aop:pointcut id="idempotentOperation"
			expression="execution(* com.xyz.service.*.*(..))"/>

		<aop:around
			pointcut-ref="idempotentOperation"
			method="doConcurrentOperation"/>

	</aop:aspect>

</aop:config>

<bean id="concurrentOperationExecutor"
	class="com.xyz.service.impl.ConcurrentOperationExecutor">
		<property name="maxRetries" value="3"/>
		<property name="order" value="100"/>
</bean>

建议优先使用注解的方式,这里就不多展开了。

AOP 代理

Spring AOP 使用 JDK 动态代理或 CGLIB 代理在运行时创建代理对象。JDK 动态代理是 JDK 内置的能力,而 CGLIB 则是一个开源库。如果目标对象实现了至少一个接口,则 Spring AOP 优先使用 JDK 动态代理,否则就使用 CGLIB 代理。

JDK 动态代理只能代理实现了接口的类,而 CGLIB 可以代理任何类。因为 JDK 动态代理通过接口反射实现,而 CGLIB 代理是通过生成一个被代理类的子类来拦截被代理类的方法调用实现。因此 CGLIB 不能代理声明为 final 类型的类和方法。一般来说,JDK 动态代理更简单,且是 JDK 直接支持的方式,因此 Spring 优先使用 JDK 动态代理。

如果要强制使用 CGLIB 代理,则可以如下配置:

注解方式

在 EnableAspectJAutoProxy 注解的参数 proxyTargetClass 设置为 true。

XML 方式

<aop:config proxy-target-class="true">
	<!-- other beans defined here... -->
</aop:config>

(未完待续)

如果觉得有用,请多多支持,点赞收藏吧!

标签:连接点,Spring,AOP,注解,速查,方法,public,火眼
From: https://blog.csdn.net/wwtg9988/article/details/140595285

相关文章

  • 基于Java+SpringBoot+Vue的卓越导师双选系统的设计与开发(源码+lw+部署文档+讲解等)
    文章目录前言项目背景介绍技术栈后端框架SpringBoot前端框架Vue数据库MySQL(MyStructuredQueryLanguage)具体实现截图详细视频演示系统测试系统测试目的系统功能测试系统测试结论代码参考数据库参考源码获取前言......
  • SpringMVC基础
    SpringMVCssm:mybatis+Spring+SpringMVCMVC三层架构1、什么是MVCMVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范是将业务逻辑、数据、显示分离的方法来组织代码MVC的主要作用是降低了视图与业务逻辑间的双向耦合MVC不是一种设计模式,MVC是一......
  • Spring 事务管理
    Spring事务管理(1)注解注解:@Transactional位置:业务层的方法上、类上、接口上作用:将当前方法交给spring进行事务管理,要么同时生效要么都不生效(2)开启事务管理日志yml文件中:logginglevel:org.springframework.jdbcTransactionManger:debug(3)默认情况下,只有出现运行时......
  • 基于springboot的助农服务平台
    基于springboot的助农服务app介绍2024届软件工程毕业设计 该项目是基于springboot的助农App的设计及实现,主要实现了管理员,用户,商家三个端的设计,其中主要实现的功能有产品模块,订单模块,购物车模块,以及相关联的管理模块,秒杀等,帮助农民出售农作物,提高农业水平的发展,提高农民......
  • 基于Java+SpringBoot+Vue的精品在线试题库系统的设计与开发(源码+lw+部署文档+讲解等)
    文章目录前言项目背景介绍技术栈后端框架SpringBoot前端框架Vue数据库MySQL(MyStructuredQueryLanguage)具体实现截图详细视频演示系统测试系统测试目的系统功能测试系统测试结论代码参考数据库参考源码获取前言......
  • 【超实用攻略】SpringBoot + validator 轻松实现全注解式的参数校验
    一、故事背景关于参数合法性验证的重要性就不多说了,即使前端对参数做了基本验证,后端依然也需要进行验证,以防不合规的数据直接进入服务器,如果不对其进行拦截,严重的甚至会造成系统直接崩溃!本文结合自己在项目中的实际使用经验,主要以实用为主,对数据合法性验证做一次总结,不了解的朋......
  • springboot属性统一配置,分层级
    app.user.name=JohnDoeapp.user.age=30app.user.address.city=NewYorkapp.user.address.country=USAimportorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.context.annotation.Configuration;@Configuration......
  • SpringBoot整合SSE技术详解
    SpringBoot整合SSE技术详解1.引言在现代Web应用中,实时通信变得越来越重要。Server-SentEvents(SSE)是一种允许服务器向客户端推送数据的技术,为实现实时更新提供了一种简单而有效的方法。本文将详细介绍如何在SpringBoot中整合SSE,并探讨SSE与WebSocket的区别。2.SS......
  • 基于SpringBoot+Vue+uniapp的企业人才引进服务平台的详细设计和实现(源码+lw+部署文档
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • Spring Boot 自动配置原理
    Author:ACatSmilingSince:2024-07-23核心场景启动器SpringBoot的每个场景启动器都引入了一个spring-boot-starter,这是SpringBoot的核心场景启动器。<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId>&l......