我有一个像这样的 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
吗?或者泛型在这里发生某种类型擦除?还有别的吗?
由于我遇到了类似的话题,我正在重新审视这个问题。我希望迟到的答复总比没有好。
我期望
只拦截saveAll
风味,因为我直接切点 package.class.method 。这似乎对FooRepository
有效,但对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;
// ...
}