【问题标题】:Spring Boot @Converter always accessible with @SpringBootTestSpring Boot @Converter 始终可以通过 @SpringBootTest 访问
【发布时间】:2019-05-16 19:16:33
【问题描述】:

我有一个使用以下转换器的Spring Boot 应用程序。

@Converter(autoApply = true)
class LocalDatePersistenceConverter implements AttributeConverter<LocalDate, java.sql.Date> {

    @Override
    public java.sql.Date convertToDatabaseColumn(LocalDate entityValue) {
        if (entityValue != null) {
            return java.sql.Date.valueOf(entityValue);
        }
        return null;
    }

    @Override
    public LocalDate convertToEntityAttribute(java.sql.Date databaseValue) {
        if (databaseValue != null) {
            return databaseValue.toLocalDate();
        }
        return null;
    }
}

此转换器是包私有的,可与带有 @SpringBootTest 注释的集成测试一起使用。但是,当尝试使用 jar 或 Maven spring-boot:run 目标启动应用程序时,出现以下错误:

2018-12-15 23:36:18.349 ERROR 1772 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Unable to instantiate AttributeConverter [org.donorcalendar.persistence.LocalDatePersistenceConverter]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1745) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:576) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1083) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:853) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.donorcalendar.Application.main(Application.java:17) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_192]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_192]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_192]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_192]
    at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:558) [spring-boot-maven-plugin-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_192]
Caused by: org.hibernate.AnnotationException: Unable to instantiate AttributeConverter [org.donorcalendar.persistence.LocalDatePersistenceConverter]
    at org.hibernate.cfg.AttributeConverterDefinition.instantiateAttributeConverter(AttributeConverterDefinition.java:63) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.cfg.AttributeConverterDefinition.from(AttributeConverterDefinition.java:80) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.boot.model.process.internal.ScanningCoordinator.applyScanResultsToManagedResources(ScanningCoordinator.java:235) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.boot.model.process.internal.ScanningCoordinator.coordinateScan(ScanningCoordinator.java:82) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.prepare(MetadataBuildingProcess.java:99) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:232) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:167) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:51) ~[spring-orm-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:390) ~[spring-orm-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:377) ~[spring-orm-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1804) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1741) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    ... 22 common frames omitted
Caused by: java.lang.IllegalAccessException: Class org.hibernate.cfg.AttributeConverterDefinition can not access a member of class org.donorcalendar.persistence.LocalDatePersistenceConverter with modifiers ""
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102) ~[na:1.8.0_192]
    at java.lang.Class.newInstance(Class.java:436) ~[na:1.8.0_192]
    at org.hibernate.cfg.AttributeConverterDefinition.instantiateAttributeConverter(AttributeConverterDefinition.java:59) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
    ... 35 common frames omitted

事实上,我认为这个错误是正常行为,我想了解的是为什么在我的集成测试中正确创建和使用了这个转换器?

@SpringBootTest 注释是否使转换器以不要求它为public 的不同方式启动?我已经知道如何解决这个问题(只需制作转换器public),但我想了解为什么代码在测试期间表现不同。

我希望进行与实际应用程序尽可能相似的端到端测试。不幸的是,对于这种情况,我在应用程序启动期间遇到问题,但在测试执行期间正常初始化和执行。

有人能解释一下为什么会有这种差异吗?如有必要,我可以提供有关应用程序设置的更多详细信息。

我的问题是关于运行应用程序和集成测试时的不同行为,我已经知道如何使用转换器修复错误,所以请不要回答我只需要添加public修饰符。我想以一种与正常启动应用程序时具有相同行为(和错误)的方式配置我的测试,以便我对端到端测试更有信心。

【问题讨论】:

    标签: java spring hibernate spring-boot spring-boot-test


    【解决方案1】:

    这个错误是由于你的 LocalDatePersistenceConverter 类默认访问修饰符是空的,所以 Hibernate 找不到它,所以你需要通过以下方式公开:

     public class LocalDatePersistenceConverter.... 
    

    在您的测试中,在启动应用程序时访问修饰符没有问题的包中。

    参考文献

    https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html

    【讨论】:

    • 为了清楚起见,我已经知道 public 会解决启动问题。我想要了解它为什么在我的集成测试中起作用,您能否详细说明为什么它在测试期间起作用?我的测试和转换器位于不同的包中,这似乎不是问题,这些测试是端到端的,我认为这个想法是让 spring 完全启动应用程序并加载所有内容,尽可能靠近真正的执行,我不明白集成测试初始化​​如何以不同的方式工作以避免这个问题。
    • 没关系,所以你需要提供测试代码才能知道你正在使用的测试策略。也许您正在使用一些不使用 Hibernate 的数据库。
    • 你可以在这里看到整个项目:link。 UserControllerIT 类就是一个例子,但总的来说只是一个用@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 注释的测试。
    猜你喜欢
    • 2018-05-23
    • 1970-01-01
    • 2017-02-08
    • 1970-01-01
    • 2017-06-21
    • 1970-01-01
    • 2020-03-23
    • 2020-06-17
    • 2018-09-13
    相关资源
    最近更新 更多