找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

202

积分

0

好友

22

主题
发表于 7 天前 | 查看: 27| 回复: 0

Spring 框架之所以在众多Java开发框架中脱颖而出,其强大的扩展能力是关键因素之一。它提供了丰富的扩展点,使得第三方组件能够无缝集成,也催生了大量基于Spring的二次开发项目。本文旨在梳理Spring的核心扩展点,并通过一个手写Mybatis与Spring整合的简易示例,带你窥探其扩展机制的精髓。

Spring 加载容器上下文的三种方式

Spring 加载容器上下文主要有以下三种方式,其中最常用的是基于配置类的方式。

public class ApplicationDemo {
    public static void main(String[] args) {
        // 1. 基于配置类加载Spring上下文
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        Student student = applicationContext.getBean(Student.class);
        System.out.println(student);

        // 2. 基于项目路径xml加载Spring上下文
        ClassPathXmlApplicationContext applicationContextXml = new ClassPathXmlApplicationContext("spring.xml");
        student = applicationContextXml.getBean(Student.class);
        System.out.println(student);

        // 3. 基于文件系统绝对路径xml加载Spring上下文
        FileSystemXmlApplicationContext applicationContextFileXml = new FileSystemXmlApplicationContext("E://spring.xml");
        student = applicationContextXml.getBean(Student.class);
        System.out.println(student);
    }
}

配置类 MyConfig 如下:

@Configuration
@ComponentScan({"com.itxs.pojo","com.itxs.extend"})
public class MyConfig {
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 1

其底层构造方法会依次执行注册和刷新:

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}

核心扩展点详解

掌握 Spring 的扩展点,是深入理解其框架设计和进行高阶 Java 开发的基础。下面我们逐一分析。

ApplicationContextInitializer

这是 Spring Boot 提供的扩展点,在 Spring 容器刷新之前初始化 ConfigurableApplicationContext 的回调接口。允许我们在容器完全初始化前执行自定义逻辑,例如激活特定配置或进行动态字节码注入。

实现类示例:

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println("-----------------------MyApplicationContextInitializer initialize");
    }
}

使用方式有三种:

  1. Main函数添加:

    @SpringBootApplication
    public class MyApplication {
        public static void main(String[] args) {
            SpringApplication springApplication = new SpringApplication(MyApplication.class);
            springApplication.addInitializers(new MyApplicationContextInitializer());
            springApplication.run(args);
        }
    }

    Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 2

  2. 配置文件配置 (application.yml):

    context:
      initializer:
        classes: com.itxs.extend.MyApplicationContextInitializer
  3. Spring Boot SPI扩展 (META-INF/spring.factories):

    org.springframework.context.ApplicationContextInitializer=com.itxs.extend.MyApplicationContextInitializer

    Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 3

BeanDefinitionRegistryPostProcessor

它是 BeanFactoryPostProcessor 的子接口,作用时机更早。在所有的 Bean 定义信息即将被加载但未实例化时,postProcessBeanDefinitionRegistry 方法被执行。我们可以在此动态注册自己的 Bean 定义。

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 4

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        System.out.println("BeanDefinitionRegistryPostProcessor-postProcessBeanDefinitionRegistry");
        System.out.println("BeanDefinitionCount:"+beanDefinitionRegistry.getBeanDefinitionCount());
        String[] beanDefinitionNames = beanDefinitionRegistry.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println("beanDefinitionName:"+beanDefinitionName);
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("BeanDefinitionRegistryPostProcessor-postProcessBeanFactory");
    }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 5

BeanFactoryPostProcessor

在 Spring 读取 Bean 定义信息之后,实例化 Bean 之前执行。用户可以修改已注册的 BeanDefinition 的元信息。

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("BeanFactoryPostProcessor-postProcessBeanFactory");
        // 将名为 “student” 的 Bean 的类型修改为 Teacher
        BeanDefinition student = configurableListableBeanFactory.getBeanDefinition("student");
        student.setBeanClassName(Teacher.class.getName());
    }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 6

BeanPostProcessor

