Spring框架核心功能手写实现
Posted 十八岁讨厌编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring框架核心功能手写实现相关的知识,希望对你有一定的参考价值。
文章目录
概要
- 手写Spring启动以及扫描流程
- 手写getBean流程
- 手写Bean生命周期流程
- 手写依赖注入流程
- 手写BeanPostProcessor机制
- 手写Aop机制
Spring启动以及扫描流程实现
我们平时都是使用这两种方法获得spring容器,上面的是通过加载类路径上的配置文件来获得容器。下面的方式和上面的原理相同只不过是通过注解的形式去实现,我们传入的也是一个配置类的class文件,我们可以把这个文件类比成第一种方法中的xml文件,然后这个xml文件里的一个个标签都变成了注解。
基础环境搭建
首先搭建好基础环境:
我们的测试类:
public class MySpringTest
public static void main(String[] args)
MyApplicationContext applicationContext = new MyApplicationContext(AppConfig.class);
Object bean = applicationContext.getBean("");
System.out.println(bean);
我们的容器类MyApplicationContext :
public class MyApplicationContext
private Class configClass;
public MyApplicationContext(Class configClass)
this.configClass = configClass;
public Object getBean(String beanName)
return null;
我们原来在编写Spring的配置文件的时候会使用一个注解@ComponentScan,来定义一个扫描路径。所以我们这里也定义了一个。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan
String value();
-
Retention指定该注解的生命周期,设置成RUNTIME便于反射获取
-
Target指定注解的使用位置,设置为TYPE表示能在类上使用
我们的AppConfig;
@ComponentScan("com.zyb.service")
public class AppConfig
根据扫描包我们再创建一个业务层类UseService,这个UseService我们一般会使用@Component注解进行标记,这里我们也是如此:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component
String value();
@Component("userService")
public class UserService
扫描逻辑实现
我们的思路就是;
- 解析配置类
- 拿到ComponentScan注解
- 得到扫描路径
- 进行扫描
首先我们可以通过如下代码拿到AppConfig中ComponentScan的内容:
public MyApplicationContext(Class configClass)
this.configClass = configClass;
//解析配置类
//ComponentScan注解 --》 扫描路径 --》 扫描
ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
System.out.println(path);
我们测试一下:
拿到路径之后我们就可以开始扫描了。而扫描的目的并不是包下的所有类,而是那些带有@Component注解的类,而Spring会将他们的对象当作Spring中的一个bean。
这里我们扫描的思路如下:
- 通过类加载器,得到类路径上的class文件
- 对文件进行筛选
- 是否以class结尾(判断是否为class文件)
- 对class文件名进行处理
- 替换
\\
为.
- 截取全限定名
- 替换
- 然后将类加载进jvm虚拟机
- 判断运行时类是否有@Component注解,如果有则进行相关的创建bean对象操作
代码如下:
public MyApplicationContext(Class configClass)
this.configClass = configClass;
//解析配置类
//ComponentScan注解 --》 扫描路径 --》 扫描
ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
String searchPath = path.replace(".", "/");
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(searchPath);
File file = new File(resource.getFile());
if (file.isDirectory())
//首先筛选末尾是.class文件的
File[] files = file.listFiles();
for (File classFile: files)
String absolutePath = classFile.getAbsolutePath();
if (absolutePath.endsWith(".class"))
//拼接全限定名
String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
fullyQualifiedClassName = fullyQualifiedClassName.replace("\\\\", ".");
//使用app类加载器加载这个类
Class<?> aClass = null;
try
aClass = classLoader.loadClass(fullyQualifiedClassName);
if (aClass.isAnnotationPresent(Component.class))
//然后加载bean
catch (ClassNotFoundException e)
e.printStackTrace();
注意;
- classLoader.loadClass接收的是类的全限定名
当我们确认当前类有@Component注解的时候并不是急着去给其创建bean,我们在使用spring的时候是可以决定该bean是否为单例的。我们在这里还是创建一个同名注解@Scope:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope
String value() default "single";
默认是single单例的,如果是原型bean则传入prototype。
而我们实现单例bean是通过一个Map单例池,不考虑懒加载。
现在我们的大致思路就是在通过@Scope注解确定bean是单例模式还是原型模式之后,在进行相应的bean的创建,但是这里我们考虑到一个问题,我们在getBean的时候如果每次都去解析这个类获得@Scope是很麻烦的。所以我们在这里引入一个新的概念BeanDefinition。
BeanDefinition中包含了当前bean的所有定义,例如bean的类型、作用域、是否懒加载等等。注意bean的name不在BeanDefinition中:
public class BeanDefinition
private Class beanClass;
private String scope;
public Class getBeanClass()
return beanClass;
public void setBeanClass(Class beanClass)
this.beanClass = beanClass;
public String getScope()
return scope;
public void setScope(String scope)
this.scope = scope;
在Spring中每扫描到一个bean都会对其进行解析然后生成各自的BeanDefinition实例,随后将这个BeanDefinition对象放在map中,key为bean的name,BeanDefinition对象作为value。以后当我们要获取一个bean时先会去map中查看,如果查找不到bean的BeanDefinition才会去解析类,以此来减少解析类的次数提高效率。
接下来我们完善一下代码,思路如下:
- 判断运行时类是否有Component注解
- 如果有,再获取该类的@Scope
- 如果没有注解则为默认的单例模式
- 如果有此注解则为原型模式
- 然后再把对应的模式添加到BeanDefinition
- 将此BeanDefinition和对应的bean name添加到beanDefinitionMap中
getBean方法思路:
- 判断要获取的bean的BeanDefinition是否存在于beanDefinitionMap
- 如果不存在,说明容器中不存在该bean则直接报错
- 如果存在则在BeanDefinition中拿到该bean的scope,根据scope再去具体的创建bean
代码如下:
public class MyApplicationContext
private Class configClass;
private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
private HashMap<String,BeanDefinition> beanDefinitionHashMap = new HashMap<>();
public MyApplicationContext(Class configClass)
this.configClass = configClass;
//解析配置类
//ComponentScan注解 --》 扫描路径 --》 扫描
ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
String searchPath = path.replace(".", "/");
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(searchPath);
File file = new File(resource.getFile());
if (file.isDirectory())
//首先筛选末尾是.class文件的
File[] files = file.listFiles();
for (File classFile: files)
String absolutePath = classFile.getAbsolutePath();
if (absolutePath.endsWith(".class"))
//拼接全限定名
System.out.println(classFile);
String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
fullyQualifiedClassName = fullyQualifiedClassName.replace("\\\\", ".");
//使用app类加载器加载这个类
Class<?> aClass = null;
try
aClass = classLoader.loadClass(fullyQualifiedClassName);
if (aClass.isAnnotationPresent(Component.class))
BeanDefinition beanDefinition = new BeanDefinition();
if (aClass.isAnnotationPresent(Scope.class))
Scope scopeAnnotation = aClass.getAnnotation(Scope.class);
String scope = scopeAnnotation.value();
beanDefinition.setScope(scope);
else
beanDefinition.setScope("single");
Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();
beanDefinitionHashMap.put(beanName,beanDefinition);
catch (ClassNotFoundException e)
e.printStackTrace();
public Object getBean(String beanName)
//首先判断是否存在该bean的BeanDefinition
if (beanDefinitionHashMap.containsKey(beanName))
BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
if (beanDefinition.getScope().equals("single"))
//单例模式创建bean
else
//原型模式创建bean
else
throw new NullPointerException("不存在该bean");
最后我们可以把扫描这部分逻辑提取出来重新建立一个scan方法:
public class MyApplicationContext
private Class configClass;
private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
private HashMap<String,BeanDefinition> beanDefinitionHashMap = new HashMap<>();
public MyApplicationContext(Class configClass)
this.configClass = configClass;
scan(configClass);
private void scan(Class configClass)
//解析配置类
//ComponentScan注解 --》 扫描路径 --》 扫描
ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
String searchPath = path.replace(".", "/");
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(searchPath);
File file = new File(resource.getFile());
if (file.isDirectory())
//首先筛选末尾是.class文件的
File[] files = file.listFiles();
for (File classFile: files)
String absolutePath = classFile.getAbsolutePath();
if (absolutePath.endsWith(".class"))
//拼接全限定名
System.out.println(classFile);
String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
fullyQualifiedClassName = fullyQualifiedClassName.replace("\\\\", ".");
//使用app类加载器加载这个类
Class<?> aClass = null;
try
aClass = classLoader.loadClass(fullyQualifiedClassName);
if (aClass.isAnnotationPresent(Component.class))
BeanDefinition beanDefinition = new BeanDefinition();
if (aClass.isAnnotationPresent(Scope.class))
Scope scopeAnnotation = aClass.getAnnotation(Scope.class);
String scope = scopeAnnotation.value();
beanDefinition.setScope(scope);
else
beanDefinition.setScope("single");
Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();
beanDefinitionHashMap.put(beanName,beanDefinition);
catch (ClassNotFoundException e)
e.printStackTrace();
public Object getBean(String beanName)
//首先判断是否存在该bean的BeanDefinition
if (beanDefinitionHashMap.containsKey(beanName))
BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
if (beanDefinition.getScope().equals("single"))
//单例模式创建bean
else
//原型模式创建bean
else
throw new NullPointerException("不存在该bean");
bean创建的简单实现
spring容器启动之后,先进行扫描步骤,而这个扫描的主要作用就是得到BeanDefinition,这样我们就有了BeanDefinitionMap,然后我们就可以根据BeanDefinitionMap去创建单例池singleBeanHashMap为我们的getBean方法提供支持。
在getBean方法中:
- 如果是单例模式的bean就直接在singleBeanHashMap中去拿
- 如果是原型模式的bean就直接使用creatBean方法创建bean
我们这里的creatBean方法就是用来创建bean的方法,我们这里暂时只对此方法进行一个简单的实现。
整体代码如下:
public class MyApplicationContext
private Class configClass;
private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
private HashMap<String,BeanDefinition> beanDefinitionHashMap = new HashMap<>();
public MyApplicationContext(Class configClass)
this.configClass = configClass;
scan(configClass);
//scan之后就得到了beanDefinitionHashMap,然后我们根据此来构建singleBeanHashMap
for (String beanName:beanDefinitionHashMap.keySet())
从零开始手写 spring ioc 框架,深入学习 spring 源码
Spring我抄袭了Spring,手写一套MySpring框架。。。