开发一个极简的IoC容器实现声明式注入,业余时间做点小玩具。
前置知识
回顾一下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 容器。
总体来说 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
的类结构图,如下图所示:
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;
}
- Bean后置处理器:BeanPostProcessor
BeanPostProcessor 是 Spring IOC 容器给我们提供的一个扩展接口。实现该接口,可以在 Spring 容器实例化Bean 之后,在执行 Bean 的初始化方法前后,添加一些处理逻辑。
public interface BeanPostProcessor {
// Bean初始化方法调用前被调用
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
// Bean初始化方法调用后被调用
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
IOC流程图
- 容器环境的初始化(系统、JVM 、解析器、类加载器等等)。
- Bean 工厂的初始化(IOC 容器首先会销毁旧工厂,旧 Bean、创建新的工厂)。
- 读取:通过
BeanDefinitonReader
读取我们项目中的配置(application.xml)。 - 定义:通过解析 Xml 文件内容,将里面的
Bean
解析成BeanDefinition
(未实例化、未初始化)。 - 将解析得到的
BeanDefinition
,存储到工厂类的Map
容器中。 - 调用
BeanFactoryPostProcessor
该方法是一种功能增强,可以在这个步骤对已经完成初始化的BeanFactory
进行属性覆盖,或是修改已经注册到BeanFactory
的BeanDefinition
- 通过反射实例化 Bean 对象。
- 进入到 Bean 实例化流程,首先设置对象属性。
- 检查
Aware
相关接口,并设置相关依赖。 - 前置处理器,执行
BeanPostProcesser
的before
方法对 Bean 进行扩展。 - 检查是否有实现 InitializingBean 回调接口,如果实现就要回调其中的 afterPropertiesSet 方法,(通过可以完成一些配置的加载)。
- 检查是否有配置自定义的
init-method
。 - 后置处理器执行
BeanPostProcessor
的postProcessAfterInitialization
方法,AOP 就是在这个阶段完成的, 在这里判断 Bean 对象是否实现接口,实现就使用 JDK 代理,否则选择 CGLIB。 - 对象创建完成,添加到
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);
}
}
打印结果:
参考源码
总结
这个案例仅仅是一个非常简洁的 IOC 模型,还有很多不完善的地方也有可能有 bug。读者若是有兴趣,可以扩充完善,甚至加上 AOP 的实现以及 Bean 后置处理器的实现。
转载自:https://juejin.cn/post/7368309373394092086