该接口在 Bean 对象实例化、依赖注入完毕后,在初始化方法调用前后进行拦截。我们可以在此修改 Bean 属性或生成动态代理,Spring AOP 正是基于此实现。

  • postProcessBeforeInitialization: 初始化之前。
  • postProcessAfterInitialization: 初始化之后。
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization beanName:"+beanName);
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization beanName:"+beanName);
        // 如果 Bean 是 Student 类型,则返回 Teacher 类型(示例逻辑)
        if (bean.getClass().isAssignableFrom(Student.class)){
            return Teacher.class;
        }
        return null;
    }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 7
Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 8

InstantiationAwareBeanPostProcessor

BeanPostProcessor 的子接口,扩展了实例化阶段和属性注入阶段的干预能力。

  • postProcessBeforeInstantiation: 实例化(new)之前。
  • postProcessAfterInstantiation: 实例化之后。
  • postProcessProperties: 属性注入时触发,@Autowired 等注解基于此实现。
@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInstantiation beanName" + beanName);
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInstantiation beanName" + beanName);
        return false;
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        System.out.println("postProcessProperties beanName" + beanName);
        return null;
    }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 9
Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 10

SmartInstantiationAwareBeanPostProcessor

InstantiationAwareBeanPostProcessor 的子接口,提供了更精细的控制。

  • predictBeanType: 预测 Bean 类型。
  • determineCandidateConstructors: 决定使用哪个构造函数。
  • getEarlyBeanReference: 用于解决循环依赖,提前暴露 Bean 引用。
@Component
public class MySmartInstantiationAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor{
    @Override
    public Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
        System.out.println("predictBeanType beanName:"+beanName);
        return null;
    }

    @Override
    public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException {
        if (!beanClass.isAssignableFrom(Student.class)){
            System.out.println("determineCandidateConstructors beanName:"+beanName);
        }
        return null;
    }

    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        System.out.println("getEarlyBeanReference beanName:"+beanName);
        return null;
    }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 11
Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 12

通过 ApplicationContextAwareProcessor 触发的扩展点

ApplicationContextAwareProcessor 是一个 BeanPostProcessor,它在初始化前触发一系列 Aware 接口的回调,让 Bean 能感知到 Spring 容器的特定对象。

private void invokeAwareInterfaces(Object bean) {
   if (bean instanceof EnvironmentAware) {
      ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
   }
   if (bean instanceof EmbeddedValueResolverAware) {
      ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
   }
   if (bean instanceof ResourceLoaderAware) {
      ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
   }
   if (bean instanceof ApplicationEventPublisherAware) {
      ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
   }
   if (bean instanceof MessageSourceAware) {
      ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
   }
   if (bean instanceof ApplicationStartupAware) {
      ((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup());
   }
   if (bean instanceof ApplicationContextAware) {
      ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
   }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 13

SmartInitializingSingleton

当 Spring 容器管理的所有非懒加载单例 Bean 都初始化完成后,会触发此接口的 afterSingletonsInstantiated 方法。

@Component
public class MySmartInitializingSingleton implements SmartInitializingSingleton {
    @Override
    public void afterSingletonsInstantiated() {
        System.out.println("afterSingletonsInstantiated");
    }
}

FactoryBean

用于定制复杂对象的创建逻辑。通过 getBean(“beanName”) 获取到的是 getObject() 方法返回的对象;要获取 FactoryBean 本身,需使用 getBean(“&beanName”)

@Component
public class MyFactoryBean implements FactoryBean<Teacher> {
    @Override
    public Teacher getObject() throws Exception {
        return new Teacher();
    }

    @Override
    public Class<?> getObjectType() {
        return Teacher.class;
    }
}
public class ApplicationExtend {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        Object student = applicationContext.getBean("student");
        System.out.println("object:"+student);
        System.out.println("factoryBeanReturn:"+applicationContext.getBean("myFactoryBean")); // 获取Teacher对象
        System.out.println("factoryBeanSelf:"+applicationContext.getBean("&myFactoryBean")); // 获取FactoryBean本身
    }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 14
Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 15

CommandLineRunner (Spring Boot)

Spring Boot 提供的扩展接口,项目启动完毕后自动执行 run 方法。多个实现可用 @Order 注解排序。

@Component
@Order(1)
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyCommandLineRunner run args:" +args);
    }
}
@Component
@Order(0)
public class MyTwoCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyTwoCommandLineRunner run args:" +args);
    }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 16

