阶段2:
// 1.编写自己的Spring容器,实现扫描包,得到bean的class对象.
2.扫描将 bean 信息封装到 BeanDefinition对象,并放入到Map.
思路:
1.将 bean 信息封装到 BeanDefinition对象中,再将其放入到BeanDefinitionMap集合中,集合的结构大概是
key[beanName]–value[beanDefintion]
key--------->对应指定的名字,未指定则以类的首字母小写为其名字
value------->对应封装好的BeanDefintion对象
2.因为bean的作用域可能是singleton,也可能是prototype,所以Spring需要扫描到bean信息,保存到集合,这样当getBean()根据实际情况处理.
具体实现
1.加一个自定义Scope注解
package com.elf.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 45~
* @version 1.0
* Scope 可以指定一个Bean的作用范围[singleton,prototype]
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
//通过value可以指定singleton,prototype
String value() default "";
}
2.在MonsterService.java上加上@Scope多实例注解
package com.elf.spring.component;
import com.elf.spring.annotation.Component;
import com.elf.spring.annotation.Scope;
/**
* @author 45~
* @version 1.0
* 说明 MonsterService 是一个Servic
*/
@Component //把MonsterService注入到我们自己的spring容器中
@Scope(value = "prototype")
public class MonsterService {
}
3.准备ioc包下写一个BeanDefinition.java 用于封装记录Bean信息.
package com.elf.spring.ioc;
/**
* @author 45~
* @version 1.0
* BeanDefinition 用于封装和记录Bean的信息 [1.scope 2.存放bean对应的Class对象,反射可以生成对应的对象]
* 2:因为将来getBean()时有可能是多实例,有可能是动态生成的,还要存放bean的class对象
*/
public class BeanDefinition {
private String scope;
private Class clazz;//存放bean的class对象
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
@Override
public String toString() {
return "BeanDefinition{" +
"scope='" + scope + '\'' +
", clazz=" + clazz +
'}';
}
}
3.pom.xml文件引入jar包下的工具类commons-lang,完成首字母小写的功能.而不用springframework自带的StringUtils工具类
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.elf</groupId>
<artifactId>elf-myspring1207</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
</project>
4.容器文件,把构造器里边的方法抽取出来封装成一个方法,直接在构造器中调用,使代码简洁.
这里完成生成BeanDefinition对象并放入到Map里面
添加内容1:
//定义属性BeanDefinitionMap -> 存放BeanDefinition对象(多例对象)
private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap =
new ConcurrentHashMap<>();
//定义属性SingletonObjects -> 存放单例对象 (跟原生容器的名字保持一致)
//因为将来存放单例池的时候肯定要指定单例对象是对应哪个Bean的,所以k用String来充当
//存放单例对象的类型是不确定的,可能是Dog,Cat,或者其他的对象,所以用Object
private ConcurrentHashMap<String,Object> singletonObjects =
new ConcurrentHashMap<>();
添加内容2:
//先得到beanName(有可能通过经典4注解,例如Component注解的value值来指定)
//1.得到类上的Component注解,此时的clazz已经是当前bean的class对象,通过类加载器得到的 反射知识
Component componentAnnotation = cla.getDeclaredAnnotation(Component.class);
//2.得到配置的value
String beanName = componentAnnotation.value();
if("".equals(beanName)){//如果没有写value,空串
//将该类的类名首字母小写作为beanName
//StringUtils其实是在springframework的框架下面的类,而这里本身我就是要自己写所以不用
beanName = StringUtils.uncapitalize(className);
}
//3.将Bean的信息封装到BeanDefinition对象中,然后将其放入到BeanDefinitionMap集合中
BeanDefinition beanDefinition = new BeanDefinition();
//!!!多看看这里多理解
beanDefinition.setClazz(cla);//由类加载器通过反射得到对象,Class<?> cla = classLoader.loadClass(classFullName);
//4.获取Scope值
if (cla.isAnnotationPresent(Scope.class)){
//如果配置了Scope,就获取它配置的值
Scope scopeAnnotatiion = cla.getDeclaredAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotatiion.value());
}else{
//如果没有配置Scope,就以默认的值singleton
beanDefinition.setScope("singleton");
}
//将beanDefinitionMap对象放入Map
beanDefinitionMap.put(beanName,beanDefinition);
}else {
//如果该类没有使用了@Component注解,说明是一个Spring bean
System.out.println("这不是一个Spring bean" + cla + " 类名=" + className);
}
容器文件
package com.elf.spring.ioc;
import com.elf.spring.annotation.*;
import org.apache.commons.lang.StringUtils;
import java.io.File;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author 45~
* @version 1.0
*/
public class ElfSpringApplicationContext {
//第一步,扫描包,得到bean的class对象,排除包下不是bean的,因此还没有放到容器中
//因为现在写的spring容器比原先的基于注解的,要更加完善,所以不会直接把它放在ConcurrentHashMap
private Class configClass;
//定义属性BeanDefinitionMap -> 存放BeanDefinition对象
private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap =
new ConcurrentHashMap<>();
//定义属性SingletonObjects -> 存放单例对象 (跟原生容器的名字保持一致)
//因为将来存放单例池的时候肯定要指定单例对象是对应哪个Bean的,所以k用String来充当
//存放单例对象的类型是不确定的,可能是Dog,Cat,或者其他的对象,所以用Object
private ConcurrentHashMap<String,Object> singletonObjects =
new ConcurrentHashMap<>();
//构造器
public ElfSpringApplicationContext(Class configClass) {
beanDefinitionScan(configClass);//调用封装方法,简洁
System.out.println("beanDefinitionMap=" + beanDefinitionMap);
}//构造器结束
//该方法完成对指定包的扫描,并将Bean信息封装到BeanDefinition对象,再放入到Map中
public void beanDefinitionScan(Class configClass){
this.configClass = configClass;
/**获取要扫描的包:
1.先得到ElfSpringConfig配置的 @ComponentScan(value= "com.elf.spring.component")
2.通过 @ComponentScan的value => 即要扫描的包 **/
ComponentScan componentScan =
(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScan.value();
System.out.println("要扫描的包path=" + path);
/**
* 得到要扫描包下的所有资源(类.class)
* 1.得到类的加载器 -> APP类加载器是可以拿到 target目录下的classes所有文件的
* 2.通过类的加载器获取到要扫描的包的资源url => 类似一个路径
* 3.将要加载的资源(.class)路径下的文件进行遍历 => io
*/
ClassLoader classLoader = ElfSpringApplicationContext.class.getClassLoader();
path = path.replace(".", "/"); // 把.替换成 /
URL resource = classLoader.getResource(path);
System.out.println("resource=" + resource);
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) { //把所有的文件都取出来
System.out.println("============================");
System.out.println("f.getAbsolutePath()=" + f.getAbsolutePath());
String fileAbsolutePath = f.getAbsolutePath();//到target的classes目录下了
//这里只处理.class文件
if (fileAbsolutePath.endsWith(".class")) {
//1.获取类名
String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1,
fileAbsolutePath.indexOf(".class"));
//2.获取类的完整路径(全类名)
String classFullName = path.replace("/", ".") + "." + className;
System.out.println("classFullName=" + classFullName);
//3.判断该类是否需要注入,就看是不是有注解@Component @Service @Contoller @Re....
try {
Class<?> cla = classLoader.loadClass(classFullName);
if (cla.isAnnotationPresent(Component.class) ||
cla.isAnnotationPresent(Controller.class) ||
cla.isAnnotationPresent(Service.class) ||
cla.isAnnotationPresent(Repository.class)) {
//演示机制
//如果该类使用了@Component注解,说明是一个Spring bean
System.out.println("这是一个Spring bean=" + cla + " 类名=" + className);
//先得到beanName(有可能通过经典4注解,例如Component注解的value值来指定)
//1.得到类上的Component注解,此时的clazz已经是当前bean的class对象,通过类加载器得到的 反射知识
Component componentAnnotation = cla.getDeclaredAnnotation(Component.class);
//2.得到配置的value
String beanName = componentAnnotation.value();
if("".equals(beanName)){//如果没有写value,空串
//将该类的类名首字母小写作为beanName
//StringUtils其实是在springframework的框架下面的类,而这里本身我就是要自己写所以不用
beanName = StringUtils.uncapitalize(className);
}
//3.将Bean的信息封装到BeanDefinition对象中,然后将其放入到BeanDefinitionMap集合中
BeanDefinition beanDefinition = new BeanDefinition();
//!!!多看看这里多理解
beanDefinition.setClazz(cla);//由类加载器通过反射得到对象,Class<?> cla = classLoader.loadClass(classFullName);
//4.获取Scope值
if (cla.isAnnotationPresent(Scope.class)){
//如果配置了Scope,就获取它配置的值
Scope scopeAnnotatiion = cla.getDeclaredAnnotation(Scope.class);
beanDefinition.setScope(scopeAnnotatiion.value());
}else{
//如果没有配置Scope,就以默认的值singleton
beanDefinition.setScope("singleton");
}
//将beanDefinitionMap对象放入Map
beanDefinitionMap.put(beanName,beanDefinition);
}else {
//如果该类没有使用了@Component注解,说明是一个Spring bean
System.out.println("这不是一个Spring bean" + cla + " 类名=" + className);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}//遍历文件for循环结束
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
}
//编写放法返回容器中的对象
public Object getBean(String name) {
return null;
}
}
运行结果
beanDefinitionMap={
monsterService=BeanDefinition{scope=‘prototype’, clazz=class com.elf.spring.component.MonsterService},
monsterDao=BeanDefinition{scope=‘singleton’, clazz=class com.elf.spring.component.MonsterDao}
}
ok
这里存在一个问题:单例多例对象都是放在beanDefinitionMap, singletonObjects里没有单例对象.