掘金 后端 ( ) • 2024-05-14 09:15

highlight: xcode theme: vuepress

image.png

前置知识

回顾一下Spring Framework

BeanFactory 作为 Spring Framework 最顶层的一个接口,定义了 IOC 容器的基本功能规范。从类图中我们可以发现最终的默认实现类是 DefaultListableBeanFactory,它实现了所有的接口。那么为何要定义这么多层次的接口呢?

每个接口都有它的使用场合,主要是为了区分在 Spring 内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。

例如:

  • ListableBeanFactory 接口表示这些 Bean 可列表化。
  • HierarchicalBeanFactory 表示这些 Bean 是有继承关系的,也就是每个 Bean 可能有父 Bean。
  • AutowireCapableBeanFactory 接口定义 Bean 的自动装配规则。

这三个接口共同定义了 Bean 的集合、Bean 之间的关系及 Bean 行为。

在 BeanFactory 里只对 IOC 容器的基本行为做了定义,根本不关心你的 Bean 是如何定义及怎样加载的。正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的。

BeanFactory 有一个很重要的子接口,就是 ApplicationContext 接口,该接口主要来规范容器中的 Bean 对象是非延时加载,即在创建容器对象的时候就对象 Bean 进行初始化,并存储到一个容器中。

// 延时加载
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("bean.xml"));

// 立即加载
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean("user", User.class);

ApplicationContext 的子类主要包含两个方面:

  • ConfigurableApplicationContext 表示该 Context 是可修改的,也就是在构建 Context 中用户可以动态添加或修改已有的配置信息。
  • WebApplicationContext 顾名思义,就是为 Web 准备的 Context 他可以直接访问到 ServletContext,通常情况下,这个接口使用少。

要知道工厂是如何产生对象的,我们需要看具体的 IOC 容器实现,Spring 提供了许多 IOC 容器实现,比如:

  • ClasspathXmlApplicationContext:根据类路径加载 xml 配置文件,并创建 IOC 容器对象。
  • FileSystemXmlApplicationContext:根据系统路径加载 xml 配置文件,并创建 IOC 容器对象。
  • AnnotationConfigApplicationContext:加载注解类配置,并创建 IOC 容器。

image.png

总体来说 ApplicationContext 必须要完成以下几件事:

  • 标识一个应用环境
  • 利用 BeanFactory 创建 Bean 对象
  • 保存对象关系表
  • 能够捕获各种事件

BeanDefinition

这里的 BeanDefinition 就是我们所说的 Spring 的 Bean,我们自己定义的各个 Bean 其实会转换成一个个 BeanDefinition 存在于 Spring 的 BeanFactory 中:

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
	// DefaultListableBeanFactory 中使用 Map 结构保存所有的 BeanDefinition 信息
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256); 
}

BeanDefinition 中保存了我们的 Bean 信息,比如这个 Bean 指向的是哪个类、是否是单例的、是否懒加载、这个 Bean 依赖了哪些 Bean 等等。

BeanDefinitionReader

Bean 的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。

这个解析过程主要通过 BeanDefinitionReader 来完成,看看 Spring 中 BeanDefinitionReader 的类结构图,如下图所示:

image.png

BeanDefinitionReader 接口:

public interface BeanDefinitionReader {

    /**
     * 下面的loadBeanDefinitions都是加载bean定义,从指定的资源中
     */
    int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
    
    int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
    
    int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
    
    int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}

BeanFactory后置处理器

后置处理器是一种拓展机制,贯穿 Spring Bean 的生命周期。

后置处理器分为两类:

  • BeanFactory后置处理器:BeanFactoryPostProcessor

实现该接口,可以在 Spring 的 Bean 创建之前,修改 Bean 的定义属性。

public interface BeanFactoryPostProcessor {

    /**
     *  该接口只有一个方法 postProcessBeanFactory,方法参数是 ConfigurableListableBeanFactory,通过该
     *  参数,可以获取 BeanDefinition
     */
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

image.png

