目录
为什么要手写Spring?
Spring是入门JavaWeb的第一个框架,是沿用至今非常久的框架之一,改变了大家刀耕火种时代的编程方式,并且围绕着Web技术的演进在不断进化与整合,已经变得非常成熟。以至于现在我这样的菜鸟后端只会按照SpringBoot的那一套约定对业务进行CRUD,不能提升自己的编程能力与设计能力。
为了在学习设计模式时有一些具体的有难度的例子进行思考与体会;为了进一步深刻体会Spring的设计理念与功能实现;为了锻炼自己主动思考的能力;我决定体验手写Spring框架,并将学习过程的感悟与问题记录下来,汇总成文章。
ioc第一版——极简实现
实现效果
测试准备xml文件resource/beans.xml
:
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id = "demoService" class = "com.cee.bean.impl.DemoServiceImpl"></bean>
</beans>
测试准备Java类com.cee.bean.DemoService,com.cee.bean.impl.DemoServiceImpl
// 接口
public interface DemoService {
void sayHello();
}
// 实现类,Bean
public class DemoServiceImpl implements DemoService {
@Override
public void sayHello() {
System.out.println("Hello mini Spring!");
}
}
测试类与实现结果:
// xml实现
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
DemoService demoService = (DemoService) context.getBean("demoService");
demoService.sayHello(); // Hello mini Spring!
// 注解实现
AnnotationApplicationContext context2 = new AnnotationApplicationContext("com.cee");
DemoService demoService2 = (DemoService) context2.getBean("demoServiceImpl");
demoService2.sayHello(); // Hello mini Spring!
1. BeanDefinition定义
在这个类里面定义两个最简单的域:id 与 className。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BeanDefinition {
private String id;
private String className;
}
2. 基于xml的实现ClassPathXmlApplicationContext
以面向过程的思想梳理极简版ioc流程,如图所示:
- 解析
Bean
的类信息 - 封装
BeanDefinition
并保存 - 读取
BeanDefinition
并注册单例 getBean(String beanName)
方法获取单例Bean实例
实现:
/**
* 1-3 在创建ClassPathXmlApplicationContext类时完成
* 1. 解析xml
* 2. 注册BeanDefinition
* 3. 生成singleton对象并保存到Map
* 4. 对外开放获取对象的接口
*/
public class ClassPathXmlApplicationContext {
private List<BeanDefinition> beanDefinitions = new ArrayList<>();
private Map<String, Object> singletons = new HashMap<>();
//构造器获取外部配置,解析出Bean的定义,形成内存映像
public ClassPathXmlApplicationContext(String fileName) {
this.readXml(fileName);
this.instanceBeans();
}
private void readXml(String fileName) {
SAXReader saxReader = new SAXReader();
try {
URL xmlPath =
this.getClass().getClassLoader().getResource(fileName);
Document document = saxReader.read(xmlPath);
Element rootElement = document.getRootElement();
//对配置文件中的每一个<bean>,进行处理
for (Element element : (List<Element>) rootElement.elements()) {
//获取Bean的基本信息
String beanID = element.attributeValue("id");
String beanClassName = element.attributeValue("class");
BeanDefinition beanDefinition = new BeanDefinition(beanID,
beanClassName);
//将Bean的定义存放到beanDefinitions
beanDefinitions.add(beanDefinition);
}
}
}
//利用反射创建Bean实例,并存储在singletons中
private void instanceBeans() {
for (BeanDefinition beanDefinition : beanDefinitions) {
try {
singletons.put(beanDefinition.getId(),
Class.forName(beanDefinition.getClassName()).newInstance());
}
}
}
//这是对外的一个方法,让外部程序从容器中获取Bean实例,会逐步演化成核心方法
public Object getBean(String beanName) {
return singletons.get(beanName);
}
}
3.基于注解的实现AnnotationApplicationContext
首先抽象出公共逻辑
@Slf4j
public class BaseApplicationContext {
protected final List<BeanDefinition> beanDefinitions = new ArrayList<>();
protected final Map<String, Object> singletonMap = new HashMap<>();
protected void instanceBeans() {
for (BeanDefinition bd : beanDefinitions) {
try {
Object singletonBean = Class.forName(bd.getClassName()).getDeclaredConstructor().newInstance();
singletonMap.put(bd.getId(), singletonBean);
}catch (ClassNotFoundException | InvocationTargetException | InstantiationException |
IllegalAccessException | NoSuchMethodException e) {
log.error("创建singleton对象出错:", e);
}
}
}
public Object getBean(String beanName) {
return singletonMap.get(beanName);
}
}
其次实现AnnotationApplicationContext
@Slf4j
public class AnnotationApplicationContext extends BaseApplicationContext {
public AnnotationApplicationContext(String packageName) {
List<Class<?>> classes = scanComponentClasses(packageName);
resolveBeanDefinition(classes);
super.instanceBeans();
}
/**
* 扫描某一个包下带注解的类
* @param packageName 包名
* @return 类
*/
public List<Class<?>> scanComponentClasses(String packageName) {
List<Class<?>> classes = new ArrayList<>();
try {
List<File> files = getFiles(packageName);
for (File file : files) {
String className = getClassName(file, packageName);
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Component.class)) {
classes.add(clazz);
}
}
} catch (Exception e) {
log.error("扫描注解过程发生错误:", e);
}
return classes;
}
/**
* 递归调用API获取待扫描包下的类文件
* @param currentPackage 当前扫描的包
* @return 包下的所有文件
* @throws Exception 资源未找到
*/
private List<File> getFiles(String currentPackage) throws Exception {
List<File> fileList = new ArrayList<>();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String path = currentPackage.replace('.', '/');
URL resource = classLoader.getResource(path);
if (resource == null) {
throw new Exception("资源 " + path + " 未找到!");
}
File directory = new File(resource.toURI());
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
fileList.addAll(getFiles(currentPackage + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
fileList.add(file);
}
}
}
return fileList;
}
/**
* 获取类名
*/
private String getClassName(File file, String packageName) {
String filePath = file.getAbsolutePath();
filePath = filePath.replace(File.separatorChar, '.');
int pkgIndex = filePath.indexOf(packageName);
String className = filePath.substring(pkgIndex, filePath.length() - 6); // Remove ".class"
return className;
}
/**
* 注册BeanDefinition
* @param classes 带@Component的类
*/
private void resolveBeanDefinition(List<Class<?>> classes) {
for (Class<?> aClass : classes) {
String beanClassName = aClass.getName();
String beanId = getSimpleClassName(beanClassName);
BeanDefinition beanDefinition = new BeanDefinition(beanId, beanClassName);
beanDefinitions.add(beanDefinition);
}
}
private static String getSimpleClassName(String fullClassName) {
String[] parts = fullClassName.split("\\.");
return convertFirstCharacterToLowerCase(parts[parts.length - 1]);
}
private static String convertFirstCharacterToLowerCase(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.substring(0, 1).toLowerCase() + str.substring(1);
}
}
总结
本文主要实现了一个极简版ioc,搭建了ioc的极简流程,并以面向过程的方式将流程实现。下一章将对基于xml的实现进行简单解耦,并给出一些问题分析。
参考链接
列出主要参考链接。