Spring 将@EnableCaching与自定义AOP建议一起使用时,代理类型不匹配(JDK与CGLIB)
我一直试图让Spring的声明性缓存与一些定制AOP建议一起在应用程序中工作,但遇到了一个代理类型不匹配的问题 给定以下Spring Boot应用程序主类:Spring 将@EnableCaching与自定义AOP建议一起使用时,代理类型不匹配(JDK与CGLIB),spring,spring-aop,Spring,Spring Aop,我一直试图让Spring的声明性缓存与一些定制AOP建议一起在应用程序中工作,但遇到了一个代理类型不匹配的问题 给定以下Spring Boot应用程序主类: @SpringBootApplication @EnableCaching public class Application { @Bean public DefaultAdvisorAutoProxyCreator proxyCreator() { return new DefaultAdvisorAut
@SpringBootApplication
@EnableCaching
public class Application {
@Bean
public DefaultAdvisorAutoProxyCreator proxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
@Bean
public NameMatchMethodPointcutAdvisor pointcutAdvisor() {
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setClassFilter(new RootClassFilter(Service.class));
advisor.addMethodName("*");
advisor.setAdvice(new EnsureNonNegativeAdvice());
return advisor;
}
public static class EnsureNonNegativeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
if ((int) args[0] < 0) {
throw new IllegalArgumentException();
}
}
}
}
我希望通过以下测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class)
public class ApplicationIT {
@Autowired
private Service service;
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void getStringWithNegativeThrowsException() {
thrown.expect(IllegalArgumentException.class);
service.getString(-1);
}
}
(此代码在上的可运行项目中都可用)
但是,运行此测试将提供:
org.springframework.beans.factory.UnsatisfiedDependencyException:
...<snip>...
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'service' is expected to be of type 'me.hdpe.spring.cacheandaop.Service' but was actually of type 'com.sun.proxy.$Proxy61'
at org.springframework.beans.factory.support.DefaultListableBeanFactory.checkBeanNotOfRequiredType(DefaultListableBeanFactory.java:1520)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1498)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1099)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1060)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:578)
...
然后,我的测试通过了,类似地,我可以测试声明式缓存是否也可以运行
那么我的问题是:
这是解决这个问题的最好方法吗?让两个自动代理创建者应用于给定的bean是合法的还是好主意?如果没有,那么让Spring的隐式自动代理创建者很好地处理自定义建议的最佳方法是什么?我怀疑“嵌套”代理是个好主意,但无法解决如何覆盖
@Enable*
的隐式自动代理创建者。如果bean使用@Transactional
或@Cacheable
注释,Spring会默认生成JDK动态代理来支持AOP
动态代理类(com.sun.proxy.$Proxy61
)继承/实现目标bean实现的所有接口。如果目标bean缺少接口,则代理类不会实现接口
但是,Spring框架可以使用cglib
生成一个特殊的代理类(缺少接口),该类继承自原始类并在子方法中添加行为
由于您的服务
类没有实现任何接口,因此Spring框架生成一个没有任何接口的合成代理类(com.sun.proxy.$Proxy61)
在DefaultAdvisorAutoProxyCreator
中将setProxyTargetClass
设置为true
后,Spring框架在运行时使用cglib
动态生成唯一的代理类。此类继承自服务
类。代理命名模式通常类似于$$EnhancerBySpringCGLIB$$
。例如,Service$$EnhancerBySpringCGLIB$$f3c18efe
在您的测试中,当setProxyTargetClass
未设置为true
时,Spring会抛出一个BeanNotorRequiredTypeException
,因为Spring找不到任何与服务
类匹配的bean
当Spring发现一个bean与您的服务
类匹配时,一旦您使用cglib
生成代理,测试就会停止失败
如果为服务
类引入接口,则不必依赖cglib
。若允许依赖接口而不是实现,则可以进一步减少类之间的耦合。比如说,
服务变化
应用程序类
应用程序更改
我花了大量时间研究这个问题,并取得了一些进展 仔细考虑之后,我认为根本问题并不是Spring选择了错误的代理类型来包装已经代理的bean。首先,Spring正试图双重代理一个bean TL;DR-使用@AspectJ而不是DefaultAdvisorAutoProxyCreator 我遇到的问题似乎是SPR-13990的表现形式(-closed as not Fix)。后面讨论中的评论与我的用例特别相关。来自维护者: Spring的AutoProxyCreator实际上总是创建一个新的代理 SPR-6083(-也不会修复)也亮起;维修人员说: 我们确实明确地避免了双重代理,但只有在使用隐式代理时才这样做 代理,例如通过
或:
上一章描述了Spring对AOP的支持,使用
@AspectJ和基于模式的方面定义。在本章中,我们
讨论较低级别的Spring AOP API和AOP支持
用于Spring 1.2应用程序。对于新应用,我们建议
中描述的Spring2.0及更高版本AOP支持的使用
上一章
…但是DefaultAdvisorAutoProxyCreator
似乎无处不在,我认为它是“较低级别”而不是“Spring1.2”。谢谢你的回答。不幸的是,这对我帮助不大。正如我在问题中试图澄清的那样,我理解Spring在什么情况下为用户bean创建JDK和CGLIB代理。但我不明白为什么Spring会围绕CGLIB代理创建JDK代理,而这显然无法实现原始bean的API。您也没有解决通过多个自动代理创建者代理代理代理的安全性(或缺乏),缓存建议可能会应用两次。
org.springframework.beans.factory.UnsatisfiedDependencyException:
...<snip>...
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'service' is expected to be of type 'me.hdpe.spring.cacheandaop.Service' but was actually of type 'com.sun.proxy.$Proxy61'
at org.springframework.beans.factory.support.DefaultListableBeanFactory.checkBeanNotOfRequiredType(DefaultListableBeanFactory.java:1520)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1498)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1099)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1060)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:578)
...
public DefaultAdvisorAutoProxyCreator proxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
public interface ServiceInterface {
String getString(int i);
}
@Component
public class Service implements ServiceInterface {
@Cacheable(cacheNames = "int-strings")
public String getString(int i) {
return String.valueOf(i);
}
}
@Bean
public NameMatchMethodPointcutAdvisor pointcutAdvisor() {
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
//advisor.setClassFilter(new RootClassFilter(Service.class));
advisor.setClassFilter(new RootClassFilter(ServiceInterface.class));
advisor.addMethodName("*");
advisor.setAdvice(new EnsureNonNegativeAdvice());
return advisor;
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class)
public class ApplicationIT {
@Autowired
//private Service service;
private ServiceInterface service;
...
}
@SpringBootApplication
public class Application {
@Bean
public DefaultAdvisorAutoProxyCreator proxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
@Bean
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
@Bean
public CacheInterceptor cacheInterceptor() {
CacheInterceptor interceptor = new CacheInterceptor();
interceptor.setCacheOperationSources(cacheOperationSource());
return interceptor;
}
@Bean
public BeanFactoryCacheOperationSourceAdvisor cacheOperationSourceAdvisor() {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setAdvice(cacheInterceptor());
advisor.setCacheOperationSource(cacheOperationSource());
return advisor;
}
@Bean
public NameMatchMethodPointcutAdvisor pointcutAdvisor() {
...
@SpringBootApplication
@EnableCaching
@EnableAspectJAutoProxy
public class Application {
@Aspect
@Component
public static class EnsureNonNegativeAspect {
@Before("execution(* me.hdpe.spring.cacheandaop.Service.*(..)) && args(i)")
public void ensureNonNegative(int i) {
if (i < 0) {
throw new IllegalArgumentException();
}
}
}
}