实现简单的Spring容器

在学习Spring的时候,常听到的两个东西:IOC和DI。

IOC:控制反转,将对象的生命周期交给Spring去维护,我们需要对象时从容器中拿。

DI:依赖注入,类与类之间的依赖关系也交给Spring去维护。例如:A依赖B,程序运行时Spring会帮我们自动注入B实例。

Spring的容器有两大类:BeanFactory和ApplicationContext。
具体的内容可以参考笔者以前的博客,今天主要记录如何自己实现简单的ApplicationContext。

查看Spring的源码会非常头疼,Spring封装了太多的类,设计固然优秀,但是小白看起来确实很吃力啊…

容器

Spring初始化容器有两种方式:通过注解和xml配置文件。
即AnnotationConfigApplicationContext和ClassPathXmlApplicationContext。
有兴趣的同学可以去看下源码。
原理都差不多,无非就是解析注解和xml文件的区别。

今天通过注解的方式来实现。

分析

实现前先简单分析一下过程,没有思路是很难写代码的。

先总结一下需要用到的注解。

基本注解

  • @ComponentScan
  • @Component
  • @Scope
  • @Autowired

学过Spring肯定对这些注解很熟悉,现在我们要自己来实现这些注解。

为了区别于Spring的注解,自定义注解都会加“My”前缀。

实现过程

  1. 编写自己的注解
  2. 定义需要扫描的包路径
  3. 将扫描的类进行实例化,放到容器中
  4. 将实例化的类进行加工(依赖注入)
  5. 客户端获取Bean时从容器中返回

自己实现

项目结构

在这里插入图片描述

自定义注解

首先定义需要用到的4个基本注解,然后再来实现功能。

@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
//组件类的扫描
public @interface MyCommentScan {
	String value();
}

@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
//组件 扫描到会实例化到容器中
public @interface MyComment {
}

@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
//作用域 单例/多例
public @interface MyScope {
	String value();
}