  • Bean后置处理器:BeanPostProcessor

BeanPostProcessor 是 Spring IOC 容器给我们提供的一个扩展接口。实现该接口,可以在 Spring 容器实例化Bean 之后,在执行 Bean 的初始化方法前后,添加一些处理逻辑。

image.png

public interface BeanPostProcessor {
    // Bean初始化方法调用前被调用
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    // Bean初始化方法调用后被调用
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

IOC流程图

Bean的创建流程

  1. 容器环境的初始化(系统、JVM 、解析器、类加载器等等)。
  2. Bean 工厂的初始化(IOC 容器首先会销毁旧工厂,旧 Bean、创建新的工厂)。
  3. 读取:通过 BeanDefinitonReader 读取我们项目中的配置(application.xml)。
  4. 定义:通过解析 Xml 文件内容,将里面的 Bean 解析成 BeanDefinition(未实例化、未初始化)。
  5. 将解析得到的 BeanDefinition,存储到工厂类的 Map 容器中。
  6. 调用 BeanFactoryPostProcessor 该方法是一种功能增强,可以在这个步骤对已经完成初始化的 BeanFactory 进行属性覆盖,或是修改已经注册到 BeanFactoryBeanDefinition
  7. 通过反射实例化 Bean 对象。
  8. 进入到 Bean 实例化流程,首先设置对象属性。
  9. 检查 Aware 相关接口,并设置相关依赖。
  10. 前置处理器,执行 BeanPostProcesserbefore 方法对 Bean 进行扩展。
  11. 检查是否有实现 InitializingBean 回调接口,如果实现就要回调其中的 afterPropertiesSet 方法,(通过可以完成一些配置的加载)。
  12. 检查是否有配置自定义的 init-method
  13. 后置处理器执行 BeanPostProcessorpostProcessAfterInitialization 方法,AOP 就是在这个阶段完成的, 在这里判断 Bean 对象是否实现接口,实现就使用 JDK 代理,否则选择 CGLIB。
  14. 对象创建完成,添加到 BeanFactory 的单例池中。

实操

定义BeanFactory接口

定义几个核心的 getBean 方法,Spring 源码中的其他 getBean 方法读者有兴趣可以自行实现。

public interface BeanFactory {

    Object getBean(String name) throws Exception;

    /**
     * 泛型方法,传入当前类或者其子类
     *
     * @param name  名称
     * @param clazz 字节码对象
     * @param <T>   泛型
     * @return T
     * @throws Exception 异常
     */
    <T> T getBean(String name, Class<? extends T> clazz) throws Exception;

    /**
     * 根据类型查找 Bean
     *
     * @param requiredType 类型
     * @return T
     * @throws BeanException 找到多个符合条件的 Bean 抛出异常
     */
    <T> T getBean(Class<?> requiredType) throws Exception;
}

定义BeanDefinition类

在源码中 BeanDefinition 是一个接口,我这里为了简化开发使用了类,读者有兴趣可以定义接口自行扩展。

public class BeanDefinition {

    private String id;

    private String className;

    public BeanDefinition(String id, String className) {
        this.id = id;
        this.className = className;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }
}

定义BeanDefinitionReader接口

想要创建 Bean,那么首先要读取 Bean,所以需要定义 BeanDefinitionReader 接口:

import org.codeart.ioc.support.BeanDefinitionRegistry;

public interface BeanDefinitionReader {

    // 获取注册表对象
    BeanDefinitionRegistry getRegistry();

    // 加载配置类并在注册表中进行注册
    void loadBeanDefinitions(String configLocation) throws Exception;

}

在本次案例中,我使用的注解方式来配置 Bean,而不是传统的 XML 文件方式。一方面是 XML 文件方式配置 Bean 在业界早就已经过时了,另一方面是使用注解方式配置 Bean 可以增强我们对注解和反射的理解程度。

定义 AnnotationDefinitionReader 接口:

/**
 * 注解方式 BeanDefinition 读取器
 */
public class AnnotationDefinitionReader implements BeanDefinitionReader {
    
