哈喽大家好,最近闲来无事,又不想去研究业务逻辑,所以就开始尝试写一下简易spring框架的实现。由于刚工作一年且因为是在学习,所以如果有写的不对的地方,还希望各位大神给一点指导。本文的主要意图呢,就是为了我学习不会间断
先放出spring的官网供大家查阅
Spring | Home
一、spring简介
这里我相信大家应该对spring都很了解了,就简单的一笔带过,不写那么多啰嗦的话题了。
核心就是一个控制反转(ioc)和面向切面编程(AOP)
主要功能呢就是:
1:方便解耦,简化开发;
2:方便的对程序进行拦截、运行、监控等功能;
3:可以对事务声明;
4:属于一个万能的框架,跟很多框架都是百搭;
由于本文只描述如何实现简易spring框架,所以不再阐述spring框架的搭建以及配置。
二、如何实现
一、准备工作
我们先创建一个基本上什么都没有的maven工程,大致如下
如果不会创建的小伙伴可以移步这位大佬的文档
(2条消息) Idea中创建maven项目(超详细)_Yan Yang的博客-CSDN博客_idea创建maven项目
建好我们的文件夹以及我们的容器类,还有测试类,这里先不用去管我的别的文件夹,只要去看我圈出来的类就可以了。这里是建了一个ZfApplicationContext去作为我们框架的容器类,以及Test测试类。
在spring框架中我们知道在创建容器类的时候要传递一个配置类作为参数,就是我们的AppConfig,
已经在调用getBean方法时传递的beanName作为参数,这里创建了一个空的业务层去模拟我们平时的业务层,这里的AppConfig类目前也是空的状态,里面并没有加内容。到这里我们的准备工作大致是完成了。
容器类中的代码,在提供一个构造方法
public class ZfApplicationContext {
//配置类
private Class configClass;
//创建容器时传入配置类
public ZfApplicationContext(Class configClass) {
this.configClass = configClass;
}
}
二、spring的启动和扫描逻辑的实现
我们知道在spring中实例化容器类的时候,传入的配置类可以通过**@ComponentScan**注解将我们要扫描的目录传递进去,这个是怎么实现的呢?
我们也来定义一个简单的**@ComponentScan**注解,这里我就不给它设置默认值了,我们后续可以手动去给它添加路径
@Retention(RetentionPolicy.RUNTIME) 、//意为运行时注解
@Target(ElementType.TYPE) //用于描述类、接口(包括注解类型) 或enum声明
public @interface ComponentScan {
String value();
}
当我们可以通过**@ComponentScan获得要扫描的路径,在spring中还有一个@Component**注解去标记那些类是需要被创建为bean对象的,所以我们还需要一个@Component注解,同样我们只是实现简单的spring框架,所以就不在去实现该注解是如何去实现给类设置默认值,我们手动去添加(主要是懒-.-!)。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value();
}
这时候我们可以将**@ComponentScan**注解写在我们的配置类上,并设置值,即为我们要扫描的目录
@ComponentScan("com.zf.service")
public class AppConfig {
}
在spring中还需要一个注解去设置我们容器中的作用域,就是我们的@Scope注解,
** singleton单例模式 -- 全局有且仅有一个实例
prototype原型模式 -- 每次获取Bean的时候会有一个新的实例
request -- request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
session -- session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
globalsession -- global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义**
所以我们应该也要在容器类****ZfApplicationContext中添加俩个属性分别为
//单例池 ,主要存单例对象 private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); //存储我们一开始扫描到的所有的bean的定义 private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
分别去存储单例对象,和我们其他的bean,这里spring中用到了ConcurrentHashMap,其实我们自己去实现简易的容器的话也可以用HashMap,这里我们key存储beanName,value就是存储我们定义好的BeanDefiniyion对象,方便我们接下来去实现getBean方法。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value();
}
我们在容器解析到bean对象时需要一个类去“存储“这个bean,这里的存储并不是真正意义上的存储,而是存储这个bean对象的类型和它的作用域。即为bean的定义,BeanDefinition,@Component,@Scope等注解就是在定义一个bean
public class BeanDefinition {
//当前某一个bean的类型
private Class clazz;
//当前bean的作用域
private String scope;
public BeanDefinition(Class clazz, String scope) {
this.clazz = clazz;
this.scope = scope;
}
public BeanDefinition() {
}
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}
接下来就是我们如何去在ZfApplicationContext中的构造方法里实现里去解析我们的配置类
我们需要去实现一个方法为scan(),这里的一段代码我是都有做好注解的。
private void scan(Class configClass) {
//解析配置类
//通过componentScan注解---->扫描路径 ---->扫描 ---->开始解析类
//获取配置类上的自定义ComponentScan注解
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
//获取扫描路径
String path = componentScanAnnotation.value();
//测试能否拿到路径
// System.out.println(path); 这里打印的com.zf.service为包名,我们需要将“.”替换为“/”,意为将其替换为路径
path = path.replace(".","/");
//扫描 更据path包名,得到包下面的路径
//三种类加载器
//Bootstrap ----> jre/lib
//Ext ------> jre/ext/lib
//App -------> classpath ---->指的是当前应用对应的那个路径
//例如: ClassLoader classLoader = ZfApplicationContext.class.getClassLoader();
//classLoader.getResource("com/zf/service"); 意思为拿到ZfApplicationContext这个app下的路径为"com/zf/service"的资源,这个相对路径就是当前这个classLoader所对应的路径
//这里的Resource即可以是某个文件也可以是某个目录
ClassLoader classLoader = ZfApplicationContext.class.getClassLoader();
//URL统一资源标识符(Uniform Resource Identifier ,URL)是采用一种特定语法标识一个资源的字符串。所标识的资源可能是服务器上的一个文件。
//这里就是拿到当前指定扫描路径下的文件目录
URL resource = classLoader.getResource(path);
//这里的resource是"com/zf/service"下目录的file对象,
File file = new File(resource.getFile());
//判断是否是一个目录
if (file.isDirectory()) {
//拿出这个目录下的所有文件
File[] files = file.listFiles();
//循环扫描到的类
for (File f : files) {
//获取该文件的绝对路径并截取成全限定名
//这里是将C:\Users\Administrator\IdeaProjects\spring2\target\classes\com\zf\service\UserService.class 截取成并替换成com.zf.service.UserService
String fileName = f.getAbsolutePath();
//当然要先判断这个类是否是类文件
if (fileName.endsWith(".class")) {
String className = fileName.substring(fileName.indexOf("com"),fileName.indexOf(".class"));
//拿到这个类的全限定名
className = className.replace("\\", ".");
//ClassLoader的加载机制,ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了
//找到当前包下某个类
try {
Class<?> clazz = classLoader.loadClass(className);
//判断该类是否有Component注解
if (clazz.isAnnotationPresent(Component.class)) {
//有的话就表示当前这个类是一个Bean对象
// 解析类 ,判断当前bean是单例bean,还是原型(prototype)的bean
//TODO BeanDefinition :bean的定义 (@Component,@Scope等注解就是在定义一个bean)
//解析某一个类,生成某一个BeanDefinition对象
//先通过Component注解取出bean的名字
Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();
//创建一个BeanDefinition对象
BeanDefinition beanDefinition = new BeanDefinition();
//将bean对象的类型设置进去
beanDefinition.setClazz(clazz);
//查看是否设置一些作用域
if (clazz.isAnnotationPresent(Scope.class)) {
//如果存在就拿出作用域名称设置进BeanDefinition对象
Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotation.value());
} else {
//如果没有就表明这个bean是默认单例的
beanDefinition.setScope("singleton");
}
//将扫描到的bean的定义存进去
beanDefinitionMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
启动时扫描完得到所有的beanDefinition对象,将beanDefinitionMap里的单例bean全部创建好存入我们的单例池,我们的构造方法就会变成如下
public ZfApplicationContext(Class configClass) {
this.configClass = configClass;
//通过componentScan注解---->扫描路径 ---->扫描 ---->开始解析成BeanDefinition------>放入beanDefinitionMap中
scan(configClass);
//启动时扫描完得到所有的beanDefinition,将beanDefinitionMap里的单例bean全部创建好存入单例池
for (Map.Entry<String,BeanDefinition> entry : beanDefinitionMap.entrySet()) {
String beanName = entry.getKey();
BeanDefinition beanDefinition = entry.getValue();
//遍历所有的bean,拿到单例的bean并创建
if (beanDefinition.getScope().equals("singleton")) {
//创建单例bean
Object bean = createBean(beanDefinition);
//放入单例池
singletonObjects.put(beanName,bean);
}
}
}
//创建bean的方法,根据bean的定义BeanDefinition,去创建一个bean
public Object createBean(BeanDefinition beanDefinition) {
//通过传入的BeanDefinition对象获取bean的类型,通过反射直接构造一个实例对象
try {
Class clazz = beanDefinition.getClazz();
Object instance = clazz.getDeclaredConstructor().newInstance();
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
然后我们的getBeanName方法就好实现啦。直接上代码吧
public Object getBean(String beanName) {
//判断传入的beanName在spring容器中是否存在,就是是否存在beanDefinitionMap中
if (beanDefinitionMap.containsKey(beanName)) {
//如果存在判断这个bean对象是否是一个单例的如果是,那就从单例池里面返回一个对象,然后返回这个bean对象
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")) {
Object o = singletonObjects.get(beanName);
return o;
} else {
//如果是原型作用域prototype,调用createBean方法直接创建返回bean对象
Object bean = createBean(beanDefinition);
return bean;
}
}else {
//抛出异常,不存在这个bean
}
return null;
}
然后我们来看一下测试类;
🆗,今天就到此为止啦!欢迎大家讨论,在此要感谢一位叫周瑜的架构老师!有不对的地方欢迎指出,好让我去及时修改,谢谢!
版权归原作者 幕幕幕幕幕幕景~ 所有, 如有侵权,请联系我们删除。