@Target(ElementType.FIELD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
//依赖注入
public @interface MyAutoWrite {
}

指定扫描的包

/**
 * @Description: 扫描com.ch.entity包下的类
 */
@MyCommentScan("com.ch.entity")
public class BeanConfig {
}

实体类

创建两个实体类Person和Wife,且Person依赖Wife。
将其注册为组件,注册到容器中,容器会帮我们自动做 依赖注入。

/**
 * @Description:
 */
@MyComment
//默认单例,可配置
//@MyScope("prototype")
@Getter
@Setter
public class Person {
	private String name;
	private int age;
	//自动注入Wife
	@MyAutoWrite
	Wife wife;

	public void say() {
		System.out.println("person...");
	}
}

@MyComment
@Getter
@Setter
public class Wife {
	public void say() {
		System.out.println("wife...");
	}
}

重头戏

现在开始编写核心实现。

BeanDefinition

编写一个类,作用是对Bean进行定义描述。
这里简单点,只描述 作用域。

/**
 * @Description: Bean的定义描述类
 */
@Data
public class BeanDefinition {
	//默认 单例
	private boolean single = true;
	//是否多例
	private boolean prototype = false;

	public boolean isSingle(){
		return !prototype;
	}
}
AppContext

这里实现了所有的逻辑,包括对类进行扫描,注册到容器,对类进行依赖注入等…。

/**
 * @Description: 基于注解的 应用上下文
 */
public class AppContext {
	//Bean的配置类,基于该类初始化容器
	private Class configClass;
	//扫描到的Bean容器
	private Map<Class, Object> beanContainer;
	//扫描的Bean的定义描述
	private Map<Class, BeanDefinition> beanDefinitionMap;

	public AppContext(Class configClass) {
		this.configClass = configClass;
		//初始化容器
		init();
	}

	private void init() {
		MyCommentScan configClassAnnotation = (MyCommentScan) configClass.getAnnotation(MyCommentScan.class);
		if (configClassAnnotation == null) {
			return;
		}
		String basePackage = configClassAnnotation.value();
		if (StrUtil.isBlank(basePackage)) {
			return;
		}
		Set<Class<?>> scanClass = ClassScaner.scanPackage(basePackage);
		beanContainer = new HashMap<>(16);
		beanDefinitionMap = new HashMap<>(16);
		for (Class<?> c : scanClass) {
			if (c.getAnnotation(MyComment.class) == null) {
				continue;
			}
			try {
				resolveBeanDefinition(c);
				if (beanDefinitionMap.get(c).isSingle()) {
					//单例的才直接实例化,多例的getBean时才实例化
					beanContainer.put(c, c.newInstance());
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		//对bean进行加工
		for (Map.Entry<Class, Object> entry : beanContainer.entrySet()) {
			beanProcess(entry.getKey(), entry.getValue());
		}
	}

	/**
	 * 解析Bean的定义
	 * @param c
	 */
	private void resolveBeanDefinition(Class c){
		BeanDefinition definition = new BeanDefinition();
		MyScope scope = (MyScope) c.getAnnotation(MyScope.class);
		definition.setPrototype(scope != null && "prototype".equalsIgnoreCase(scope.value()));
		beanDefinitionMap.put(c, definition);
	}

	/**
	 * 对bean进行加工 依赖注入
	 * @param c
	 * @return
	 */
	private void beanProcess(Class c,Object o){
		Field[] fields = c.getDeclaredFields();
		for (Field field : fields) {
			if (field.getAnnotation(MyAutoWrite.class) == null) {
				continue;
			}
			//去容器中找 是否有其依赖的对象
			if (!beanContainer.containsKey(field.getType())) {
				continue;
			}
			Object depend = beanContainer.get(field.getType());
			field.setAccessible(true);
			try {
				field.set(o, depend);
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 从容器中获取对象
	 * @param c
	 * @param <T>
	 * @return
	 */
	public <T> T getBean(Class<T> c) {
		BeanDefinition beanDefinition = beanDefinitionMap.get(c);
		if (beanDefinition == null) {
			return null;
		}
		if (beanDefinition.isSingle()) {
			return (T) beanContainer.get(c);
		}
		if (beanDefinition.isPrototype()) {
			try {
				T t = c.newInstance();
				beanProcess(c, t);
				return t;
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return null;
	}
}

客户端测试

单例

容器初始化时直接实例化对象并注册到容器中,每次获取时,直接从容器中返回,单实例。

public class Client {
	public static void main(String[] args) {
		AppContext context = new AppContext(BeanConfig.class);
		Person bean = context.getBean(Person.class);
		System.out.println(bean);
		bean = context.getBean(Person.class);
		System.out.println(bean);
		/*bean.say();
		bean.getWife().say();*/
	}
}

输出:
com.ch.entity.Person@87aac27
com.ch.entity.Person@87aac27

可以看到,获取的都是同一个实例。

多例

默认是单例,给Person类加上注解:@MyScope(“prototype”) 即可。

给Person类加上@MyScope(“prototype”)注解后重新测试,
输出如下:

com.ch.entity.Person@3e3abc88
com.ch.entity.Person@6ce253f1

每次getBean()获取的都是新的实例。

依赖注入

测试容器是否会帮我们自动注入依赖。

public class Client {
	public static void main(String[] args) {
		AppContext context = new AppContext(BeanConfig.class);
		Person bean = context.getBean(Person.class);
		System.out.println(bean);
		bean.say();
		System.out.println(bean.getWife());
		bean.getWife().say();
	}
}
输出:
com.ch.entity.Person@3e3abc88
person...
com.ch.entity.Wife@6ce253f1
wife...

可以看到,容器自动帮我们给Person实例自动注入了Wife实例。

尾巴

这应该是一个最简单的容器了,只实现了最基础的容器功能,但重要的是明白它的原理不是嘛。

毕竟,人生就是一个不断追求“不惑”的过程。

发布了100 篇原创文章 · 获赞 23 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_32099833/article/details/103281555
今日推荐