Spring CGLIB 代理仅拦截 public 方法调用
Spring CGLIB proxies intercept only public method calls
Spring 文档指出:
CGLIB proxies intercept only public method calls! Do not call
non-public methods on such a proxy. They are not delegated to the
actual scoped target object.
但经过观察,我认为要么我的实验(下面的代码)不好,要么这种行为随着时间的推移而改变了。
我观察到只有 final 或私有方法被绕过。
这是实验:(spring 版本:5.1.3
)
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
private StudentService studentService;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
StudentService studentService() {
ProxyFactory proxyFactory = new ProxyFactory(new StudentService());
proxyFactory.setProxyTargetClass(true);
proxyFactory.addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("method " + invocation.getMethod() + " is called on " +
invocation.getThis() + " with args " + invocation.getArguments());
Object ret = invocation.proceed();
System.out.println("method " + invocation.getMethod() + " returns " + ret);
return ret;
}
});
return (StudentService) proxyFactory.getProxy();
}
@Override
public void run(String... args) throws Exception {
studentService.doIt();
}
class StudentService {
void doIt() {
System.out.println("doIt");
}
}
输出:
method void com.example.demo.DemoApplication$StudentService.doIt() is called on com.example.demo.DemoApplication$StudentService@127a7a2e with args [Ljava.lang.Object;@14008db3
doIt
method void com.example.demo.DemoApplication$StudentService.doIt() returns null
此外 - 通过实验 - CGLIB 库(没有 spring,使用增强器 class)也允许代理包级方法。
更新
我还有一个观察结果(与上面的相反)。
在典型的 jdbc 应用程序中:
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
private ExampleService exampleService;
@Autowired
private XExampleService xExampleService;
@Autowired
private XXExampleService xxExampleService;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println(exampleService.getClass());
System.out.println(xExampleService.getClass());
System.out.println(xxExampleService.getClass());
}
@Service
class ExampleService {
void tranx() {
}
}
@Service
class XExampleService {
@org.springframework.transaction.annotation.Transactional
void tranx() {
}
}
@Service
class XXExampleService {
@Transactional
public void tranx() {
}
}
}
输出:
class com.example.demo.DemoApplication$ExampleService
class com.example.demo.DemoApplication$XExampleService
class com.example.demo.DemoApplication$XXExampleService$$EnhancerBySpringCGLIB$b1603e8
这意味着,如果 spring 按照我的行为创建代理 - 就像在 TransactionInterceptor 中一样 - CGLIB 代理仅为 public 方法创建。
更新2
我想我发现了这种接受 public 方法的行为只发生在哪里。
它发生在 PointCut (TransactionAttributeSourcePointcut
) 使用的 AnnotationTransactionAttributeSource
。
来自代码:
/**
* Create a custom AnnotationTransactionAttributeSource, supporting
* public methods that carry the {@code Transactional} annotation
* or the EJB3 {@link javax.ejb.TransactionAttribute} annotation.
* @param publicMethodsOnly whether to support public methods that carry
* the {@code Transactional} annotation only (typically for use
* with proxy-based AOP), or protected/private methods as well
* (typically used with AspectJ class weaving)
*/
public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
可能的答案是因为基于 Java 的代理无法使用 package-private
方法(因为 Java 实现 类 无法将较弱的访问权限分配给接口实现的方法) .
所以这可能会阻止 spring 团队对包私有方法进行操作,即使 CGLib 可以代理它们。
Spring 文档指出:
CGLIB proxies intercept only public method calls! Do not call non-public methods on such a proxy. They are not delegated to the actual scoped target object.
但经过观察,我认为要么我的实验(下面的代码)不好,要么这种行为随着时间的推移而改变了。
我观察到只有 final 或私有方法被绕过。
这是实验:(spring 版本:5.1.3
)
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
private StudentService studentService;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
StudentService studentService() {
ProxyFactory proxyFactory = new ProxyFactory(new StudentService());
proxyFactory.setProxyTargetClass(true);
proxyFactory.addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("method " + invocation.getMethod() + " is called on " +
invocation.getThis() + " with args " + invocation.getArguments());
Object ret = invocation.proceed();
System.out.println("method " + invocation.getMethod() + " returns " + ret);
return ret;
}
});
return (StudentService) proxyFactory.getProxy();
}
@Override
public void run(String... args) throws Exception {
studentService.doIt();
}
class StudentService {
void doIt() {
System.out.println("doIt");
}
}
输出:
method void com.example.demo.DemoApplication$StudentService.doIt() is called on com.example.demo.DemoApplication$StudentService@127a7a2e with args [Ljava.lang.Object;@14008db3
doIt
method void com.example.demo.DemoApplication$StudentService.doIt() returns null
此外 - 通过实验 - CGLIB 库(没有 spring,使用增强器 class)也允许代理包级方法。
更新
我还有一个观察结果(与上面的相反)。 在典型的 jdbc 应用程序中:
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
private ExampleService exampleService;
@Autowired
private XExampleService xExampleService;
@Autowired
private XXExampleService xxExampleService;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println(exampleService.getClass());
System.out.println(xExampleService.getClass());
System.out.println(xxExampleService.getClass());
}
@Service
class ExampleService {
void tranx() {
}
}
@Service
class XExampleService {
@org.springframework.transaction.annotation.Transactional
void tranx() {
}
}
@Service
class XXExampleService {
@Transactional
public void tranx() {
}
}
}
输出:
class com.example.demo.DemoApplication$ExampleService
class com.example.demo.DemoApplication$XExampleService
class com.example.demo.DemoApplication$XXExampleService$$EnhancerBySpringCGLIB$b1603e8
这意味着,如果 spring 按照我的行为创建代理 - 就像在 TransactionInterceptor 中一样 - CGLIB 代理仅为 public 方法创建。
更新2
我想我发现了这种接受 public 方法的行为只发生在哪里。
它发生在 PointCut (TransactionAttributeSourcePointcut
) 使用的 AnnotationTransactionAttributeSource
。
来自代码:
/**
* Create a custom AnnotationTransactionAttributeSource, supporting
* public methods that carry the {@code Transactional} annotation
* or the EJB3 {@link javax.ejb.TransactionAttribute} annotation.
* @param publicMethodsOnly whether to support public methods that carry
* the {@code Transactional} annotation only (typically for use
* with proxy-based AOP), or protected/private methods as well
* (typically used with AspectJ class weaving)
*/
public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
可能的答案是因为基于 Java 的代理无法使用 package-private
方法(因为 Java 实现 类 无法将较弱的访问权限分配给接口实现的方法) .
所以这可能会阻止 spring 团队对包私有方法进行操作,即使 CGLib 可以代理它们。