在学习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”前缀。
实现过程
- 编写自己的注解
- 定义需要扫描的包路径
- 将扫描的类进行实例化,放到容器中
- 将实例化的类进行加工(依赖注入)
- 客户端获取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实例。
尾巴
这应该是一个最简单的容器了,只实现了最基础的容器功能,但重要的是明白它的原理不是嘛。
毕竟,人生就是一个不断追求“不惑”的过程。