Skip to content

Injecting Third-Party Beans into IoC Container

Published: at 04:00 PM
Loading...

During actual development, we often need to integrate various third-party libraries to improve development efficiency or extend system functionality. However, when using the Spring or Spring Boot framework, how to properly register components from these third-party libraries into the IoC container often becomes a problem that needs to be solved.

Manual Registration with @Bean

Taking the third-party utility package HuTool as an example, first we introduce the Maven dependency for HuTool:

<dependency>    
    <groupId>cn.hutool</groupId>    
    <artifactId>hutool-all</artifactId>    
    <version>5.8.41</version>
</dependency>

In HuTool, if we want to use the cn.hutool.http.HttpUtil utility class, we hope to use it like this:

@Autowired
private HttpUtil httpUtil;

But obviously, writing it directly like this will result in an error:

No qualifying bean of type 'cn.hutool.http.HttpUtil' available

This is because all classes in Hutool use static methods and are not registered in the Spring container.

We can manually create a Bean in a configuration class using the @Bean annotation:

@Configuration
public class HutoolConfig {
    @Bean
    public HttpUtil getHttpUtil() {
        return new HttpUtil();
    }
}

Then we perform a unit test:

@Autowired
private HttpUtil httpUtil;
@Test
void test1(){
    System.out.println(httpUtil.get("https://www.baidu.com"));
}

image.png

When creating a Bean in a configuration class, if you need to pass parameters, and if the IoC container has this parameter type, it can be auto-injected. For example:

@Configuration
public class SecurityConfig {
    @Bean
    public AuthenticationManager getAuthenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                   .build();
    }
}

This is equivalent to omitting:

@Autowired
HttpSecurity http;

@ComponentScan — Extending Scan Paths

If certain classes in a third-party library already use annotations like @Component, @Service, @Repository, but these classes are not under our default scan path, we can extend the scan range through @ComponentScan.

@SpringBootApplication
@ComponentScan(basePackages = {
    "com.example.myapp",           // Your project package path
    "com.baomidou.mybatisplus.extension.plugins"  // Third-party library package path
})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@Import to Directly Import Configuration Classes

Using @Import can directly import configuration classes or regular classes into the Spring container:

// Third-party configuration class
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);
    }
}

// Import in main configuration class
@Configuration
@Import(RedissonConfig.class)
public class ApplicationConfig {
}

Or use it directly on the startup class:

@SpringBootApplication
@Import({RedissonConfig.class, OtherConfig.class})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Importing Regular Classes

@Import can also directly import regular classes without any annotations:

// Third-party regular class
public class DataConverter {
    public String convert(Object data) {
        return JSON.toJSONString(data);
    }
}

// Import configuration
@Configuration
@Import(DataConverter.class)
public class ConverterConfig {
}

After importing, it can be directly injected and used:

@Service
public class DataService {
    @Autowired
    private DataConverter dataConverter;
    
    public String processData(Object data) {
        return dataConverter.convert(data);
    }
}

ImportSelector for Dynamic Bean Registration

ImportSelector allows us to dynamically select which configuration classes to import based on conditions, which is very useful when we need to register Beans dynamically based on environment or configuration.

// Custom ImportSelector
public class ElasticsearchImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // Can dynamically decide which configuration classes to import based on annotation attributes, environment variables, etc.
        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"
            };
        }
        
        // Default configuration
        return new String[]{
            "com.example.config.ElasticsearchDefaultConfig"
        };
    }
}

// Configuration class
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);
    }
}

// Use in configuration class
@Configuration
@Import(ElasticsearchImportSelector.class)
public class SearchConfig {
}

More Complex Example: Conditional Import

public class ConditionalImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        List<String> imports = new ArrayList<>();
        
        // Check if a certain class exists in the classpath
        if (ClassUtils.isPresent("com.example.ThirdPartyClass", null)) {
            imports.add("com.example.config.ThirdPartyConfig");
        }
        
        // Check system properties
        if ("true".equals(System.getProperty("enable.cache"))) {
            imports.add("com.example.config.CacheConfig");
        }
        
        // Check environment variables
        String profile = System.getenv("SPRING_PROFILES_ACTIVE");
        if ("prod".equals(profile)) {
            imports.add("com.example.config.ProdConfig");
        }
        
        return imports.toArray(new String[0]);
    }
}

Using DeferredImportSelector

If you need to perform imports after all @Configuration classes have been processed, you can implement the DeferredImportSelector interface:

public class DeferredElasticsearchImportSelector implements DeferredImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        // Deferred import logic
        return new String[]{"com.example.config.ElasticsearchConfig"};
    }
}

@EnableXxx Composite Annotations

@EnableXxx is a high-level encapsulated configuration approach that typically combines @Import with other annotations, providing users with a simple switch to enable a certain functional module.

@EnableXxx can be placed on the startup class or on a configuration class.


Previous Post
Spring Bean Scope and Lifecycle
Next Post
Spring AOP