    private final Map<String, Object> singletonObjects;
    
    private final BeanDefinitionRegistry registry;

    public AnnotationDefinitionReader(Map<String, Object> singletonObjects) {
        this.singletonObjects = singletonObjects;
        this.registry = new SimpleBeanDefinitionRegistry();
    }

    public AnnotationDefinitionReader(Map<String, Object> singletonObjects, BeanDefinitionRegistry registry) {
        this.singletonObjects = singletonObjects;
        this.registry = registry;
    }

    @Override
    public BeanDefinitionRegistry getRegistry() {
        return registry;
    }

    @Override
    public void loadBeanDefinitions(String configLocation) throws Exception {
        // 极简的 IOC 容器,目前不考虑类似于 Spring 的注解的嵌套修饰,如 @Component 修饰 @Configuration
        List<Class<?>> classes = AnnotationUtil.scanComponents(configLocation, Component.class);
        // 所有被 @Bean 修饰的方法必须要是 public
        for (Class<?> clazz : classes) {
            // 解析 @Component
            Component component = clazz.getAnnotation(Component.class);
            String componentName = component.value();
            if (StrUtil.isEmpty(componentName)) {
                componentName = StrUtil.lowerFirst(clazz.getSimpleName());
            }
            BeanDefinition beanDefinition = new BeanDefinition(componentName, clazz.getName());
            registry.registerBeanDefinition(componentName, beanDefinition);
            
            // 解析 @Bean
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                // @Bean 注解存在,暂时不支持方法参数注入以及懒加载
                if (method.isAnnotationPresent(Bean.class)) {
                    Bean annotation = method.getAnnotation(Bean.class);
                    String beanName = annotation.value();
                    if (StrUtil.isEmpty(beanName)) {
                        beanName = method.getName();
                    }
                    Class<?> returnedType = method.getReturnType();
                    BeanDefinition definition = new BeanDefinition(beanName, returnedType.getName());
                    registry.registerBeanDefinition(beanName, definition);
                    Object obj = clazz.newInstance();
                    Object returnedObj = method.invoke(obj);
                    if (singletonObjects.containsKey(beanName)) {
                        throw new BeanException(String.format("Bean already exist: %s.\n", beanName));
                    }
                    singletonObjects.put(beanName, returnedObj);
                }
            }
        }
    }
}

定义必需的注解

使用注解方式配置 Bean,那么必然离不开注解。我在这里定义了 @Component@Bean@Inject 注解。因为是主打极简,所以没有考虑注解嵌套修饰的情况,源码中存在大量注解嵌套的情况,读者有兴趣自行实现。

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    
    String value() default "";
}


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {

    /**
     * The value may indicate a suggestion for a logical component name,
     * to be turned into a Spring bean in case of an autodetected component.
     * @return the suggested component name, if any (or empty String otherwise)
     */
    String value() default "";

}

/**
 * 注入 Bean 注解
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inject {
    
    String value() default "";

}

定义Bean注册器

读取的 Bean 都要转化为 BeanDefinition 对象存储到一个 Map 集合中去,这样才能去创建 Bean。

/**
 * BeanDefinition 注册器
 */
public interface BeanDefinitionRegistry {

    /**
     * 注册BeanDefinition对象到注册表中
     */
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanException;

    /**
     * 从注册表中删除指定名称的BeanDefinition对象
     */
    void removeBeanDefinition(String beanName) throws Exception;

    /**
     * 根据名称从注册表中获取BeanDefinition对象
     */
    BeanDefinition getBeanDefinition(String beanName) throws Exception;

    /**
     * 判断注册表中是否包含指定名称的BeanDefinition对象
     */
    boolean containsBeanDefinition(String beanName);

    /**
     * 获取注册表中BeanDefinition对象的个数
     */
    int getBeanDefinitionCount();

