在实际开发过程中,我们往往需要集成各类第三方库来提高开发效率或扩展系统功能。然而,在使用 Spring 或 Spring Boot 框架时,如何将这些第三方库中的组件正确地注册到 IoC 容器中,常常成为需要解决的问题。
手动注册 @Bean
以第三方工具包 HuTool
举例,首先我们先引入 HuTool
的 Maven
依赖,如下:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.41</version>
</dependency>
在 HuTool
中,如果我们要使用 cn.hutool.http.HttpUtil
工具类,我们希望能够像这样使用:
@Autowired
private HttpUtil httpUtil;
但很明显,直接这样写会报错:
No qualifying bean of type 'cn.hutool.http.HttpUtil' available
因为 Hutool
的类都是静态方法,没有注册到 Spring
容器中。
我们可以手动在配置类中创建 Bean,使用 @Bean
注解,如下:
@Configuration
public class HutoolConfig {
@Bean
public HttpUtil getHttpUtil() {
return new HttpUtil();
}
}
然后我们进行单元测试:
@Autowired
private HttpUtil httpUtil;
@Test
void test1(){
System.out.println(httpUtil.get("https://www.baidu.com"));
}
如果在配置类中创建 Bean 时,你有需要传入形参,如果 IOC
容器中有这个形参的类型,则可以自动注入。例如:
@Configuration
public class SecurityConfig {
@Bean
public AuthenticationManager getAuthenticationManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.build();
}
}
他相当于省略了
@Autowired
HttpSecurity http;
@ComponentScan —— 扩展扫描路径
如果第三方库中的某些类已经使用了 @Component
、@Service
、@Repository
等注解,但这些类不在我们默认的扫描路径下,可以通过 @ComponentScan
来扩展扫描范围。
@SpringBootApplication
@ComponentScan(basePackages = {
"com.example.myapp", // 你的项目包路径
"com.baomidou.mybatisplus.extension.plugins" // 第三方库包路径
})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- basePackages 是一个数组,一旦使用
@ComponentScan
,@SpringBootApplication
中的默认扫描路径便失效,我们必须在 basePackages 配置上我们项目的包路径,然后再去添加第三方库的包路径。
@Import 直接导入配置类
使用 @Import
可以直接导入配置类或普通类到 Spring 容器中:
// 第三方配置类
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("your-password");
return Redisson.create(config);
}
}
// 在主配置类中导入
@Configuration
@Import(RedissonConfig.class)
public class ApplicationConfig {
}
或者直接在启动类上使用:
@SpringBootApplication
@Import({RedissonConfig.class, OtherConfig.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
导入普通类
@Import
还可以直接导入没有任何注解的普通类:
// 第三方普通类
public class DataConverter {
public String convert(Object data) {
return JSON.toJSONString(data);
}
}
// 导入配置
@Configuration
@Import(DataConverter.class)
public class ConverterConfig {
}
导入后可以直接注入使用:
@Service
public class DataService {
@Autowired
private DataConverter dataConverter;
public String processData(Object data) {
return dataConverter.convert(data);
}
}
ImportSelector 动态注册 Bean
ImportSelector
允许我们根据条件动态选择要导入的配置类,这在需要根据环境或配置动态注册 Bean 时非常有用。
// 自定义 ImportSelector
public class ElasticsearchImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 可以根据注解属性、环境变量等动态决定导入哪些配置类
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableElasticsearch.class.getName());
String mode = (String) attributes.get("mode");
if ("rest".equals(mode)) {
return new String[]{
"com.example.config.ElasticsearchRestClientConfig"
};
} else if ("transport".equals(mode)) {
return new String[]{
"com.example.config.ElasticsearchTransportConfig"
};
}
// 默认配置
return new String[]{
"com.example.config.ElasticsearchDefaultConfig"
};
}
}
// 配置类
public class ElasticsearchRestClientConfig {
@Bean
public ElasticsearchClient elasticsearchClient() {
RestClient restClient = RestClient.builder(
new HttpHost("localhost", 9200)
).build();
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper()
);
return new ElasticsearchClient(transport);
}
}
// 在配置类中使用
@Configuration
@Import(ElasticsearchImportSelector.class)
public class SearchConfig {
}
更复杂的示例:条件导入
public class ConditionalImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
List<String> imports = new ArrayList<>();
// 检查类路径中是否存在某个类
if (ClassUtils.isPresent("com.example.ThirdPartyClass", null)) {
imports.add("com.example.config.ThirdPartyConfig");
}
// 检查系统属性
if ("true".equals(System.getProperty("enable.cache"))) {
imports.add("com.example.config.CacheConfig");
}
// 检查环境变量
String profile = System.getenv("SPRING_PROFILES_ACTIVE");
if ("prod".equals(profile)) {
imports.add("com.example.config.ProdConfig");
}
return imports.toArray(new String[0]);
}
}
使用 DeferredImportSelector
如果需要在所有 @Configuration
类处理完之后再进行导入,可以实现 DeferredImportSelector
接口:
public class DeferredElasticsearchImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
// 延迟导入逻辑
return new String[]{"com.example.config.ElasticsearchConfig"};
}
}
@EnableXxx 组合注解
@EnableXxx
是一种高层封装的配置方式,它通常组合了 @Import
和其他注解,为用户提供一个简单的开关来启用某个功能模块。
@EnableXxx
可以放在启动类上,也可以放在配置类上。