Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/spring/11.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Spring 将@EnableCaching与自定义AOP建议一起使用时,代理类型不匹配(JDK与CGLIB)_Spring_Spring Aop - Fatal编程技术网

Spring 将@EnableCaching与自定义AOP建议一起使用时,代理类型不匹配(JDK与CGLIB)

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

我一直试图让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();
            }
        }
    }
}
我希望通过以下测试:

@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();
            }
        }
    }
}