一般情况下,一个SpringBoot应用 = 一个微服务 = 一个模块 = 一个有边界的上下文,如果有多个模块,我们就开发多个微服务,多个SpringBoot应用,然后使用Springcloud实现它们之间动态访问和监控。 但是有时我们也会希望将多个模块放入一个SpringBoot应用中,这样模块之间调用可以在一个JVM内进行,适合小型系统的部署,随着规模扩大,我们还可将这些模块变成一个个微服务,以SpringBoot应用分布式运行。
SpringBoot为模块化提供了非常直接简单的组合方式,可以说完全替代OSGI或其他模块插件技术。
什么是Spring Boot中的模块?
本文意义上的“模块”是一组加载到应用程序上下文中的Spring组件。
模块可以是业务模块,为应用程序提供一些业务服务,或者为几个其他模块或整个应用程序提供跨领域关注的技术模块。
创建模块的几种办法
Spring模块的基础是一个@Configuration注释,这是一种Spring的Java配置特性,可以用来标注在你的模块配置类中,配合@Configuration有几种更细粒度的方式:
(1)@ComponentScan
创建模块的最简单方法是使用@ComponentScan注释:
@Configuration
@ComponentScan(basePackages = "io.reflectoring.booking")
public class BookingModuleConfiguration {
}
如果这个配置类由importing 机制(稍后解释)导入的一个,它将查看包io.reflectoring.booking中的所有类,如果使用了 Spring的构造型注释 中任何一个注释,这些类的实例将被加载到Spring的应用上下文中。
只要你总是希望将包及其子包的所有类加载到Spring应用上下文中,那么使用这种方式就可以了。如果你需要更多控制加载内容,请继续。。
(2)@Bean 定义
Spring的Java配置功能还有一个@Bean注释,用于创建加载bean的实例到Spring应用上下文中:
@Configuration public class BookingModuleConfiguration { @Bean public BookingService bookingService(){ return new BookingService(); } // potentially more @Bean definitions ... }
导入此配置类时,BookingService实例将被创建并插入Spring的应用上下文中。
使用这种方式进行模块的创建就可以更清楚地了解实际加载的bean,因为你只需要查看一个地方(配置类),更方便 ,这种办法与使用@ComponentScan地方相比,后者需要你查看包中所有类的构造型注释,看看是什么构造型,符合条件才能被加载。
(3)@Conditional 注释
如果你需要对哪些组件应该加载到Spring应用上下文中要进行更细粒度的控制,则可以使用Spring Boot的@Conditional...注释:
@Configuration @ConditionalOnProperty(name = "io.reflectoring.security.enabled", havingValue = "true", matchIfMissing = true) public class SecurityModuleConfiguration { // @Bean definitions ... }
在使用这个模块时,必须在application配置文件中设置属性io.reflectoring.security.enabled为true才能使用这个模块。(见后面使用模块)
还可以使用其他@Conditional...注释来定义加载模块的条件。有一个依赖条件,具体取决于JVM的版本以及某个类是否存在于类路径中或某个bean是否存在于Spring应用上下文中。
如果你曾经问过自己Spring Boot如何神奇地将应用程序所需的bean加载到应用程序上下文中,原理就在于使用了这个注释@Conditional,Spring Boot本身大量使用@Conditional注释。
以上三种办法是创建一个模块的方式,那么如何使用这些模块呢?也有几种方式可选,注意,要分清模块创建和模块使用两个大的边界。
使用模块的几种办法
创建模块后,我们需要将其导入到SpringBoot应用程序中,有下面几种办法:
(1)@Import
最直接的方法是使用@Import注释:
@SpringBootApplication @Import(BookingModuleConfiguration.class) public class ModularApplication { // ... }
这将导入BookingModuleConfiguration类及其随附的所有bean - 无论它们是由声明@ComponentScan还是@Bean注释。
(2)@Enable... 注释
Spring Boot带有一组注释,每个注释都自己导入某个模块。一个例子是@EnableScheduling,它导入调度子系统所需的所有Beans及其@Scheduled注释,也就是说,如果你在你的应用类中使用了@Scheduled注释,如果想使得这种调度功能起效,还必须在入口处加入@EnableScheduling,否则就不起效,这也是SpringBoot使用中容易掉的坑,关键还是没有了解Spring的模块机制:
@SpringBootApplication @EnableScheduling public class SpringbatchApplication { public static void main(String[] args) { SpringApplication.run(SpringbatchApplication.class, args); } }
我们也可以导入自己的Enable配置:
@SpringBootApplication @EnableBookingModule public class ModularApplication { public static void main(String[] args) { SpringApplication.run(ModularApplication.class, args); } }
上面代码中EnableBookingModule不是Spring自己的注释,而是我们自己的定做的,代码如下:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import(BookingModuleConfiguration.class) @Configuration public @interface EnableBookingModule { }
该@EnableBookingModule注释实际上只是包装了@Import,首先导入我们的BookingModuleConfiguration,如果我们有一个模块是由多个配置类组成,这种办法是一种将这些配置类聚合到单个模块中的方便且富有表现力的方法。
(3)自动配置Auto-Configuration
如果我们想自动加载模块而不是将之前那样在源代码中导入指定的硬连接hard-wiring,我们可以使用Spring Boot的自动配置功能,也就是不再源代码中使用注释,而是使用配置文件。
请在模块所在项目下(注意,不是模块使用的项目)建立文件META-INF/spring.factories,运行时需要放入classpath类路径中 ,在该文件中写入:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=/ io.reflectoring.security.SecurityModuleConfiguration
多个配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=/ com.mycorp.libx.autoconfigure.LibXAutoConfiguration,/ com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
该模块的使用者所在Springboot项目启动时会将SecurityModuleConfiguration类的所有bean导入到Spring应用上下文中。
要在SpringBoot中使用这个SecurityModuleConfiguration,还需要在模块使用的项目中在application.yml中定义:
io: reflectoring: security: enabled: true
这里将io.reflectoring.security.enabled设置true,是对应前面该模块创建时使用@Conditional注释时有一个条件:
@ConditionalOnProperty(name = "io.reflectoring.security.enabled", havingValue = "true", matchIfMissing = true)
使用模块的策略
前面介绍了在Spring Boot应用程序中使用模块的几个办法,但是我们什么时候在什么情况下选择哪一个呢?
(1)业务模块使用@Import
对于包含业务逻辑的模块 - 比如上面的BookingModuleConfiguration - 在大多数情况下使用@Import,使用带注释的静态导入应该足够了。通常那些没有加载业务模块也是没有意义的,因此我们不需要对它们的加载条件进行任何控制。
(2)技术模块使用自动配置
另一方面,技术性的模块 - 如安全SecurityModuleConfiguration - 这些技术通常会提供一些跨域的切面关注(类似AOP),例如日志记录,异常处理,授权或监视功能,这些功能在开发和运行时需求不一样,在开发过程中,可能根本不需要这些功能,因此我们希望有一种方法来禁用它们。
我们不希望使用@Import静态地导入每个技术模块,因为它们不应该对我们的代码产生任何影响。
因此,使用技术模块的最佳选择是自动配置功能。模块在后台静默加载,我们可以使用在代码之外配置属性中影响它们。
本文案例: github