ApplicationListener

用于监听 Spring 的内置或自定义事件。常见内置事件如:

  • ContextRefreshedEvent: 容器刷新完成。
  • ContextClosedEvent: 容器关闭。
@Configuration
public class MyApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        System.out.println("MyApplicationListener onApplicationEvent");
    }
}

BeanNameAware & BeanFactoryAware

  • BeanNameAware: 在初始化前,让 Bean 获知自己在容器中的名字。
  • BeanFactoryAware: 在实例化后、属性注入前,让 Bean 获知 BeanFactory 实例。
@Component
public class MyBeanNameAware implements BeanNameAware {
    @Override
    public void setBeanName(String s) {
        System.out.println("setBeanName:"+s);
    }
}
@Component
public class MyBeanFactoryAware implements BeanFactoryAware {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanFactoryAware setBeanFactory:"+beanFactory.getBean("myBeanFactoryAware").getClass().getSimpleName());
    }
}

初始化及销毁回调方法

  • InitializingBean#afterPropertiesSet: 属性设置完成后调用,在 postProcessAfterInitialization 之前。
  • DisposableBean#destroy: Bean 销毁时调用。
  • @PostConstruct: 标注的方法在 postProcessBeforeInitialization 之后,afterPropertiesSet 之前执行。
  • @PreDestroy: 标注的方法在容器关闭时执行。
@Repository
public class Student implements InitializingBean, DisposableBean {

    @PostConstruct
    public void init() throws Exception {
        System.out.println("Student init");
    }

    @PreDestroy
    public void close() throws Exception {
        System.out.println("Student close");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("Student destroy");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Student afterPropertiesSet");
    }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 17
Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 18

手写 Mybatis-Spring 整合示例

理解了 Spring 的扩展点后,我们来看一个实际应用:模拟 Mybatis 如何与 Spring 整合。其源码思路主要利用了 FactoryBeanImportBeanDefinitionRegistrar

本示例仅为演示整合原理,不引入真实 Mybatis 依赖。

核心思路

  1. 利用 JDK 动态代理为 Mapper 接口生成代理对象。
  2. 借助 FactoryBean,使其 getObject() 方法返回代理对象,getObjectType() 方法返回 Mapper 接口类型。
  3. 通过 ImportBeanDefinitionRegistrar 配合自定义注解,扫描指定包下的 Mapper 接口,并将对应的 FactoryBean 定义动态注册到 Spring 容器中。

项目结构与实现

1. 项目依赖 (pom.xml)
仅需 Spring 上下文依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.itxs</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.9</version>
        </dependency>
    </dependencies>
</project>

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 19
Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 20

2. 自定义扫描注解 (ItxsScan)
通过 @Import 导入注册器。

@Retention(RetentionPolicy.RUNTIME)
@Import({ItxsImportBeanDefinitionRegistrar.class})
public @interface ItxsScan {
    String value() default "";
}

3. Spring 配置类 (AppConfig)
使用自定义的 @ItxsScan 注解指定 Mapper 接口的扫描路径。

@Configuration
@ComponentScan({"com.itxs"})
@ItxsScan("com.itxs.dao")
public class AppConfig {
}

4. Mapper 接口与自定义SQL注解
模拟 Mybatis 的 @Select 注解。

package com.itxs.dao;

import com.itxs.annotation.ItxsSelect;
import com.itxs.pojo.User;

public interface UserMapper {
    @ItxsSelect("select * from user where user_id = #{userId}")
    User selectByUserId(Integer userId);
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 21

5. Service 层
直接通过 @Autowired 注入 Mapper 接口,这也是整合后我们希望达到的效果。

@Component
public class UserService {
    @Autowired
    UserMapper userMapper;
    @Autowired
    OrderMapper orderMapper;

