Skip to content

第三方 Bean 注入 IoC 容器

发表于: 时间 00:00
加载中...

在实际开发过程中,我们往往需要集成各类第三方库来提高开发效率或扩展系统功能。然而,在使用 Spring 或 Spring Boot 框架时,如何将这些第三方库中的组件正确地注册到 IoC 容器中,常常成为需要解决的问题。

手动注册 @Bean

以第三方工具包 HuTool 举例,首先我们先引入 HuToolMaven 依赖,如下:

<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"));
}

image.png

如果在配置类中创建 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);
    }
}

@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 可以放在启动类上,也可以放在配置类上。


下一篇文章
Spring AOP