从零开始-手写模拟Spring框架 (附教程源码)
首先就是创建一个普通的Java工程,起名TrySpring, 最终项目如下图:
然后在 创建两个包 分别为service、spring。service用来存放bean、配置类等,spring包用来存放我们手撸的spring注解等
在service中创建UserService 类,再创建Test类,在Test类中创建我们的spring容器,此时以配置类的形式去创建容器,在service包中创建AppConfig配置类,在spring包里创建CsApplicationContext类,并提供一个参数与构造方法。
CsApplicationContext类
package com.cs.spring; public class CsApplicationContext { private Class configClass; public CsApplicationContext(Class configClass) { this.configClass = configClass; } }
Test类
package com.cs.service; import com.cs.spring.CsApplicationContext; public class Test { public static void main(String[] args) { //创建spring容器 CsApplicationContext context = new CsApplicationContext(AppConfig.class); } }
配置类APPConfig启动要去扫描路径,所以在spring包中定义ComponentScan注解,并提供属性等
package com.cs.spring; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME)//表示注解生效的时间 @Target(ElementType.TYPE)//指定注解写的位置 public @interface ComponentScan { String value() default "";//指定扫描路径 }
package com.cs.service; import com.cs.spring.ComponentScan; @ComponentScan("com.cs.service")//指定扫描路径 public class AppConfig { }
而UserService类一般会在类上加@Component注解表示把这个类定义为一个bean,所以此时再去spring包下写一个Component注解
package com.cs.spring; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME)//表示注解生效的时间 @Target(ElementType.TYPE)//指定注解写的位置 public @interface Component { String value() default "";//给我们当前定义的bean取一个名字。 }
package com.cs.service; import com.cs.spring.Component; @Component("userService")//给当前bean指定一个名字 public class UserService { }
此时我们就有了一个spring容器了,但是没啥用处,spring容器中有个getBean()方法,所以此时再去CsApplicationContext中创建一个getBean方法
package com.cs.spring; public class CsApplicationContext { private Class configClass; public CsApplicationContext(Class configClass) { this.configClass = configClass; } public Object getBean(String beanName){ return null;//暂时这样处理 } }
此时需要思考下,spring容器里的构造方法和getBean方法需要干什么 ?
我们在外面new一个Spring容器对象,就相当于我们去创建一个spring容器,或者启动一个spring容器,那么在启动spring容器的过程中,spring应该去干什么,他会去干什么?
1.spring扫描底层实现
spring启动首先去扫描,那spring是怎么进行扫描的呢?——通过类上面的@ComponentScan注解指定的路径
通过判断接收到的类,上面有没有ComponentScan注解,
package com.cs.spring; import java.io.File; import java.lang.annotation.Annotation; import java.net.URL; public class CsApplicationContext { private Class configClass; public CsApplicationContext(Class configClass) { this.configClass = configClass; //首先去判断你给我的这个类上面有没有ComponentScan这个注解 if (configClass.isAnnotationPresent(ComponentScan.class)) { ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class); String path = componentScanAnnotation.value();//拿到我们的扫描路径com.cs.service // System.out.println(path); path = path.replace(".", "/");//将包改写成路径形式com/cs/service //通过类加载器的一个getResource方法我们传进去一个相对路径获取对应的 资源 ClassLoader classLoader = CsApplicationContext.class.getClassLoader(); URL resource = classLoader.getResource(path); File file = new File(resource.getFile()); // System.out.println(file); //D:\workspace\EveryDayFuck\TryagainSpring\out\production\TryagainSpring\com\cs\service if (file.isDirectory()) { //拿到文件夹里面所有的内容,所有文件 File[] files = file.listFiles(); //判断查找.class结尾的文件 for (File f : files) { //拿到文件的绝对路径,包含文件名 String fileName = f.getAbsolutePath(); // System.out.println(fileName); String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class")); // System.out.println(className);//com\cs\service\AppConfig //com.cs.service.userSrevice className = className.replace("\\", "."); // System.out.println(className); /* com.cs.service.AppConfig com.cs.service.Test com.cs.service.UserService */ if (fileName.endsWith(".class")) { //此时我们要去判断这个.class结尾的文件是不是一个bean //而判断是不是bean,我们去看看这个类上面有没有@Component注解 //此时用反射来判断是否有@Component注解,要用反射就得拿到Class对象, //通过类加载器的loadClass方法传入类的全限定名到方法里去拿到对应的Class对象 try { Class<?> clazz = classLoader.loadClass(className); if (clazz.isAnnotationPresent(Component.class)) { //存在@Component注解,是Bean } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } } } public Object getBean(String beanName) { return null;//暂时这样处理 } }
2. 模拟BeanDefinition的生成
此时已经扫描到了有哪些bean,此时还不能直接给bean创建出来,因为还要分单例bean,还是多例bean,多例bean是在我们去获取的时候才会去创建,此时看向getBean方法,会根据传给我的beanName去找到对应的是哪个类,但是怎么根据名字去找到对应的类呢?又怎么根据名字判断是单例还是多例?如果是单例从单例池中去拿,如果是多例直接创建一个。
此时再去spring包下创建一个Scope注解
spring是首先会先生成一个BeanDefinition对象,在spring包下创建BeanDefinition类,提供两个属性,类型、作用域
package com.cs.spring; public class BeanDefinition { private Class type;//类的类型 private String scope;//是单例还是多例 public Class getType() { return type; } public void setType(Class type) { this.type = type; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } }
总结:我们在扫描的过程间,会拿到很多的类,然后去加载这个类去判断这个类上面有没有Component注解,如果有就相当于定义了一个bean,那么我就去生成一个BeanDefinition对象,因为这个是空的BeanDefinition对象,那么这个BeanDefinition对象的类型是什么,我们可以先设置下,然后再去判断是单例的还是多例的,可以通过去判断这个类上面有没有@Scope注解,如果有就去看看Scope注解里定义的值是什么,直接设置给BeanDefinition对象,如果没有@Scope注解,就默认是单例。此时就真正生成了一个BeanDefinition对象。——然后去创建一个Map给BeanDefinition对象存进去。
3. 手写模拟getBean方法的底层实现
package com.cs.spring; import java.io.File; import java.lang.annotation.Annotation; import java.net.URL; import java.util.concurrent.ConcurrentHashMap; public class CsApplicationContext { private Class configClass; private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>(); public CsApplicationContext(Class configClass) { this.configClass = configClass; //1.扫描--->BeanDefinition-->beanDefinitionMap。首先去判断你给我的这个类上面有没有ComponentScan这个注解 if (configClass.isAnnotationPresent(ComponentScan.class)) { ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class); String path = componentScanAnnotation.value();//拿到我们的扫描路径com.cs.service // System.out.println(path); path = path.replace(".", "/");//将包改写成路径形式com/cs/service //通过类加载器的一个getResource方法我们传进去一个相对路径获取对应的 资源 ClassLoader classLoader = CsApplicationContext.class.getClassLoader(); URL resource = classLoader.getResource(path); File file = new File(resource.getFile()); // System.out.println(file); //D:\workspace\EveryDayFuck\TryagainSpring\out\production\TryagainSpring\com\cs\service if (file.isDirectory()) { //拿到文件夹里面所有的内容,所有文件 File[] files = file.listFiles(); //判断查找.class结尾的文件 for (File f : files) { //拿到文件的绝对路径,包含文件名 String fileName = f.getAbsolutePath(); // System.out.println(fileName); String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class")); // System.out.println(className);//com\cs\service\AppConfig //com.cs.service.userSrevice className = className.replace("\\", "."); // System.out.println(className); /* com.cs.service.AppConfig com.cs.service.Test com.cs.service.UserService */ if (fileName.endsWith(".class")) { //此时我们要去判断这个.class结尾的文件是不是一个bean //而判断是不是bean,我们去看看这个类上面有没有@Component注解 //此时用反射来判断是否有@Component注解,要用反射就得拿到Class对象, //通过类加载器的loadClass方法传入类的全限定名到方法里去拿到对应的Class对象 try { Class<?> clazz = classLoader.loadClass(className); if (clazz.isAnnotationPresent(Component.class)) { Component component = clazz.getAnnotation(Component.class); String beanName = component.value(); //存在@Component注解,是Bean,去生成BeanDefinition对象 BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setType(clazz); //在去判断这个类上面有没有@Scope注解; if (clazz.isAnnotationPresent(Scope.class)) { //有scope注解 Scope scopeAnnotaion = clazz.getAnnotation(Scope.class); String scope = scopeAnnotaion.value(); beanDefinition.setScope(scope); }else { //没有scope注解就是单例的 beanDefinition.setScope("Singleton"); } beanDefinitionMap.put(beanName,beanDefinition); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } } //2.实例化单例Bean,拿到所有的单例bean对象,判断是否有单例bean,有的话容器启动时候直接创建,存入单例池 for (String beanName : beanDefinitionMap.keySet()) { BeanDefinition beanDefinition = beanDefinitionMap.get(beanName); if (beanDefinition.getScope().equals("Singleton")){ //如果是单例的汲取创建bean Object bean = createBean(beanName, beanDefinition); //通过单例池来保证单例 singletonObjects.put(beanName,bean); } } } private Object createBean(String beanName,BeanDefinition beanDefinition){ return null; } public Object getBean(String beanName) { BeanDefinition beanDefinition = beanDefinitionMap.get(beanName); if (beanDefinition == null){ throw new NullPointerException(); }else { String scope = beanDefinition.getScope(); if ("Singleton".equals(scope)){ //单例,在spring容器启动的时候就给单例bean生成出来 //此时直接从单例池中获取单例bean Object bean = singletonObjects.get(beanName); //某些情况下导致单例池中没有就再去创建 if (bean == null){ Object bean1 = createBean(beanName, beanDefinition); //放入单例池中 singletonObjects.put(beanName,bean1); } return bean;//找到了,直接返回 }else { //多例,每次额外去创建BeanDefinition对象 return createBean(beanName,beanDefinition); } } } }
4. 手写bean的创建流程
下面去考虑怎么去创建bean————creatBean()
在createBean(String beanName,BeanDefinition beandefinition),中有两个参数,创建bean,bean是一个对象我们得知道是哪个类,我们可以通过参数beanDefinition对象的getType方法获得类对象,再利用反射通过构造方法再去newInstance创建对象
private Object createBean(String beanName,BeanDefinition beanDefinition){ Class clazz = beanDefinition.getType(); try { Object instance = clazz.getConstructor().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; }
此时去测试单例,
5. 手写依赖注入
bean的默认初始名
//对Bean的名字进行处理,默认是首字母小写 if (beanName.equals("")){ //首字母小写,如果第一第二个字母大写就不管他 beanName = Introspector.decapitalize(clazz.getSimpleName()); }
看下decapitalize方法的源码
public static String decapitalize(String name) { if (name == null || name.length() == 0) { return name; } if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){ return name; } char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); } }
此时可以不用在@Component后面加bean的名字,默认就是类名首字母小写
去测试一下
去service包下新建一个OrderService类,加上@Component注解
package com.cs.service; import com.cs.spring.Component; @Component public class OrderService { }
要实现依赖注入,此时在UserService类中,新增一个属性OrderService,去注入,此时需要再去新建一个@Autowired注解
package com.cs.spring; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)//表示写在字段上 public @interface Autowired { }
此时就可以UserService中使用@Autowired注解,但此时OrderService是否有值呢?我们去测试一下
package com.cs.service; import com.cs.spring.Autowired; import com.cs.spring.Component; import com.cs.spring.Scope; @Component("userService")//给当前bean指定一个名字 @Scope("Prototype") public class UserService { @Autowired private OrderService orderService; public void test(){ System.out.println(orderService); } }
可以发现,此时依赖还是没有注入的,那么依赖注入是在哪里完成的呢?
应该在创建bean的时候就去给他添加属性,也就是在createBean方法中,此处也就是在模拟bean的生命周期,bean的创建,第一个步骤就是先实例化得到一个对象,第二个步骤就是给对象进行依赖注入,
一个对象里面,或者其对应的类里面有很多的属性,我们只需要去给那些加了@Autowired的属性赋值,
此时OrderService就有值了,
6.BeanName 的回调
可能不知道某个bean的名字,或者名字呗其他机制给更改了,那么怎么能最终知道这个bean的名字呢?其实这个bean的名字是由spring生成出来的,最终需要spring来告诉我们当前bean的名字是什么,这就需要一种回调,Aware回调,这里需要一个接口
package com.cs.spring; public interface BeanNameAware { public void setBeanName(String beanName); }
我们的bean要去实现这个接口
package com.cs.service; import com.cs.spring.Autowired; import com.cs.spring.BeanNameAware; import com.cs.spring.Component; import com.cs.spring.Scope; @Component("userService")//给当前bean指定一个名字 public class UserService implements BeanNameAware { @Autowired private OrderService orderService; private String beanName; public void test(){ System.out.println(orderService); } @Override public void setBeanName(String beanName) { this.beanName = beanName; } }
那么spring在什么时候来调用这个setBeanName方法呢?也就是我们刚刚的依赖注入完成后,spring会去判断 之前生成的对象是否实现了BeanNameAware这个接口,如果实现了 就给对象强制转换成这个接口类型,然后直接去调用你的方法
还有更多的实现,建议大家可以查看源码了。
如:模拟Spring初始化机制实现、BeanPostProcessor模拟实现、AOP模拟实现等
trySpring源码:
原文链接: https://www.yukx.com/javadev/article/details/2390.html 优科学习网从零开始-手写模拟Spring框架 (附教程源码)
-
什么是高并发?高并发(HighConcurrency)是一种系统运行过程中遇到的一种“短时间内遇到大量操作请求”的情况,主要发生在web系统集中大量访问收到大量请求(例如:12306的抢票情况;天猫双十一活动)。该情况的发生会导致系统在这段时间内执行大量操作,例如对资源的请求,数据库的操作等。高并发
-
首先就是创建一个普通的Java工程,起名TrySpring,最终项目如下图:然后在创建两个包分别为service、spring。service用来存放bean、配置类等,spring包用来存放我们手撸的spring注解等在service中创建UserService类,再创建Test类,在Test类中
-
AQS(AbstractQueuedSynchronizer)是一个抽象的队列同步器,通过维护一个共享资源状态(VolatileIntState)和一个先进先出(FIFO)的线程等待队列来实现一个多线程访问共享资源的同步框架。一、AQS原理 AQS为每个共享资源都设置一个共享资源锁
-
word-break:break-all举例一般情况下,元素拥有默认的white-space:normal(自动换行,PS:不换行是white-space:nowrap),当录入的文字超过定义的宽度后会自动换行,但当录入的数据是一堆没有空格的字符或字母或数字(常规数据应该不会有吧,但有些测试人员是会
-
Bigdecimal的初始化这里对比了两种形式,第一种直接value写数字的值,第二种用string来表示 BigDecimal num1 = new BigDecimal(0.005); BigDecimal num2 = new BigDecimal(1000000
-
基本概念定义 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。双亲委派机制双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载