    public void getUser(){
        userMapper.selectByUserId(1);
        orderMapper.selectByOrderId(1);
    }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 22

6. 核心:FactoryBean 实现 (ItxsFactoryBean)
负责创建 Mapper 接口的代理对象。当调用代理方法时,会打印出 SQL 信息(此处简化了真实的 数据库 操作)。

package com.itxs.utils;

import com.itxs.annotation.ItxsSelect;
import org.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ItxsFactoryBean implements FactoryBean {
    private Class mapper;
    public ItxsFactoryBean(Class mapper) {
        this.mapper = mapper;
    }

    @Override
    public Object getObject() throws Exception {
        // 使用 JDK 动态代理
        Object o = Proxy.newProxyInstance(ItxsFactoryBean.class.getClassLoader(), new Class[]{mapper}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (Object.class.equals(method.getDeclaringClass())){
                    return method.invoke(this,args);
                }
                // 获取方法上的SQL注解
                ItxsSelect annotation = method.getAnnotation(ItxsSelect.class);
                System.out.println("调用方法名称是"+method.getName()+",sql语句为"+annotation.value());
                // 此处可衔接真实的 Mybatis 执行流程
                return null;
            }
        });
        return o;
    }

    @Override
    public Class<?> getObjectType() {
        return mapper;
    }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 23

7. 核心:ImportBeanDefinitionRegistrar 实现 (ItxsImportBeanDefinitionRegistrar)
在 Spring 容器启动时被调用,扫描指定包,为每个 Mapper 接口创建 ItxsFactoryBean 的 BeanDefinition 并注册。

package com.itxs.utils;

import com.itxs.annotation.ItxsScan;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import java.io.File;
import java.net.URL;
import java.util.*;

public class ItxsImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, org.springframework.beans.factory.support.BeanNameGenerator importBeanNameGenerator) {
        // 获取 @ItxsScan 注解的扫描路径
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ItxsScan.class.getName());
        String mapperPath = annotationAttributes.get("value").toString();
        List<Class> mappers = scan(mapperPath);

        for (Class mapper : mappers) {
            // 构建 ItxsFactoryBean 的 BeanDefinition
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
            AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
            beanDefinition.setBeanClass(ItxsFactoryBean.class);
            // 设置构造参数:Mapper接口的Class对象
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapper);
            // 注册BeanDefinition,Bean名称为类名首字母小写
            registry.registerBeanDefinition(StringUtils.toLowerCaseFirstOne(mapper.getSimpleName()),beanDefinition);
        }
    }
    // 扫描指定包路径下所有 .class 文件的工具方法(略)
    private List<Class> scan(String path) { ... }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 24

8. 主程序
启动 Spring 容器,并调用 Service 方法验证整合是否成功。

package com.itxs;

import com.itxs.config.AppConfig;
import com.itxs.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MybatisApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.getUser();
    }
}

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 25

运行结果

执行主程序,成功通过 Service 调用了 Mapper 代理对象的方法,并打印出了对应的 SQL 语句,证明我们的简易整合框架工作正常。这个示例清晰地展示了如何利用 Spring 的扩展点(FactoryBean, ImportBeanDefinitionRegistrar)将第三方框架(如 Mybatis)的组件(Mapper 接口)纳入 Spring 容器的管理之中。

Spring扩展点机制详解:从原理到Mybatis整合实践 - 图片 - 26

通过以上对 Spring 扩展点的系统梳理和一个完整的整合案例实践,我们可以深刻体会到 Spring 框架在设计上的高度可扩展性。这种设计哲学使得开发者能够灵活地介入 Bean 的生命周期,为构建复杂的企业级 应用架构 提供了坚实的基础。




上一篇:Linux运维10个核心命令实战指南:高效监控、管理与故障排查
下一篇:整合信息论IIT深度解析:从香农信息到意识意义的内在因果结构
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 21:11 , Processed in 0.276252 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表