手写模拟Spring框架核心逻辑
了解Spring框架工作大概流程
模拟Spring框架核心代码并不是实现真正的Spring核心源码,而是为了后续看源码进行的一个铺垫,同时我也相信在以后的某个时间段的面试中会跟面试官扯到这个犊子 ,主要还是通过手写一遍了解Spring工作的大概流程,对于Spring不单单停止在应用上,更应该往深里学,废话到这,下面正式开始。。。
我们现在用的框架大部分都是SpringCloud或者SpringBoot去启动的,但是他们的启动都是会间接使用到ClassPathXmlApplicationContext去扫描xml配置,所以我们得了解Spring的启动类,如下图:
或者说使用注解的方式,AnnotationConfigApplicationContext(这个启动类会自动帮我们创建对象,使用了他,就不用我们自己去new一个对象了),可以直接理解为他就是一个容器,如下图:
⚠️:这里再普及一点小知识,所有的Bean都是对象,但是对象不一定都是Bean,第二个就是为什么使用Spring,原因就是回到JDBC + Servlet版本的时候,项目中大量的创建、使用对象,造成依赖性特别强,不好维护,Spring就相当于项目的管理者,可以帮我们创建对象、给属性去赋值等等这些吃力不讨好的苦力活…
创建AnnotationConfigApplicationContext启动类
这里我们首先创建一个项目,不需要引入任何依赖
定义Component注入注解,然后创建AnnotationConfigApplicationContext启动类,
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default "";
}
注入在我们的实体类上,但是现在是没有功能的
创建AnnotationConfigApplicationContext实体类,并提供一个getBean(String beanName)方法,然后在测试类里面去获取我们的bean
写到这里,空壳子就出来了,首先,我们知道AnnotationConfigApplicationContext是启动类,会帮我们去创建bean,所以我们要写一个createBean()方法,但是在创建之前要先扫描,所以我们这里要定义一个扫描的注解,去扫描需要创建的对象
- @ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
String value() default "";
}
- AppConfig,这里需要注意一个点,并非去扫描所有在com.csw.spring.service.impl下面的类,而是扫描加了@component注解的类,这里还有一个注意点就是AnnotationConfigApplicationContext并非会创建所有加了@component,而是会去加载非懒加载的单例bean
@ComponentScan("com.csw.spring.service.impl")
public class AppConfig {
}
如下图:
⚠️课堂小知识:什么是单例bean?什么是原型bean?
-
- 单例bean又分为懒加载和非懒加载,简单来说懒加载就是每次启动了之后不会马上去创建,而是会在调用了getBean()方法之后才会去创建,非懒加载就是启动了之后去创建,创建单例bean最直接的方式就是在类上加@Service或者@Component
-
- prototypeScope,即原型bean,每次请求时都会创建新的bean实例直接使用。创建原型Bean,就是在@Service或者@Component的基础上加个@Scope(“prototype”)。
- prototypeScope,即原型bean,每次请求时都会创建新的bean实例直接使用。创建原型Bean,就是在@Service或者@Component的基础上加个@Scope(“prototype”)。
在做完了以上的步骤,AnnotationConfigApplicationContext启动会去判断是不是加了@component注解和是单例bean还是原型bean,非懒加载单例bean还是懒加载单例bean,这时候我们就需要一个@Lazy的注解去区分
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Lazy {
}
AnnotationConfigApplicationContext扫描的步骤
package com.csw.spring.context;
import com.csw.spring.anno.ComponentScan;
import java.lang.annotation.Annotation;
/**
* @author csw
* @date 2021/12/19 01:12:45
* @version 1.0
*/
public class CswApplicationContext {
private Class configClass;
public CswApplicationContext(Class configClass) {
this.configClass = configClass;
//判断扫描是不是存在
if (configClass.isAnnotationPresent(ComponentScan.class)) {
//获取ComponentScan的信息
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();//获取扫描路径
System.out.println(path);
}
}
public Object getBean(String beanName) {
return null;
}
}
启动测试类后得到,如下图:
但是我们这里真正需要获取的路径是编译后的class文件,如下图:
课堂小知识⚠️:类加载是在本地文件夹的绝对路径上加载的
根据以上我们可以通过getClassLoader方法获取类加载后的文件路径
package com.csw.spring.context;
import com.csw.spring.anno.Component;
import com.csw.spring.anno.ComponentScan;
import com.csw.spring.anno.Lazy;
import com.csw.spring.anno.Scope;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
/**
* @author csw
* @date 2021/12/19 01:12:45
* @version 1.0
*/
public class CswApplicationContext {
private Class configClass;
public CswApplicationContext(Class configClass) {
this.configClass = configClass;
//判断扫描是不是存在
if (configClass.isAnnotationPresent(ComponentScan.class)) {
//获取ComponentScan的信息
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();//获取扫描路径,com.csw.spring.service.impl
//替换com.csw.spring.service.impl为com/csw/spring/service/impl
path = path.replace(".","/");
//获取类加载后文件路径
ClassLoader classLoader = CswApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path);
File file = new File(resource.getFile());
//对获取对文件路径进行循环遍历
for (File f : file.listFiles()) {
//获取path路径:/Users/shengwencheng/Desktop/cloud/SpringCore/spring-core/Spring/target/classes/com/csw/spring/service/impl/UserServiceImpl.class
String s = f.getAbsolutePath();
//只要class结尾的文件
if(s.endsWith(".class")) {
//截取com到.class之间到路径
s = s.substring(s.indexOf("com"),s.indexOf(".class"));
//把转义替换成.
s = s.replace("\\",".");
try {
Class clazz = classLoader.loadClass(s);
//判断类上是否有Component注解
if (clazz.isAnnotationPresent(Component.class)) {
Component componentAnnotation = (Component) clazz.getAnnotation(Component.class);
String beanName = componentAnnotation.value()