Spring 启动自定义启动器,在其中定义实体,而无需使用 @EntityScan.可能吗?



我正在使用春季启动的自定义启动器进行一些测试。我设法配置了除实体以外的所有内容。我尝试使用@Import加载@AutoConfiguration类中的实体,但这不起作用。相反,如果我们在启动器中使用@EntityScan,则会加载实体,但是如果您导入此启动器并在项目中具有依赖于启动器的实体,则您被迫在其中使用@EntityScan,在我看来,这破坏了启动器的自动配置含义,因为当您导入启动器时,您不应该为了使用它而执行任何操作, 是的,您可以覆盖默认配置,但不会强制执行任何操作来声明某些属性。

初学者中的自动配置类示例:

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
@EnableJpaRepositories(basePackages = "com.example.springbootstarterexample.repository")
@Import({SomeServiceImpl.class, SomeEntityController.class /*, SomeEntity.class NOT WORKING*/})
@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {
}

然后,如果您在启动器的消费者中有实体,则必须这样做,并且其中有实体:

@SpringBootApplication
@EntityScan(basePackages = "com.example.springbootconsumer.model")
public class SpringBootConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootConsumerApplication.class, args);
}
}

否则,我们可以从启动器中删除@EntityScan并在消费者中执行此操作:

@SpringBootApplication
@EntityScan(basePackages = {"com.example.springbootconsumer.model", "com.example.springbootstarterexample.domain"})
public class SpringBootConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootConsumerApplication.class, args);
}
}

但这完全会阻止自动配置,因为您必须知道实体在启动器中的位置才能启动应用程序。 如果有兴趣,我已经写了一个例子。

编辑尝试使用@AutoConfigurationPackage

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
//@EnableJpaRepositories(basePackages = {"com.example.springbootstarterexample.repository"})
@AutoConfigurationPackage(basePackageClasses = {SomeEntity.class, SomeEntityRepository.class})
@Import({SomeServiceImpl.class, SomeEntityController.class /*, SomeEntity.class NOT WORKING*/})
//@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {
}

这样,存储库就不会被激活

Description:
Parameter 0 of constructor in com.example.springbootstarterexample.service.SomeServiceImpl required a bean of type 'com.example.springbootstarterexample.repository.SomeEntityRepository' that could not be found.

Action:
Consider defining a bean of type 'com.example.springbootstarterexample.repository.SomeEntityRepository' in your configuration.

如果我使用@EnableJpaRepositories则找到存储库进行注入,而不是实体

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
@EnableJpaRepositories(basePackages = {"com.example.springbootstarterexample.repository"})
@AutoConfigurationPackage(basePackageClasses = {SomeEntity.class})
@Import({SomeServiceImpl.class, SomeEntityController.class /*, SomeEntity.class NOT WORKING*/})
//@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {
}

错误:

Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.example.springbootstarterexample.domain.SomeEntity

使用包的名称我有相同的结果

编辑 2@AutoConfiguration类由META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports删除@Import加载:

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
//@EnableJpaRepositories(basePackages = {"com.example.springbootstarterexample.repository"})
@AutoConfigurationPackage(basePackageClasses = {SomeEntity.class, SomeServiceImpl.class, SomeEntityController.class, SomeEntityRepository.class})
//@Import({SomeServiceImpl.class, SomeEntityController.class /*, SomeEntity.class NOT WORKING*/})
//@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {
}

试图在消费者中注入一些东西:

Description:
Parameter 0 of constructor in com.example.springbootconsumer.SpringBootConsumerApplication required a bean of type 'com.example.springbootstarterexample.service.SomeService' that could not be found.

Action:
Consider defining a bean of type 'com.example.springbootstarterexample.service.SomeService' in your configuration.

这似乎根本没有加载任何配置。

EDIT 3将日志级别放到 TRACE 并将所有类放在同一个包下,ExampleAutoConfiguration类的包现在看起来像这样:

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
//@EnableJpaRepositories(basePackages = {"com.example.springbootstarterexample.repository"})
@AutoConfigurationPackage
//@Import({SomeServiceImpl.class, SomeEntityController.class /*, SomeEntity.class NOT WORKING*/})
//@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {
}

我找到了正在扫描的@AutoConfiguration类的日志,但我在日志中找不到包中定义的任何 bean:

2022-09-08 20:03:24.495 TRACE 17132 --- [           main] a.ConfigurationClassBeanDefinitionReader : Registered bean definition for imported class 'com.example.springbootstarterexample.autoconfigure.ExampleAutoConfiguration'

如果我使用正常配置,我看到所有 bean 都已创建

2022-09-08 22:31:34.580 TRACE 2308 --- [           main] a.ConfigurationClassBeanDefinitionReader : Registered bean definition for imported class 'com.example.springbootstarterexample.service.SomeServiceImpl'
2022-09-08 22:31:34.581 TRACE 2308 --- [           main] a.ConfigurationClassBeanDefinitionReader : Registered bean definition for imported class 'com.example.springbootstarterexample.controller.SomeEntityController'
2022-09-08 22:31:34.585 TRACE 2308 --- [           main] a.ConfigurationClassBeanDefinitionReader : Registered bean definition for imported class 'com.example.springbootstarterexample.autoconfigure.ExampleAutoConfiguration'
2022-09-08 22:31:34.685 TRACE 2308 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Spring Data JPA - Registering repository: someEntityRepository - Interface: com.example.springbootstarterexample.repository.SomeEntityRepository - Factory: org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean
2022-09-08 22:31:39.094 DEBUG 2308 --- [           main] org.hibernate.cfg.Ejb3Column             : Binding column: Ejb3DiscriminatorColumn{logicalColumnName'DTYPE', discriminatorTypeName='string'}
2022-09-08 22:31:39.112 DEBUG 2308 --- [           main] o.h.cfg.annotations.EntityBinder         : Import with entity name SomeEntity
2022-09-08 22:31:39.113 TRACE 2308 --- [           main] o.h.b.i.InFlightMetadataCollectorImpl    : Import: SomeEntity -> com.example.springbootstarterexample.domain.SomeEntity
2022-09-08 22:31:39.114 TRACE 2308 --- [           main] o.h.b.i.InFlightMetadataCollectorImpl    : Import: com.example.springbootstarterexample.domain.SomeEntity -> com.example.springbootstarterexample.domain.SomeEntity

终于找到了解决方案,多亏了这篇文章。 配置类变为:

@AutoConfigureBefore(JpaRepositoriesAutoConfiguration.class)
@EnableJpaRepositories(basePackages = {"com.example.springbootstarterexample.repository"})
@Import({SomeServiceImpl.class, SomeEntityController.class, StarterEntityRegistrar.class /*, SomeEntity.class NOT WORKING*/})
public class ExampleAutoConfiguration {
}

和实体的注册之星:

public class StarterEntityRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, SomeEntity.class.getPackageName());
}
}

一个工作示例

可以使用 Register 方法添加所需的所有包:

以编程方式注册自动配置包名称。后续调用会将给定的包名称添加到已注册的包名称中。可以使用此方法手动定义将用于给定 BeanDefinitionRegistry 的基本包。通常,建议不要直接调用此方法,而是依赖于默认约定,其中包名称是从一个或多个@EnableAutoConfigurationconfiguration类设置的。

您需要将@ComponentScan添加到配置 Bean。

@AutoConfiguration(after = JpaRepositoriesAutoConfiguration.class)
//@AutoConfigureAfter(JpaRepositoriesAutoConfiguration.class)
@EnableJpaRepositories(basePackages = "com.example.springbootstarterexample.repository")
@ComponentScan("com.example.springbootstarterexample")
@EntityScan(basePackages = "com.example.springbootstarterexample.domain")
public class ExampleAutoConfiguration {
}

一个有趣的问题,但是如果您与库的使用者就如何在其实体所在的位置共享信息达成一致,例如,他们会提供一个META-INF/entityscanforyouapp文件,其中包含他们拥有实体的包列表

使用者 1 将创建META-INF/entityscanforyouapp

com.consumerapp1.entities

使用者 1 将创建META-INF/entityscanforyouapp

com.consumerapp2.entities

您可以发现所有这些文件,然后创建一个包含所有发现的包的 EntityManagerFactory Bean

@Bean
@Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder, DataSource dataSource) {
Map<String, Object> vendorProperties = <... read the additional hibernate properties if you need...>;
String[] packagesWithEntities = discoverAllThePackages();
return factoryBuilder.dataSource(datasource).packages(packagesWithEntities).properties(vendorProperties).build();
}

这有点"粗糙而准备好",但如果你想走这条路,应该提供一个很好的起点。

最新更新