为什么 Spring 的 @Transactional 不能用于受保护的方法?

Why doesn't Spring's @Transactional work on protected methods?

来自Does Spring @Transactional attribute work on a private method?

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings.

我能想到排除 privatepackage-private 方法的充分理由,但为什么 protected 方法不能进行事务处理?以下堆栈跟踪显示 public 方法(通过接口代理调用)的正确行为:

at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_51]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281) ~[spring-tx-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at com.sun.proxy.$Proxy145.improveType(Unknown Source) ~[na:na]

调用 "identical" 受保护的方法(通过非接口 CGLIB 代理)时,我们得到以下信息:

at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_51]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653) ~[spring-aop-4.2.1.RELEASE.jar:4.2.1.RELEASE]
at my.company.webservices.facade.EntityFacade$$EnhancerBySpringCGLIB$$fd77735b.findEntity(<generated>) ~[spring-core-4.2.1.RELEASE.jar:na]

这显然是一个设计决定(为什么?),但我认为它毫无提示地失败是相当值得怀疑的,而这显然是开发人员的错误。

编辑 这在使用接口时显然不是问题(接口中只有 public 方法),但是因为 Spring 不一定需要接口来通过 CGLIB 代理对象,调用受保护的 @Transactional方法的行为就像 public 方法(即通过代理调用),只是 设计 它忽略了事务性。

因为这个:

In proxy mode (which is the default), only 'external' method calls coming in through the proxy will be intercepted. This means that 'self-invocation', i.e. a method within the target object calling some other method of the target object, won't lead to an actual transaction at runtime even if the invoked method is marked with @Transactional!

还有这个:

Due to the proxy-based nature of Spring's AOP framework, protected methods are by definition not intercepted, neither for JDK proxies (where this isn't applicable) nor for CGLIB proxies (where this is technically possible but not recommendable for AOP purposes). As a consequence, any given pointcut will be matched against public methods only!

Spring 人可能希望与 JDK 代理保持一致。您不希望根据 JDK 与 CGLIB 有不同的代理配置和不同的结果。

受保护的方法不是 public 合同的一部分。这就是为什么他们没有被代理。在大多数情况下,消费者看不到它们。

如果您 a) 通过 IF 连接并向下转换为具体实现(坏)或 b) 连接具体实现并且使用者驻留在同一个包中,则可以调用受保护的方法。因为只有 b) 有意义,所以我可以理解为什么 Spring 不代理受保护的方法。这是一个罕见的极端情况,只适用于 CGLIB,不适用于 JDK 代理。

也许您想知道扩展 bean 和调用的用例 super.myProtectedMethod():这些调用根本没有代理,与访问级别无关。

其他答案的补充信息。

这是来自 the Spring blog 的示例图片:

如您所见,代理环绕着实现 class(此处为 AccountServiceImpl),代理本身仅实现来自 AccountService 接口的方法。 Interface 仅提供 public 方法(public 契约),因此代理不能环绕受保护的方法并提供它在 public 方法上提供的事务行为.

如果您使用 AspectJ,则可以在同一服务中调用方法,但我不确定这是否也适用于 protected 方法,因为我直到现在。