SpringBoot项目创建一个helloworld的web项目非常快速且方便,然后内部的流程实际上非常复杂。很多像我一样的小白,想通过阅读源码方式来了解SpringBoot的运行流程和机制,会发现根本无从入手!!,想要先了解一个点,却发现一个点涉及的类和接口实在太多,难以梳理这个流程。
本文主要是对SpringBoot的其中一个核心功能进行探索:容器加载和获取Bean的基本方式
。需要对SpringBoot稍微有一点基础理论,需要知道容器
的概念,Bean
的概念等。
全文主要内容有以下几个方面:
ApplicationContext
来模拟SpringBoot的ApplicationContext
,以及它的实现类WhutApplicationContext
完成Bean的加载和获取。App
类当做SpringBoot的启动类,UserService
类并添加上相应注解,来测试是否能成功注入到容器中。由于本文的很多类和注解跟SpringBoot框架自带的一样,所以在使用的时候注意一下,不要引用成框架的类了,如@Component,@ComponentScan、@Scope、BeanDefinition!!!!!!!!
`
本文内容主要参考自:Spring底层之bean底层原理,spring容器底层原理
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
@Component
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default ""; // 给当前Bean起一个名字
}
@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
String value() default ""; // 扫描的路径
}
@Scope
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value() default ""; // 设置单例 或者 原型模式
}
package com.whut.spring.pojo;
/**
* bean的定义
*/
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;
}
}
ApplicationContext 接口
public interface ApplicationContext {
Object getBean(String beanName);
Object createBean(String beanName, BeanDefinition beanDefinition);
}
@ComponentScan("com.whut.spring.service")
public class AppConfig {
}
UserService
@Component("userService")
@Scope("prototype")
public class UserService {
}
StudentService
@Component
public class StudentService {
}
在这个启动类中我们
/**
* 模拟Spring的启动类
*
*/
public class App
{
public static void main( String[] args )
{
ApplicationContext context = new WhutApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
System.out.println(userService);
System.out.println();
StudentService studentService = (StudentService) context.getBean("studentService");
System.out.println(studentService);
}
}
至此本文项目的框架已经搭建完毕,只剩下最后一件也是最为重要的一件事,就是ApplicationContext
的实现类。
在实现类中,主要有以下3个方法。
public class WhutApplicationContext implements ApplicationContext{
/*
静态常量
*/
private static final String SINGLETON = "singleton";
/*
存放Bean的Map
*/
private static ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>();
/*
存放Bean的定义信息的Map
*/
private static ConcurrentHashMap<String, BeanDefinition> beanDefinitionHashMap = new ConcurrentHashMap<>();
/**
* 容器构造方法
* @param configClass 配置类的Class
*/
public WhutApplicationContext(Class<?> configClass) {
}
/**
* 获取Bean
* @param beanName
* @return Bean的实例
*/
@Override
public Object getBean(String beanName) {
}
/**
* 创建一个 bean (反转控制法)
* @param beanName
* @param beanDefinition
* @return
*/
@Override
public Object createBean(String beanName,BeanDefinition beanDefinition){
}
首先最为重要的就是容器的构造方法,在这个方法中,我们将完成Bean的扫描
和Bean的加载
。通过传入一个带有@ComponentScan
注解的类的class来确认扫描包的路径,从而完成Bean的扫描与加载。主要步骤如下:
/*
1. 传入的 configClass 类中有我们需要加入spring容器包的地址
2. 通过这个地址,获取到该地址下的所有文件
3. 遍历这些文件,并筛选出.class文件
4. 通过这些.class文件的地址获取到他的类(控制反转)
5. 判断类上面是否有@Component注解,并解析其名字,如名字为空,者以该类类名首字母小写设置为该类的名称。
6. 创建一个bean的定义类(beanDefinition),用于包容放入容器的对象和它的一些配置(如:是否是单例,是否为懒加载),将对象和它的配置放入该类中。
7. 创建一个总的bean容器,以对象名(或者Component注解中的value属性)作为key,存储beanDefinition。
*/
具体代码如下:
/**
* 容器构造方法
* @param configClass 配置类的Class
*/
public WhutApplicationContext(Class<?> configClass) {
/*
1. 传入的 configClass 类中有我们需要加入spring容器包的地址
2. 通过这个地址,获取到该地址下的所有文件
3. 遍历这些文件,并筛选出.class文件
4. 通过这些.class文件的地址获取到他的类(控制反转)
5. 判断类上面是否有@Component注解,并解析其名字,如名字为空,者以该类类名首字母小写设置为该类的名称。
6. 创建一个bean的定义类(beanDefinition),用于包容放入容器的对象和它的一些配置(如:是否是单例,是否为懒加载),将对象和它的配置放入该类中。
7. 创建一个总的bean容器,以对象名(或者Component注解中的value属性)作为key,存储beanDefinition。
*/
if ((configClass).isAnnotationPresent(ComponentScan.class)) {
// 1. 传入的 configClass 类中有我们需要加入spring容器包的地址
ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class);
String path = componentScan.value();
path = path.replace(".", "/"); // 转为目录格式
ClassLoader classLoader = WhutApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path); // 获取绝对路径
assert resource != null;
File file = new File(resource.getFile());
System.out.println("扫描到的资源路径为:" + file.getAbsolutePath());
if (file.isDirectory()) {
/*
2. 通过这个地址,获取到该地址下的所有文件
*/
File[] files = file.listFiles();
assert files == null;
for (File f : files) {
// 文件的绝对路径
String tempPath = f.getAbsolutePath();
/*
3. 遍历这些文件,并筛选出.class文件
*/
if (tempPath.endsWith(".class")) {
// 获取 com/whut/spring/service/UserService.java
String className = tempPath.substring(tempPath.indexOf("com"), tempPath.indexOf(".class"));
// 转成 com.whut.spring.service.UserService
className = className.replace("\\",".");
/*
4. 判断类上面是否有@Component注解,并解析其名字,如名字为空,者以该类类名首字母小写设置为该类的名称。
*/
try {
Class<?> aClass = classLoader.loadClass(className);
if (aClass.isAnnotationPresent(Component.class)) {
System.out.println("扫描到Component ==> " + className);
Component component = aClass.getAnnotation(Component.class);
/*
5. 判断类上面是否有@Component注解,并解析其名字,如名字为空,者以该类类名首字母小写设置为该类的名称。
*/
String beanName = component.value();
if ("".equals(beanName)) {
beanName = Introspector.decapitalize(aClass.getSimpleName());
}
/*
6. 创建一个bean的定义类(beanDefinition),用于包容放入容器的对象和它的一些配置(如:是否是单例,是否为懒加载),将对象和它的配置放入该类中。
*/
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(aClass);
if (aClass.isAnnotationPresent(Scope.class)) {
Scope scope = aClass.getAnnotation(Scope.class);
beanDefinition.setScope(scope.value());
}else{
beanDefinition.setScope(SINGLETON);
}
/*
7. 创建一个总的bean容器,以对象名(或者Component注解中的value属性)作为key,存储beanDefinition。
*/
beanDefinitionHashMap.put(beanName, beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
/*
8. 实例化单例模式的Bean
*/
//实例化单例bean
for (String beanName : beanDefinitionHashMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")) {// 判断是否为单例
Object bean = createBean(beanName,beanDefinition);
assert bean != null;
beanMap.put(beanName,bean);
}
}
}
}
在这个方法中,我们需要判断Bean是否为单例,如果是就直接返回(保证只有一个),如果不是,就新创建一个。
/**
* 获取Bean
* @param beanName
* @return Bean的实例
*/
@Override
public Object getBean(String beanName) {
BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
if (beanDefinition == null) {
throw new NullPointerException();
}else{
String scope = beanDefinition.getScope();
if (scope.equals(SINGLETON)) {//单例
System.out.println("获取到一个 单例模式的Bean ==> " + beanName);
Object bean = beanMap.get(beanName);
if (bean == null){
Object tempBean = createBean(beanName, beanDefinition);
if (tempBean == null) throw new NullPointerException("创建Bean失败...");
beanMap.put(beanName, tempBean);
}
return bean;
}else {//多例
System.out.println("创建了一个非单例模式的Bean ==> " + beanName);
return createBean(beanName,beanDefinition);
}
}
}
/**
* 创建一个 bean (反转控制法)
* @param beanName
* @param beanDefinition
* @return
*/
@Override
public Object createBean(String beanName,BeanDefinition beanDefinition){
Class clazz = beanDefinition.getType();
//通过类的构造器 生成一个类的对象
try {
return clazz.getConstructor().newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
如果没有书写错误,支持,我们可以启动测试类了。启动后,控制台会打印如下信息:
扫描到Component ==> com.whut.spring.service.StudentService
扫描到Component ==> com.whut.spring.service.UserService
创建了一个非单例模式的Bean ==> userService
com.whut.spring.service.UserService@4ec6a292
获取到一个 单例模式的Bean ==> studentService
com.whut.spring.service.StudentService@1b40d5f0