    /**
     * 获取注册表中所有的BeanDefinition的名称
     */
    String[] getBeanDefinitionNames();
    
}

定义简单的实现类:

/**
 * 注册器默认实现
 */
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {

    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanException {
        if (beanDefinitionMap.containsKey(beanName)) {
            throw new BeanException(String.format("Bean already exists: %s.\n", beanName));
        }
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public void removeBeanDefinition(String beanName) throws Exception {
        beanDefinitionMap.remove(beanName);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws Exception {
        return beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    @Override
    public String[] getBeanDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }
}

定义上下文接口

定义 ApplicationContext 接口:

public interface ApplicationContext extends BeanFactory {

    /**
     * 进行配置文件加载,并进行对象创建
     */
    void refresh();

}

定义抽象伴随类 AbstractApplicationContext

public abstract class AbstractApplicationContext implements ApplicationContext {

    // 声明解析器变量
    protected BeanDefinitionReader beanDefinitionReader;

    // 定义存储 Bean 对象的 Map 集合
    protected final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(128);

    // 声明配置文件类路径的变量
    protected String configLocation;

    @Override
    public void refresh() {
        // 加载beanDefinition对象,省略了 Spring 中其他组件的初始化
        try {
            beanDefinitionReader.loadBeanDefinitions(configLocation);
            // 初始化bean
            finishBeanInitialization();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * bean初始化
     *
     * @throws Exception 异常
     */
    protected void finishBeanInitialization() throws Exception {
        // 获取对应的注册表对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();

        // 获取beanDefinition对象
        String[] beanNames = registry.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            //进行bean的初始化
            getBean(beanName);
        }
    }
}

定义实现类 AnnotatedApplicationContext

/**
 * 应用了注解的应用上下文
 */
public class AnnotatedApplicationContext extends AbstractApplicationContext {

    public AnnotatedApplicationContext(String configLocation) {
        this.configLocation = configLocation;
        // 把单例对象集合注入进去
        this.beanDefinitionReader = new AnnotationDefinitionReader(this.singletonObjects);
        this.refresh();
    }

    @Override
    public Object getBean(String name) throws Exception {
        if (singletonObjects.containsKey(name)) {
            return singletonObjects.get(name);
        }
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        BeanDefinition beanDefinition = registry.getBeanDefinition(name);

        // 通过反射创建对象
        String className = beanDefinition.getClassName();
        Class<?> clazz = Class.forName(className);
        Object beanObj = clazz.newInstance();

        // 调用 set 方法注入 Bean
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(Inject.class)) {
                Inject annotation = field.getAnnotation(Inject.class);
                String beanName = annotation.value();
                if (StrUtil.isEmpty(beanName)) {
                    Class<?> aClass = field.getType();
                    Object bean = getBean(aClass);
                    field.set(beanObj, bean);
                } else {
                    Object bean = getBean(beanName);
                    field.set(beanObj, bean);
                }
            }
        }

        singletonObjects.put(name, beanObj);
        return beanObj;
    }

    @Override
    public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
        Object bean = getBean(name);
        if (bean == null) {
            return null;
        }
        return clazz.cast(bean);
    }

    @Override
    public <T> T getBean(Class<?> requiredType) throws Exception {
        String ans = null;
        int count = 0;
        for (Map.Entry<String, Object> entry : this.singletonObjects.entrySet()) {
            Object obj = entry.getValue();
            Class<?> aClass = obj.getClass();
            if (aClass.getName().equals(requiredType.getName())) {
                ans = entry.getKey();
                count++;
            }
        }
        if (count > 1) {
            throw new BeanException(String.format("Expect 1 bean, but found more than 1: %s.\n", requiredType.getName()));
        }
        if (StrUtil.isNotEmpty(ans)) {
            return (T) getBean(ans);
        }

        BeanDefinitionRegistry registry = this.beanDefinitionReader.getRegistry();
        for (String beanDefinitionName : registry.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionName);
            if (beanDefinition.getClassName().equals(requiredType.getName())) {
                return (T) getBean(beanDefinitionName);
            }
        }
        throw new BeanException(String.format("Bean not found: %s.\n", requiredType.getName()));
    }
}

