为什么 Spring-AOP 切入点对 save(..) 有效,但对 saveAll(..) 无效

问题描述 投票:0回答:2

我有一个像这样的 Spring-Data 存储库:

package com.example.demo;

@RepositoryRestResource
public interface FooRepository extends JpaRepository<Foo, Long> {

    @Override
    <S extends Foo> S save(S entity);

    @Override
    <S extends Foo> List<S> saveAll(Iterable<S> entities);

}

还有这样的一个方面:

@Aspect
@Component
public class FooAspect {

    @Before("execution(* org.springframework.data.repository.CrudRepository.save(*))")
    void crudSaveBefore(JoinPoint joinPoint) throws Throwable {
        System.out.println("crud save");
    }

    @Before("execution(* com.example.demo.FooRepository.save(*))")
    void fooSaveBefore(JoinPoint joinPoint) throws Throwable {
        System.out.println("foo save");
    }

    @Before("execution(* org.springframework.data.repository.CrudRepository.saveAll(*))")
    void crudSaveAll(JoinPoint joinPoint) throws Throwable {
        System.out.println("crud save all");
    }

    @Before("execution(* com.example.demo.FooRepository.saveAll(*))")
    void fooSaveAll(JoinPoint joinPoint) throws Throwable {
        System.out.println("foo save all");
    }

}

当我运行

fooRepository.save(..)
时,在控制台中我看到:
foo save

当我运行

fooRepository.saveAll(..)
时,在控制台中我看到
foo save all
crud save all

我期望

saveAll
只拦截
FooRepository
风味,因为我直接切点 package.class.method 。这似乎对
save
有效,但对
saveAll
无效。

这是因为

saveAll
中的参数是
Iterable
吗?或者泛型在这里发生某种类型擦除?还有别的吗?

java spring-boot aop spring-aop pointcut
2个回答
3
投票

看来是AOP问题。对于代理

FooRepository.saveAll
,它调用
CrudRepository.saveAll
@Before
表达式:

AbstractAspectJAdvice 683


0
投票

由于我遇到了类似的话题,我正在重新审视这个问题。我希望迟到的答复总比没有好。

我期望

saveAll
只拦截
FooRepository
风味,因为我直接切点 package.class.method 。这似乎对
save
有效,但对
saveAll
无效。

令我感到相当惊讶的是,并非所有 4 个切入点都匹配。令人困惑的问题是为什么

execution(* org.springframework.data.repository.CrudRepository.save(*))

匹配。我已经发现,这个问题不是由 Spring AOP 本身引起的,而是在本机 AspectJ 中表现出来。在那里,切入点也不匹配。

作为解决方法,更改切入点以使用

+
显式包含子类或实现接口的类:

execution(* org.springframework.data.repository.CrudRepository+.save(*))

现在,所有 4 个切入点都匹配,这也是说明原因的线索。

事实上

+
有帮助,意味着 AspectJ 切入点匹配器正确识别我们正在处理接口方法,并且
save
是跨层次结构继承的。与
saveAll
的区别在于类型擦除。没有泛型的
saveAll
看起来像
List saveAll(Iterable)
,这与原始接口相同,而对于
save
来说,接口中的
Object save(Object)
与实现类中的
Foo save(Foo)
更多。也就是说,方法签名直接匹配接口的签名,这就是为什么我们需要告诉 AspectJ 在匹配时也检查继承。

这并不是 AspectJ 中的错误,而是其在泛型类型和类型擦除方面的使用中的微妙之处。

saveAll
匹配的事实只是一个“幸运的机会”,因为在返回类型和方法参数中我们都使用特定类型,而泛型仅适用于这些类型的类型参数。然而,在
save
中,泛型类型直接按擦除方式使用。

反汇编类证实了我上面解释的内容:

public abstract interface org/springframework/data/repository/CrudRepository implements org/springframework/data/repository/Repository {
  // ...
  // signature <S:TT;>(TS;)TS;
  // declaration: S save<S extends T>(S)
  public abstract save(Ljava/lang/Object;)Ljava/lang/Object;
  // ...
  // signature <S:TT;>(Ljava/lang/Iterable<TS;>;)Ljava/lang/Iterable<TS;>;
  // declaration: java.lang.Iterable<S> saveAll<S extends T>(java.lang.Iterable<S>)
  public abstract saveAll(Ljava/lang/Iterable;)Ljava/lang/Iterable;
  // ...
}
class FooRepositoryImpl implements FooRepository {
  // ...
  // signature <S:LFoo;>(TS;)TS;
  // declaration: S save<S extends Foo>(S)
  public save(LFoo;)LFoo;
  // ...
  // signature <S:LFoo;>(Ljava/lang/Iterable<TS;>;)Ljava/util/List<TS;>;
  // declaration: java.util.List<S> saveAll<S extends Foo>(java.lang.Iterable<S>)
  public saveAll(Ljava/lang/Iterable;)Ljava/util/List;
  // ...
}
© www.soinside.com 2019 - 2024. All rights reserved.