Unlocking Spring’s Hidden Extension Points: From FactoryBean to @Import and Beyond
This article provides a comprehensive guide to Spring’s extension mechanisms, covering FactoryBean, @Import, bean lifecycle callbacks, BeanPostProcessor, BeanFactoryPostProcessor, the SPI system, Spring Boot startup hooks, event publishing, and custom XML namespaces, complete with runnable code examples and diagrams.
FactoryBean
FactoryBean is a special bean type that differs from BeanFactory; when a class implements FactoryBean, the object returned by its getObject() method is registered in the container.
Demo code shows a UserFactoryBean that creates a User instance.
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
User user = new User();
System.out.println("Calling UserFactoryBean.getObject to create Bean:" + user);
return user;
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}Test class registers the factory bean and retrieves the User bean, demonstrating the behavior.
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(UserFactoryBean.class);
ctx.refresh();
System.out.println("Bean obtained: " + ctx.getBean(User.class));
}
}FactoryBean in open source frameworks
MyBatis integration
MyBatis uses MapperFactoryBean to create mapper proxies via FactoryBean.
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
}OpenFeign integration
Feign client proxies are created by FeignClientFactoryBean, which also implements FactoryBean.
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
private Class<?> type;
@Override
public Object getObject() throws Exception {
return getTarget();
}
@Override
public Class<?> getObjectType() {
return type;
}
}FactoryBean is suitable for complex bean creation and is widely used in framework integrations.
@Import annotation
@Import is used by annotations such as @EnableScheduling and @EnableAsync to import configuration classes.
@Import({SchedulingConfiguration.class})
public interface EnableScheduling {}
@Import({AsyncConfigurationSelector.class})
public interface EnableAsync {}Classification of imported configuration classes
Three cases are supported.
1. Implements ImportSelector
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
default Predicate<String> getExclusionFilter() { return null; }
}Example UserImportSelector returns the fully qualified name of User.
public class UserImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
System.out.println("Calling UserImportSelector.selectImports");
return new String[]{"com.sanyou.spring.extension.User"};
}
}2. Implements ImportBeanDefinitionRegistrar
Allows custom BeanDefinition registration.
public class UserImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry, BeanNameGenerator generator) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class)
.addPropertyValue("username", "三友的java日记")
.getBeanDefinition();
System.out.println("Injecting User into Spring container");
registry.registerBeanDefinition("user", beanDefinition);
}
}3. Plain configuration class
A regular class without special interfaces can also be imported.
Bean lifecycle
Bean creation and destruction callbacks include @PostConstruct, @PreDestroy, InitializingBean, DisposableBean, and custom initMethod / destroyMethod.
public class LifeCycle implements InitializingBean, ApplicationContextAware, DisposableBean {
@Autowired
private User user;
public LifeCycle() { System.out.println("LifeCycle object created"); }
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
System.out.println("Aware callback, setApplicationContext called, user=" + user);
}
@PostConstruct
public void postConstruct() { System.out.println("@PostConstruct called"); }
@Override
public void afterPropertiesSet() throws Exception { System.out.println("InitializingBean afterPropertiesSet called"); }
public void initMethod() throws Exception { System.out.println("@Bean initMethod called"); }
@PreDestroy
public void preDestroy() throws Exception { System.out.println("@PreDestroy called"); }
public void destroyMethod() throws Exception { System.out.println("@Bean destroyMethod called"); }
@Override
public void destroy() throws Exception { System.out.println("DisposableBean destroy called"); }
}Running the test prints the order of callbacks:
LifeCycle object created
Aware callback, setApplicationContext called, user=com.sanyou.spring.extension.User@57d5872c
@PostConstruct called
InitializingBean afterPropertiesSet called
@Bean initMethod called
@PreDestroy called
DisposableBean destroy called
@Bean destroyMethod calledBeanPostProcessor
Allows custom logic after bean initialization. Example sets username for User beans.
public class UserBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof User) {
((User) bean).setUsername("三友的java日记");
}
return bean;
}
}Spring’s built‑in processors include AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, AsyncAnnotationBeanPostProcessor, ScheduledAnnotationBeanPostProcessor, etc., which handle various annotations.
BeanFactoryPostProcessor
Can modify the BeanFactory before any beans are instantiated, e.g., disabling circular references.
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
((DefaultListableBeanFactory) beanFactory).setAllowCircularReferences(false);
}
}Spring SPI mechanism
Spring loads implementations from META-INF/spring.factories. Example defines a custom key MyEnableAutoConfiguration mapping to User and retrieves it via SpringFactoriesLoader.
public class MyEnableAutoConfiguration {} // spring.factories entry
com.sanyou.spring.extension.spi.MyEnableAutoConfiguration=com.sanyou.spring.extension.User List<String> classNames = SpringFactoriesLoader.loadFactoryNames(MyEnableAutoConfiguration.class, MyEnableAutoConfiguration.class.getClassLoader());
classNames.forEach(System.out::println);Spring Boot startup extension points
Auto‑configuration
Spring Boot reads EnableAutoConfiguration entries from spring.factories and imports the corresponding configuration classes.
PropertySourceLoader
Enables custom configuration file formats. Example JsonPropertySourceLoader parses JSON into a PropertySource.
public class JsonPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() { return new String[]{"json"}; }
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
ReadableByteChannel channel = resource.readableChannel();
ByteBuffer buffer = ByteBuffer.allocate((int) resource.contentLength());
channel.read(buffer);
String content = new String(buffer.array());
JSONObject json = JSON.parseObject(content);
Map<String, Object> map = new HashMap<>();
for (String key : json.keySet()) {
map.put(key, json.getString(key));
}
return Collections.singletonList(new MapPropertySource("jsonPropertySource", map));
}
}Register it in spring.factories under the key org.springframework.boot.env.PropertySourceLoader to make Spring Boot recognize .json files.
ApplicationContextInitializer
Runs before the context is refreshed, receiving a ConfigurableApplicationContext for custom configuration.
EnvironmentPostProcessor
Modifies the ConfigurableEnvironment early in the startup process; ConfigFileApplicationListener uses it to load external configuration files.
ApplicationRunner and CommandLineRunner
Executed after the application has started; beans are obtained from the fully initialized context.
Spring Event system
Provides decoupled communication via ApplicationEvent, ApplicationListener, and ApplicationEventPublisher. Example defines a FireEvent and two listeners.
public class FireEvent extends ApplicationEvent {
public FireEvent(String source) { super(source); }
} public class Call119FireEventListener implements ApplicationListener<FireEvent> {
@Override
public void onApplicationEvent(FireEvent event) { System.out.println("Call 119"); }
} public class SavePersonFireEventListener implements ApplicationListener<FireEvent> {
@Override
public void onApplicationEvent(FireEvent event) { System.out.println("Save person"); }
}Publishing the event triggers both listeners, and events propagate from child to parent contexts.
Namespace extension
Custom XML namespaces are defined with an XSD, a NamespaceHandler, and entries in spring.handlers and spring.schemas. Example creates a sanyou namespace with a mybean element that registers a User bean.
public class SanYouNameSpaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("mybean", new SanYouBeanDefinitionParser());
}
private static class SanYouBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected boolean shouldGenerateId() { return true; }
@Override
protected String getBeanClassName(Element element) { return element.getAttribute("class"); }
}
}Using
<sanyou:mybean class="com.sanyou.spring.extension.User"/>in applicationContext.xml successfully creates the User bean.
Spring’s extension points—such as BeanPostProcessor, ApplicationEvent, and custom namespaces—provide powerful ways to customize framework behavior and integrate third‑party libraries.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
