语重心长的话
因为是刚刚开始,容我多说两句,那么久只说这两句,首先第一句不是特别重要,重要的是第二句,第二句的重点和第一句一样.....巴拉巴拉,不鬼扯了!
一开始我也想着自己看源码,但是当我打开一个自己写好的spring项目的时候却无从下手,总而言之,言而总之,就是不知道从哪里下手。所以我选择去和老师学。嗯,那我们开始吧!
从这里开始
了解java的同学学起来很快啊,主要还是老师讲的好。
在使用spring bean是我们需要去 ClassPathXmlApplicationContext() 中获取到 ApplicationContext 对象 这个对象就是我们所谓的 大容器,你可以这么理解,它里面主要包含了两个容器和 一个 Class 属性的对象(初学,只涉及到这几个属性,源码里面应该还有很多配置,这里我们后续应该会了解到)。那我们就详细讲解一下其中的厉害关系。
1、创建一个普通的对象(UserService)和一个测试类(Test)(下面我用 PanXiaoheiApplicationContext 代替 ApplicationContext )
1.1、UserService
UserService 对象中我需要将他放到spring容器中,因此需要加一个 @Component("userService") 注解。因为我们还要判断这个类似单例还是多例,因此需要添加一个@Scope("prototype")注解。
import com.panxiaohei.spring.Component;
@Component("userService")
@Scope("prototype")
public class UserService {//测试注入ben
}
Component 注解类中,我们只需要实现几个简单的约束。
@Retention(RetentionPolicy.RUNTIME)//生效时间
@Target(ElementType.TYPE)//只能写在类上面
public @interface Component {
String value() default "";//给定当前ben取一个名字
}
Scope 注解类
@Retention(RetentionPolicy.RUNTIME)//生效时间
@Target(ElementType.TYPE)//只能写在类上面
public @interface Scope{
String value() default "";//单例或多例
}
我的理解:注解就像一个笔记本,用于约束和附带信息。
1.2、Test
Test 类中使用main方法来创建我们的 PanXiaoheiApplicationContext 类
import com.panxiaohei.spring.PanXiaoheiApplicationContext;
public class Test {
public static void main(String[] args) {
PanXiaoheiApplicationContext applicationContext = new PanXiaoheiApplicationContext(AppConfig.class);
UserService userService = (UserService) applicationContext.getBen("userService");
}
}
AppConfig 为我们的配置注解类:
@ComponentScan("com.panxiaohei.service")
public class AppConfig {
}
由此可见我们的配置类上有一个指定扫描路径,即我们需要将那个包下面的类放入spring容器中。
接下来我们来实现这个注解:
ConponentScan 注解类
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 "";//指定扫描路径
}
这里我们什么配置都没写,就加了两个注解,分别表示生效时间和使用这个注解的位置。
2 、我们自己创建一个 ApplicationContext 对象:
解:由上面Tset类可得,我们的 PanXiaoheiApplicationContext 对象中需要一个有参构造器和一个 getBean 的方法,下面的两个容器,我们后续会用到。
public class PanXiaoheiApplicationContext {
private Class configClass;
private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =new ConcurrentHashMap<>();
private ConcurrentHashMap<String, Object> singletonObject =new ConcurrentHashMap<>();
public PanXiaoheiApplicationContext(Class configClass) {
this.configClass = configClass;
}
/**
* 根据名字找到类,判断是单例还是多例
* @param beanName
*/
public Object getBen(String beanName) {
}
}
我们需要在这个类中大展身手了!
3、bean的实现(存储)
3.1 扫描需要放入容器的类。
思路:
- 传入的 configClass 类中有我们需要加入spring容器包的地址
- 通过这个地址,获取到该地址下的所有文件
- 遍历这些文件,并筛选出.class文件
- 通过这些.class文件的地址获取到他的类(控制反转)
- 判断类上面是否有@Component注解,并解析其名字,如名字为空,者以该类类名首字母小写设置为该类的名称。
- 创建一个bean的定义类(beanDefinition),用于包容放入容器的对象和它的一些配置(如:是否是单例,是否为懒加载),将对象和它的配置放入该类中。
- 创建一个总的bean容器,以对象名(或者Component注解中的value属性)作为key,存储beanDefinition。
实现:
// 扫描
if (configClass.isAnnotationPresent(ComponentScan.class)) { //判断configClass类有没有ComponentScan注解
ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();//扫描路径 com.panxiaohei.service
path = path.replace(".","/"); //转化为目录格式 com/panxiaohei/service
ClassLoader classLoader = PanXiaoheiApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(path);//获取绝对路径
File file = new File(resource.getFile());
System.out.println("扫描的绝对路径:"+file);
if (file.isDirectory()) { //是否为文件夹
File[] files = file.listFiles();
for (File f:files) {
String fileName = f.getAbsolutePath();//文件的绝对路径
// System.out.println("目录下的文件路径:"+fileName);
if (fileName.endsWith(".class")) { //判断字符串末尾
//获取到 com\panxiaohei\service\UserService
String calssName = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
//转化成 com.panxiaohei.service.UserService
calssName = calssName.replace("\\", ".");
// System.out.println("===> "+calssName);
/**
* 判断类上是否有Conpoent注解
*/
try {
Class<?> aClass = classLoader.loadClass(calssName);
if (aClass.isAnnotationPresent(Component.class)) {
System.out.println("===> "+calssName);
//获取该类上的 Compoenet 对象
Component component = aClass.getAnnotation(Component.class);
String beanName=component.value();
if (beanName.equals("")){
//生成名字 默认首字母小写
beanName = Introspector.decapitalize(aClass.getSimpleName());
}
//生成 BeanDefinition 对象
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setType(aClass);//bean的类型
//判断是单例还是多例
if (aClass.isAnnotationPresent(Scope.class)) {
Scope scopeAnnotaicon = aClass.getAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotaicon.value());
} else {
beanDefinition.setScope("singleton");
}
//存储
beanDefinitionMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
以上涉及到的 BeanDefinition 对象:
package com.panxiaohei.spring;
/**
* 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;
}
}
createBean 方法
/**
* 创建一个 bean (反转控制法)
*
* @param beanName
* @param beanDefinition
* @return
*/
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;
}
至此,我们完成了对象提交到bean容器中管理,但是涉及到单例bean时,我们应该在创建容器的时候就应该去new出来,因为单例bean只会创建一次,因此在完成bean的存储之后我们接着需要单独再存储一次单例bean。
3.2 存储单例bean
继:实现扫描之后.
思路:
- 遍历bean容器中所有的 BeanDefinition 筛选出Scope为 “singleton ”的对象
- 将筛选出来的对象,以对象名(或者Component注解中的value属性)作为key,放入 bean单例容器中
实现:
//实例化单例bean
for (String beanName : beanDefinitionMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition.getScope().equals("singleton")) {// 判断是否为单例
Object bean = createBean(beanName,beanDefinition);
singletonObject.put(beanName,bean);
}
}
至此,我们完成了将对象交给spring容器托管。
4、获取spring容器中的bean对象
还记得上面我们写Test对象中,PanXiaoheiApplicationContext 对象的getbean方法吗?
那么它里面是这么实现的呢?
思路:
- 通过对象名(或者Component注解中的value属性值)获取到 PanXiaoheiApplicationContext 对象中的总的bean容器中的beanDefinition
- 判断该definition中的scope是否为单例
- 单例从bean单例容器中获取bean,并返回bean中存储的对象。
- 多例,返回一个new的bean对象。
实现:
public Object getBen(String beanName) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition == null) {
throw new NullPointerException();
}else {
String scope = beanDefinition.getScope();
if (scope.equals("singleton")) {//单例
Object bean = singletonObject.get(beanName);
if (bean == null){
Object o = createBean(beanName, beanDefinition);
singletonObject.put(beanName,o);
}
return bean;
}else {//多例
return createBean(beanName,beanDefinition);
}
}
}
总结
学到这里我才发现,原理bean中并没有我想象的那么复杂,也可能是看源码摸不着头,因为它太深沉,父类子类继承太多,太繁琐。因此跟着前辈的步伐走真的可以少走很多弯路,也给我们这些穷苦家庭的孩子有了一个很好的学习机会。可能获取学习会越来越深入,涉及到锁的时候就会难一点,但是也要充分做好准备。也许是为了找到一份好工作去学,也去是因为想要开创思维,了解前人的强大,从而壮大自己我去学。不管是哪一种,都是好的。学无止境都是好的。
最后,祝和我一样刚开始接触spring底层的码子们都能顺顺利利,找到自己的一条路!后续我也会持续更新自己的总结,也希望能帮到大家!。