定义注解工具类AnnotationUtil

定义此工具类用于扫描类的根路径下的组件,实在不会写可以问 ChatGPT:

public class AnnotationUtil {

    /**
     * 在某个路径下扫描被某个注解修饰的所有字节码对象
     * @param packagePath   类路径
     * @param annotation    修饰类的注解
     * @return  字节码对象列表
     */
    public static List<Class<?>> scanComponents(String packagePath, Class<? extends Annotation> annotation) {
        List<Class<?>> componentClasses = new ArrayList<>();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String path = packagePath.replace('.', '/');
        URL resource = classLoader.getResource(path);
        if (resource == null) {
            throw new IllegalArgumentException("Package not found: " + packagePath);
        }
        File directory = new File(resource.getFile());
        if (directory.exists()) {
            File[] files = directory.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory()) {
                        componentClasses.addAll(scanComponents(packagePath + "." + file.getName(), annotation));
                    } else if (file.getName().endsWith(".class")) {
                        String className = packagePath + '.' + file.getName().substring(0, file.getName().length() - 6);
                        try {
                            Class<?> clazz = Class.forName(className);
                            if (clazz.isAnnotationPresent(annotation)) {
                                componentClasses.add(clazz);
                            }
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return componentClasses;
    }

}

简单的测试

定义 AppConfig 配置类:

@Component
public class AppConfig {
    
    @Bean
    public User user01() {
        User user = new User();
        user.setId(888);
        user.setName("特朗普");
        user.setAddress("华盛顿");
        return user;
    }
    
    @Bean("user02")
    public User user02() {
        User user = new User();
        user.setId(999);
        user.setName("马斯克");
        user.setAddress("特斯拉");
        return user;
    }

}

定义 User 类:

@Data
public class User {
    
    private Integer id;
    
    private String name;
    
    private String address;

    public User() {
    }

    public User(Integer id, String name, String address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }
}

定义三层业务类:

@Component
public class UserDao {

    public List<User> getUserList() {
        User[] users = {
                new User(1, "洛必达", "艾泽拉斯"),
                new User(2, "欧拉", "铁炉堡"),
                new User(3, "高斯", "诺森德"),
                new User(4, "伯努利", "卡利姆多"),
        };
        return Arrays.stream(users).collect(Collectors.toList());
    }

}

@Component
public class UserService {
    
    @Inject
    private UserDao userDao;

    public List<User> getUserList() {
        return userDao.getUserList();
    }

}

@Component
public class UserController {

    @Inject
    private UserService userService;
    
    public List<User> getUserList() {
        return userService.getUserList();
    }
}

编写 JUnit:

public class AnnotatedApplicationContextTest {
    
    @Test
    public void testGetBean() throws Exception {
        String packageName = "org.codeart.ioc";
        AnnotatedApplicationContext ctx = new AnnotatedApplicationContext(packageName);
        UserController userController = ctx.getBean(UserController.class);
        List<User> userList = userController.getUserList();
        userList.forEach(System.out::println);
        User user01 = ctx.getBean("user01", User.class);
        User user02 = (User) ctx.getBean("user02");
        System.out.println(user01);
        System.out.println(user02);
    }

    @Test
    public void testGetBeanMoreThanOne() throws Exception {
        String packageName = "org.codeart.ioc";
        AnnotatedApplicationContext ctx = new AnnotatedApplicationContext(packageName);
        User user = ctx.getBean(User.class);
        System.out.println(user);
    }

}

打印结果:

image.png

image.png

参考源码

源码地址

总结

这个案例仅仅是一个非常简洁的 IOC 模型,还有很多不完善的地方也有可能有 bug。读者若是有兴趣,可以扩充完善,甚至加上 AOP 的实现以及 Bean 后置处理器的实现。