Spring源码分析 手写简单IOC容器
Spring的两大特性就是IOC和AOP。
IOC Container,控制反转容器,通过读取配置文件或注解,将对象封装成Bean存入IOC容器待用,程序需要时再从容器中取,实现控制权由程序员向程序的转变。
一、代码结构

二、注解扫描
1、新建OrderService、UserService、Test类用于模拟实际开发
@Component("orderService")
@Scope("prototype")
public class OrderService implements BeanNameAware {
@Autowired
private UserService userService;
public void test() {
System.out.println(userService);
}
public void setBeanName(String name) {
this.beanName = beanName;
}
}
@Component("userService")
public class UserService {
}
public class Test {
public static void main(String[] args) {
//对应AnnotationConfigApplicationContext
//扫描 (判断是否存在Component) + 实例化(Bean的生命周期:1、实例化 2、依赖注入)
CustomApplicationContext customApplicationContext = new CustomApplicationContext(AppConfig.class);
Object userService1 = customApplicationContext.getBean("orderService");
System.out.println(userService);
System.out.println(userService1);
}
}
2、新建注解Component、ComponentScan、Autowired、Scope
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component { // 用于将类注册成Bean
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan { //用于配置类扫描的包
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})
public @interface Autowired { //属性注入
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope { //bean的范围
String value() default "singleton";
}
3、定义CustomApplicationContext实现注解扫描
- 对应Spring的AnnotationConfigApplicationContext,通过传入配置文件对象,读取ComponentScan的Value进行包扫描,扫描后获取带@Component注解的类
public class CustomApplicationContext {
private Class configClass;
public CustomApplicationContext(Class configClass) {
this.configClass = configClass;
//扫描(判断类上是否存在Component)(class文件) --> 形成beanDefinition
List<Class> classList = scan(configClass);
}
private List<Class> scan(Class configClass) {
List<Class> list = new ArrayList<Class>();
//存在注解,通过value获取要扫描包路径
if (configClass.isAnnotationPresent(ComponentScan.class)){
ComponentScan componentScan = (ComponentScan)configClass.getAnnotation(ComponentScan.class);
String path = componentScan.value();
path = path.replace(".","/"); //包转化为包路径(com.gg.service)
//扫描path路径下到类
ClassLoader classLoader = CustomApplicationContext.class.getClassLoader();
// 利用类加载器,根据包路径获取URL/target/classes/com/gg/service
URL resource = classLoader.getResource(path);
File file = new File(resource.getFile());
if (file.isDirectory()) {
for (File f: file.listFiles()){
String absolutePath = f.getAbsolutePath(); //类的完整路径
absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
absolutePath = absolutePath.replace("/",".");
//com.gg.service.userService
Class clazz = null;
try {
clazz = classLoader.loadClass(absolutePath);
// 存在注解,将类加入数组
if (clazz.isAnnotationPresent(Component.class)) {
list.add(clazz);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
return list;
}
}
4、将扫描获得的类封装成beanDefinition
- 扫描得到的类,将beanName、类型、类的范围封装成beanDefinition
- getBean时不用重新扫描,直接冲beanDefinitionMap获取
private Map<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
public CustomApplicationContext(Class configClass) {
this.configClass = configClass;
List<Class> classList = scan(configClass); //扫描得到class
for (Class clazz: classList){
Component component = (Component) clazz.getAnnotation(Component.class);
String beanName = component.value();
BeanDefinition beanDefinition = new BeanDefinition();
if (clazz.isAnnotationPresent(Scope.class)){
Scope scope = (Scope) clazz.getAnnotation(Scope.class);
beanDefinition.setScope(scope.value());
}else{
beanDefinition.setScope("singleton");//默认为单例
}
beanDefinition.setBeanClass(clazz);
beanDefinitionMap.put(beanName,beanDefinition);
}
public class BeanDefinition { //bean的定义
private String scope;
private Class beanClass;
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public Class getBeanClass() {
return beanClass;
}
public void setBeanClass(Class beanClass) {
this.beanClass = beanClass;
}
}
- 至此,我们通过新建CustomApplicationContext,将添加注解的类加入到beanDefinitionMap。
三、实例化Bean
1、单例类先实例化,存入单例池
- 从beanDefinitionMap取出BeanDefinition,对单例类调用createBean()实例化,添加到单例池
private Map<String,Object> singletonPool = new ConcurrentHashMap<String, Object>();
public CustomApplicationContext(Class configClass) {
this.configClass = configClass;
//扫描将注解的类存入beanDefinitionMap
List<Class> classList = scan(configClass);
for (Class clazz: classList){
Component component = (Component) clazz.getAnnotation(Component.class);
String beanName = component.value();
BeanDefinition beanDefinition = new BeanDefinition();
if (clazz.isAnnotationPresent(Scope.class)){
Scope scope = (Scope) clazz.getAnnotation(Scope.class);
beanDefinition.setScope(scope.value());
}else{
beanDefinition.setScope("singleton");
}
beanDefinition.setBeanClass(clazz);
beanDefinitionMap.put(beanName,beanDefinition);
}
//将单例类添加到单例池singletonPool
for (String beanName : beanDefinitionMap.keySet()){
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")){
//实例化bean
Object bean = createBean(beanName,beanDefinition);
singletonPool.put(beanName,bean);
}
}
}
private Object createBean(String beanName,BeanDefinition beanDefinition) {
//实例化、填充属性、Aware、初始化
Class beanClass = beanDefinition.getBeanClass();
try {
// 从beanDefinition中获取类型,并实例化
Object bean = beanClass.getDeclaredConstructor().newInstance();
//属性填充
Field[] fields = beanClass.getDeclaredFields(); //DeclaredFields 所有属性
for(Field field: fields){
if (field.isAnnotationPresent(Autowired.class)){
Object annotationField = getBean(field.getName());
field.setAccessible(true); //反射产生对象要打开权限
field.set(bean,annotationField);
}
}
// Aware
if (bean instanceof BeanNameAware){
((BeanNameAware)bean).setBeanName(beanName); //实现该端口就调用此方法
}
// 初始化
if (bean instanceof InitializingBean){
((InitializingBean)bean).afterPropertiesSet(); //实现该端口就调用此方法
}
return bean;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
- 新建BeanNameAware、InitializingBean接口,实例类时,通过判断是否实现该接口,在实例初始化时
public interface BeanNameAware {
public void setBeanName(String name);
}
public interface InitializingBean {
public void afterPropertiesSet();
}
- 通过调用createBean()实例化beanDefinition中的类,单例在启动时实例,原型在调用getBean()再实例
2、getBean()
public Object getBean(String beanName){
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("prototype")){
return createBean(beanName,beanDefinition);
}else{
Object bean = singletonPool.get(beanName);
if (bean == null){
Object newBean = createBean(beanName,beanDefinition);
singletonPool.put(beanName,newBean);
return newBean;
}
return bean;
}
}
Spring源码分析 手写简单IOC容器的更多相关文章
- Spring源码分析之-加载IOC容器
本文接上一篇文章 SpringIOC 源码,控制反转前的处理(https://mp.weixin.qq.com/s/9RbVP2ZQVx9-vKngqndW1w) 继续进行下面的分析 首先贴出 Spr ...
- 源码分析 | 手写mybait-spring核心功能(干货好文一次学会工厂bean、类代理、bean注册的使用)
作者:小傅哥 博客:https://bugstack.cn - 汇总系列原创专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言介绍 一个知识点的学习过程基本分为:运行helloworld ...
- 面试必会之ArrayList源码分析&手写ArrayList
简介 ArrayList是我们开发中非常常用的数据存储容器之一,其底层是数组实现的,我们可以在集合中存储任意类型的数据,ArrayList是线程不安全的,非常适合用于对元素进行查找,效率非常高. 线程 ...
- spring源码分析之玩转ioc:bean初始化和依赖注入(一)
最近赶项目,天天加班到十一二点,终于把文档和代码都整完了,接上继续整. 上一篇聊了beanProcess的注册以及对bean的自定义修改和添加,也标志着创建bean的准备工作都做好了,接下来就是开大招 ...
- Spring源码 20 手写模拟源码
参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...
- Spring源码分析(十九)容器的功能扩展概览
摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 经过前面几章的分析,相信大家已经对 Spring 中的容器功能有了简单 ...
- 《四 spring源码》手写springmvc
手写SpringMVC思路 1.web.xml加载 为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息.通过web.xml ...
- 《四 spring源码》手写springioc框架
手写SpringIOCXML版本 /** * 手写Spring专题 XML方式注入bean * * * */ public class ClassPathXmlApplicationContext { ...
- spring源码分析系列3:BeanFactory核心容器的研究
目录 @(spring源码分析系列3:核心容器的研究) 在讲容器之前,再明确一下知识点. BeanDefinition是Bean在容器的描述.BeanDefinition与Bean不是一个东西. Be ...
随机推荐
- 【FZYZOJ】下片 题解(最短路+超级源点)
题目描述 为了提高服务器的耐受能力,很多流量大的网站都会架设多台服务器,而互联网的路由能找到线路最短的一台服务器. 现在UOI想要下片,他有好多台电脑,又有好多服务器可以提供下载.UOI将给你一个网络 ...
- Serverless无服务器架构详解
本文对Serverless架构的基础概念.具体产品.应用场景.工作原理进行详细解析. 基础概念 Serverless: 无服务器架构,即在无需管理服务器等底层资源的情况下完成应用的开发和运行,是云原生 ...
- CI4框架应用三 - app目录
我们再来看一下项目的app目录结构,这个目录就是我们开发的主目录,项目的配置,代码的编写都在这个目录中. Administrator@PC- MINGW64 /c/wamp64/www/ci4/app ...
- Docker 搭建 Keycloak
Docker 搭建 Keycloak 命令 需要创建好数据库,启动容器指定数据库信息 # KEYCLOAK_USER 用户名 # KEYCLOAK_PASSWORD 密码 # DB_ADDR 数据库地 ...
- Jenkins(Extended E-mail Notification)邮箱配置正确但是并没有发送邮件
废话 近期在把之前的接口自动化demo与jenkins集成,昨天发现了邮件配置正确但是没有发送邮件的问题,通过勾选系统设置 - >Extended E-mail Notification -&g ...
- Bytom 储蓄分红 DAPP 开发指南
储蓄分红DAPP 储蓄分红合约简介 储蓄分红合约指的是项目方发起了一个锁仓计划(即储蓄合约和取现合约),用户可以在准备期自由选择锁仓金额参与该计划,等到锁仓到期之后还可以自动获取锁仓的利润.用户可以在 ...
- Flink的状态编程和容错机制(四)
一.状态编程 Flink 内置的很多算子,数据源 source,数据存储 sink 都是有状态的,流中的数据都是 buffer records,会保存一定的元素或者元数据.例如 : ProcessWi ...
- Grazing on the Run 题解
[题目大意] 大致题意就是,你的初始坐标为\(x\),你要去数轴上的\(n\)个点,问你到达所有点的时间总和最小是多少. 直接贪心肯定不行,所以考虑\(DP\) 先把坐标离散(也就是预处理两点距离\( ...
- 2020-04-06:insert语句在mysql里经历了什么?
1.会话状态转换为update 2.激活事物状态由 not_active 变为 active 3.查找定位数据 4.进行乐观插入 记录insert的undo记录记录undo的redo log 入red ...
- C#LeetCode刷题-数学
数学篇 # 题名 刷题 通过率 难度 2 两数相加 29.0% 中等 7 反转整数 C#LeetCode刷题之#7-反转整数(Reverse Integer) 28.6% 简单 8 字符串转整数 ...