我一直试图让Spring的声明式缓存在一个应用程序中与一些自定义AOP建议一起工作,并且遇到了代理类型不匹配的问题。
给出以下Spring Boot应用程序主类:
@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();
}
}
}
}
和服务:
@Component
public class Service {
@Cacheable(cacheNames = "int-strings")
public String getString(int i) {
return String.valueOf(i);
}
}
我希望以下测试通过:
@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);
}
}
(此代码在https://github.com/hdpe/spring-cache-and-aop-issue上的可运行项目中都可用。)
但是,运行此测试会给出:
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)
...
那为什么呢?好吧,我想 ......
@EnableCaching
会触发创建InfrastructureAdvisorAutoProxyCreator
,通过代理Service
Service
不实现任何接口,因此CGLIB用于创建其代理DefaultAdvisorAutoProxyCreator
然后运行以应用我的自定义建议(似乎,缓存建议再次)围绕服务方法SpringProxy
和Advised
接口,这次Spring创建了一个JDK动态代理Service
,因此自动装入测试类失败。因此,为了解决问题(或者至少隐藏此问题),我可以强制我的代理创建者生成CGLIB代理:
public DefaultAdvisorAutoProxyCreator proxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
我的测试然后通过,同样我可以测试声明性缓存也是可操作的。
所以我的问题:
这是解决此问题的最佳方法吗?让两个自动代理创建者适用于给定的bean是合法的还是个好主意?如果没有,那么使Spring的隐式自动代理创建者能够很好地使用自定义建议的最佳方法是什么?我怀疑“嵌套”代理是一个好主意,但无法理解如何覆盖@Enable*
的隐式自动代理创建者。
答案 0 :(得分:3)
如果bean使用@Transactional
或@Cacheable
注释,则默认情况下Spring会生成 JDK动态代理以支持AOP。
动态代理类(com.sun.proxy.$Proxy61
)继承/实现目标bean实现的所有接口。如果目标bean缺少接口,则代理类不实现接口。
但是,Spring框架可以使用cglib
生成一个特殊的代理类(缺少一个接口),它继承自原始类并在子方法中添加行为。
由于您的Service
类没有实现任何接口,因此Spring框架生成一个没有任何接口的合成代理类(com.sun.proxy。$ Proxy61)。
在setProxyTargetClass
中将true
设置为DefaultAdvisorAutoProxyCreator
后,Spring框架会使用cglib
在运行时动态生成唯一的代理类。该类继承自Service
类。代理命名模式通常类似于<bean class>$$EnhancerBySpringCGLIB$$<hex string>
。例如,Service$$EnhancerBySpringCGLIB$$f3c18efe
。
在测试中,当BeanNotOfRequiredTypeException
未设置为setProxyTargetClass
时,Spring会抛出true
,因为Spring找不到任何与您的Service
类匹配的bean。
一旦使用cglib
生成代理,您的测试就会停止失败,因为Spring会找到与您的Service
类匹配的bean。
如果您为cglib
课程引入了界面,则不必依赖Service
。如果允许依赖于接口而不是实现,则可以进一步减少类之间的耦合。例如,
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;
...
}
答案 1 :(得分:2)
我花了很多时间来研究这个问题并取得了一些进展。
在考虑了它之后,我已经确定根本问题并不是Spring真的选择了错误的代理类型来包装已经代理的bean。 Spring正试图首先对bean进行双重代理!
TL; DR - 使用@AspectJ而不是DefaultAdvisorAutoProxyCreator
我遇到过的问题似乎是SPR-13990(DefaultAdvisorAutoProxyCreator doesn't get the target class of existing proxy - 已关闭,因为赢得修复)的表现形式。讨论后面的评论与我的用例特别相关。来自维护者:
Spring的AutoProxyCreators实际上总是创建一个新的代理......
SPR-6083(DefaultAdvisorAutoProxyCreator doesn't work with tx:annotation-driven on Cglib classes - 也赢得)也很有启发性;维护者说:
我们明确避免双重代理,但仅在使用隐式时 代理,例如通过
<tx:annotation-driven>
或<aop:config>
。我&#39;米 害怕显式的DefaultAdvisorAutoProxyCreator定义 不会参与这个过程...
建议似乎是:
Advisor
你自己DefaultAdvisorAutoProxyCreator
;或<aop:config>
style&#34;配置。 自己注册Advisor
删除@EnableCaching
并定义自己包含的bean,InfrastructureAdvisorAutoProxyCreator
无法注册,我可以继续使用我自己的自动代理创建器,{{1}方法也将利用。我的配置如下所示:
@Cacheable
现在这非常可怕 - 我不得不毫无意义地重新定义了许多核心Spring配置,因此我可以关闭@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() {
...
并且我已经失去了使用{{1}的能力可能还有其他各种各样的东西。
<强> @AspectJ 强>
所以第二种方法 - 一旦我发现&#34; AutoProxyRegistrar
样式&#34;在JavaConfig中意味着@AspectJ注释 - 是要走的路。请注意,@ AspectJ在这里并不意味着您正在使用AspectJ编译器/编织器!它仍然只是Spring AOP从@AspectJ注释创建JDK或CGLIB代理。
使用@ AspectJ-style配置,整个配置浓缩为:
CachingConfigurer
令人难以置信的是,这可行,因为<aop:config>
将任何现有的@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();
}
}
}
}
提升为@AspectJ自动代理创建者,导致只有一个代理同时应用我的自定义和缓存建议
总结
我认为如果你想使用@EnableAspectJAutoProxy
,InfrastructureAdvisorAutoProxyCreator
等创建的隐式建议来使用自定义Spring AOP建议,你可能会更好地使用@AspectJ而不是{ {1}}。这似乎是春天希望你走下去的方向,他们暗示in the docs:
前一章描述了Spring对AOP的支持 @AspectJ和基于模式的方面定义。在本章中我们 通常讨论较低级别的Spring AOP API和AOP支持 用于Spring 1.2应用程序。对于新的应用程序,我们建议 使用Spring 2.0及以后的AOP支持描述 前一章......
......但是@EnableCaching
似乎无处不在,我以为它是低级别的&#39;而不是春天1.2&#39;。