去年实现的IOC逻辑,对其进行了优化
下面是我写的旧版,现在看来过度仿照源码,保持类名与方法名一致等等操作,致使一个简单实现spring逻辑的代码有点冗余。
以下代码留做纪念:
因为手写tomcat已经完成了,所以现在来实现spring
当然,spring本身就是使用java写的,手写spring主要是为了熟悉源码,所以参考源码实现了一个简单的spring。
源码:spring-projects/spring-framework: Spring Framework (github.com)
1.springIOC
1.自定义注解
官方源码:
新建org.springframework.annotation包,在其下新建四个注解
@Controller,@Service,@Repository都有带@Component父注解
源码有个@Documented注解,生成帮助文档的,没什么用,就不加。
首先实现Component
package org.springframework.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) //TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Retention(RetentionPolicy.RUNTIME) //注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
public @interface Component {
String value() default ""; //注解参数
}
如下代码,
@AliasFor(annotation = Component.class) 该注解是为了属性起别名,但是我暂时只是想实现一个简单的spring,就不写它了
实现@Controller
package org.springframework.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) //TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Retention(RetentionPolicy.RUNTIME) //注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Component
public @interface Controller {
//@AliasFor(annotation = Component.class)
String value() default ""; //注解参数
}实现@Service
package org.springframework.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) //TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Retention(RetentionPolicy.RUNTIME) //注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Component
public @interface Service {
//@AliasFor(annotation = Component.class)
String value() default ""; //注解参数
}实现@Repository
package org.springframework.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE) //TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Retention(RetentionPolicy.RUNTIME) //注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Component
public @interface Repository {
//@AliasFor(annotation = Component.class)
String value() default ""; //注解参数
}实现@Autowired
package org.springframework.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* ElementType:
* CONSTRUCTOR:用于描述构造器
* METHOD:用于描述方法
* FIELD:用于描述域
* LOCAL_VARIABLE:用于描述局部变量
* PACKAGE:用于描述包
* PARAMETER:用于描述参数
* TYPE:用于描述类、接口(包括注解类型) 或enum声明
*/
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
//参数默认值为true
boolean required() default true;
}
2.获取扫描包路径
常见的,我们需要读取xml文件

比如component-scan属性

参考官方源码:
就是对xml文件进行解析, 通过XmlBeanDefinitionReader来读取xml配置文件,loadBeanDefinitions函数读取XML配置文件,而在源码中还有考虑到许多,比如获取到spring XML配置文件的地址,配置它的环境,设置成员属性等,这里我们只是实现一个简单的spring,不考虑这么多,所以我直接解析xml文件。
初始化spring容器中的refresh()方法中,会调用obtainFreshBeanFactory()方法,它是读取并解析spring xml配置文件的入口
类似,我们,仿照一个。
新建org/springframework/context/support/ClassPathXmlApplicationContext.java
package org.springframework.context.support;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
/**
* @author PoemsAndDreams
* @date 2023-09-25 10:42
*/
public class ClassPathXmlApplicationContext {
private String configResources;
private XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader();
public ClassPathXmlApplicationContext(String configResources) {
this.configResources = configResources;
//初始化spring容器
refresh();
}
private void refresh() {
// obtainFreshBeanFactory 加载spring入口
obtainFreshBeanFactory();
}
private void obtainFreshBeanFactory() {
//加载xml配置文件
loadBeanDefinitions();
}
private void loadBeanDefinitions() {
xmlBeanDefinitionReader.loadBeanDefinitions(configResources);
}
}新建org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java
package org.springframework.beans.factory.xml;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.IOException;
import java.io.InputStream;
/**
* @author PoemsAndDreams
* @date 2023-09-25 10:15
*/
public class XmlBeanDefinitionReader {
//加载xml配置文件
public void loadBeanDefinitions(String configResources){
//获取扫描包路径
getComponentScanPackage(configResources);
System.out.println(getComponentScanPackage(configResources));
}
private String getComponentScanPackage(String configResources) {
//DOM4J 解析 XML
InputStream resourceAsStream = null;
try {
//创建 SAXReader 对象
SAXReader saxReader = new SAXReader();
//获取class对象加载文件返回流
resourceAsStream = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configResources);
//获取document对象
Document document = saxReader.read(resourceAsStream);
//获取根节点
Element rootElement = document.getRootElement();
//获取扫描包
Element element = rootElement.element("component-scan");
Attribute attribute = element.attribute("base-package");
return attribute.getValue();
} catch (DocumentException e) {
throw new RuntimeException(e);
}finally {
if (resourceAsStream != null){
try {
resourceAsStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}新建一个模块UseSpring来测试功能,spring模块是上述我们写的代码的模块

引入我们写的spring项目依赖,这样就可以使用我们自己写的代码了
<dependency>
<groupId>com.dreams</groupId>
<artifactId>spring</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>在resources加入新建applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--以下为spring格式校验链接,引入可以对xml格式校验-->
<!--<beans xmlns="http://www.springframework.org/schema/beans"-->
<!-- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"-->
<!-- xmlns:context="http://www.springframework.org/schema/context"-->
<!-- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">-->
<!--spring的xml格式校验链接结束-->
<!-- 获取扫描包路径,spring的xml需要加入context:,我们自己实现的spring就不需要加-->
<!-- <context:component-scan base-package="com.yutian"></context:component-scan>-->
<component-scan base-package="com.yutian"></component-scan>
<!--</beans>-->测试一下
package com.yutian;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestController {
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//......
System.out.println(context);
}
}
3.ioc逻辑实现
参考官方源码
BeanDefinition 主要是用来描述 Bean,其存储了 Bean 的相关信息,Spring 实例化 Bean 时需读取该 Bean 对应的 BeanDefinition。BeanDefinition 整体可以分为两类,一类是描述通用的 Bean,还有一类是描述注解形式的 Bean。
所以我们新建一个BeanDefinition 类
package org.springframework.beans.factory.config;
/**
* @author PoemsAndDreams
* @date 2023-09-26 11:14
* Bean的定义类
*/
public class BeanDefinition {
private String BeanName;
private Class clazz;
//其他信息,如单例等,这里实现一个简单的spring,就不实现了
//Getter and Setter
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
public String getBeanName() {
return BeanName;
}
public void setBeanName(String beanName) {
BeanName = beanName;
}
}在XmlBeanDefinitionReader类中加入
//存储bean定义信息,所以扫描到的
private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
//Getter方法
public ConcurrentHashMap<String, BeanDefinition> getBeanDefinitionMap() {
return beanDefinitionMap;
}完善一下XmlBeanDefinitionReader的loadBeanDefinitions方法
public void loadBeanDefinitions(String configResources){
//获取扫描包
String scanPackage = getComponentScanPackage(configResources);
//获取扫描包路径
findScanPackagePath(scanPackage);
}getComponentScanPackage函数
//获取扫描包
private String getComponentScanPackage(String configResources) {
//DOM4J 解析 XML
InputStream resourceAsStream = null;
try {
//创建 SAXReader 对象
SAXReader saxReader = new SAXReader();
//获取class对象加载文件返回流
resourceAsStream = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configResources);
//获取document对象
Document document = saxReader.read(resourceAsStream);
//获取根节点
Element rootElement = document.getRootElement();
//获取扫描包
Element element = rootElement.element("component-scan");
Attribute attribute = element.attribute("base-package");
return attribute.getValue();
} catch (DocumentException e) {
throw new RuntimeException(e);
}finally {
if (resourceAsStream != null){
try {
resourceAsStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}findScanPackagePath函数,获取扫描包路径
private void findScanPackagePath(String scanPackage) {
ClassLoader classLoader = XmlBeanDefinitionReader.class.getClassLoader();
scanPackage = scanPackage.replace(".","/");
URL url = classLoader.getResource(scanPackage);
//因为获取的目录可能含有空格,且会使用%20替代空格,所以我们需要替换回去
String urlFile = url.getFile();
if (urlFile.contains("%20")){
urlFile = urlFile.replace("%20"," ");
}
File file = new File(urlFile);
//加载并实例化
loadAllClass(file);
}loadAllClass(file)加载并实例化,将其放到Bean定义类Map的BeanDefinitionMap中,其中,一个接口存放其实现类的class.
//加载
private void loadAllClass(File path) {
File[] files = path.listFiles();
for (File file : files) {
//如果是个目录
if (!file.isDirectory()){
//获取文件路劲
String fileName = file.getAbsolutePath();
if (fileName.endsWith(".class")){
String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
className = className.replace("\\",".");
//获取类加载器
ClassLoader classLoader = XmlBeanDefinitionReader.class.getClassLoader();
try {
//加载类
Class<?> clazz = classLoader.loadClass(className);
//类是否有Component,Service,Controller,Repository注解
if (clazz.isAnnotationPresent(Component.class) || clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class) || clazz.isAnnotationPresent(Repository.class)){
Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
Controller controllerAnnotation = clazz.getDeclaredAnnotation(Controller.class);
Service serviceAnnotation = clazz.getDeclaredAnnotation(Service.class);
Repository repositoryAnnotation = clazz.getDeclaredAnnotation(Repository.class);
String value = "";
//Bean定义类
BeanDefinition beanDefinition = new BeanDefinition();
//判断注解是否有value值
if (componentAnnotation != null || controllerAnnotation != null || serviceAnnotation != null || repositoryAnnotation != null){
if (componentAnnotation != null && !componentAnnotation.value() .equals("")){
value = componentAnnotation.value();
}else if (controllerAnnotation != null && !controllerAnnotation.value().equals("")){
value = controllerAnnotation.value();
}else if (serviceAnnotation != null && !serviceAnnotation.value().equals("")){
value = serviceAnnotation.value();
} else if (repositoryAnnotation != null && !repositoryAnnotation.value().equals("")) {
value = repositoryAnnotation.value();
} else {
String name = clazz.getSimpleName();
//默认以开头小写的类名作为实例名
value = name.valueOf(name.charAt(0)).toLowerCase() + name.substring(1);
}
//不能重名
if (beanDefinitionMap.get(value) != null) {
throw new RuntimeException("spring IOC Container is already exists " + beanDefinitionMap.get(value));
}
beanDefinition.setClazz(clazz);
beanDefinition.setBeanName(value);
//保存到bean定义
beanDefinitionMap.put(value,beanDefinition);
//获取到该类实现的所有接口
Class[] interfaces = clazz.getInterfaces();
//在beanDefinitionMap中存储为一个接口对应一个实现类
for (Class anInterface : interfaces) {
String interfaceSimpleName = anInterface.getSimpleName();
interfaceSimpleName = interfaceSimpleName.valueOf(interfaceSimpleName.charAt(0)).toLowerCase() + interfaceSimpleName.substring(1);
beanDefinitionMap.put(interfaceSimpleName,beanDefinition);
}
}
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
else {
loadAllClass(file);
}
}
}回到ClassPathXmlApplicationContext类,加入属性
//@Autowired注入逻辑 private AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
更改refresh()方法
private void refresh() {
// obtainFreshBeanFactory 加载spring入口
obtainFreshBeanFactory();
//实例化Bean
createBean();
}
createBean()方法如下,@Autowired逻辑在createBeanInstance()方法
private void createBean() {
autowiredAnnotationBeanPostProcessor.setXmlBeanDefinitionReader(xmlBeanDefinitionReader);
//具体逻辑
autowiredAnnotationBeanPostProcessor.createBeanInstance();
}可以看到具体逻辑在AutowiredAnnotationBeanPostProcessor类
新建org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
其中InstanceMap保存实例化,方便我们使用
package org.springframework.beans.factory.annotation;
import org.springframework.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author PoemsAndDreams
* @date 2023-09-25 12:34
*/
public class AutowiredAnnotationBeanPostProcessor {
private XmlBeanDefinitionReader xmlBeanDefinitionReader;
//实例化存储
private ConcurrentHashMap<String, Object> InstanceMap = new ConcurrentHashMap<>();
public ConcurrentHashMap<String, Object> getInstanceMap() {
return InstanceMap;
}
//Setter方法
public void setXmlBeanDefinitionReader(XmlBeanDefinitionReader xmlBeanDefinitionReader) {
this.xmlBeanDefinitionReader = xmlBeanDefinitionReader;
}
}createBeanInstance()方法也在AutowiredAnnotationBeanPostProcessor类
public void createBeanInstance() {
ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = xmlBeanDefinitionReader.getBeanDefinitionMap();
for (String beanName : beanDefinitionMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
Class clazz = beanDefinition.getClazz();
try {
//实例化
Object instance = clazz.getDeclaredConstructor().newInstance();
//保存至InstanceMap,方便使用
InstanceMap.put(beanName,instance);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
//自动注入逻辑
postProcessProperties();
}@Autowired注入逻辑在postProcessProperties方法
//@Autowired注入逻辑
public void postProcessProperties() {
ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = xmlBeanDefinitionReader.getBeanDefinitionMap();
for (String beanName : beanDefinitionMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//获取到实际的class
Class clazz = beanDefinition.getClazz();
//获取到所有字段
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
//是否有Autowired注解
if (declaredField.isAnnotationPresent(Autowired.class)) {
Class<?> aClass = declaredField.getType();
Object instance = null;
try {
String name = declaredField.getName();
//获取实现该接口的类
instance = InstanceMap.get(name);
Object o = InstanceMap.get(beanName);
declaredField.setAccessible(true);
declaredField.set(o, instance);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}最后在ClassPathXmlApplicationContext.java加入获取bean的方法
public Object getBean(String beanName){
//获取
ConcurrentHashMap<String, Object> instanceMap = autowiredAnnotationBeanPostProcessor.getInstanceMap();
Object o = instanceMap.get(beanName);
//获取不到即抛出异常
if (o == null){
throw new RuntimeException("No bean named" + beanName +" available");
}
return o;
}
public Object getBean(Class<?> clazz) {
ConcurrentHashMap<String, Object> instanceMap = autowiredAnnotationBeanPostProcessor.getInstanceMap();
ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = xmlBeanDefinitionReader.getBeanDefinitionMap();
Object bean = new Object();
for (BeanDefinition value : beanDefinitionMap.values()) {
Class aClass = value.getClazz();
if (aClass == clazz){
String beanName = value.getBeanName();
bean = instanceMap.get(beanName);
return bean;
}
}
//获取不到即抛出异常
throw new RuntimeException("No qualifying bean of type" + clazz + " available");
}测试一下,
如图的目录结构

在UserController
package com.yutian.controller;
import com.yutian.service.impl.UserServiceImpl;
import org.springframework.annotation.Autowired;
import org.springframework.annotation.Controller;
/**
* @author PoemsAndDreams
* @date 2023-09-27 07:49
*/
@Controller(value = "uc")
public class UserController {
@Autowired
UserServiceImpl userService;
public void test(){
userService.test();
}
}在UserService
package com.yutian.service;
/**
* @author PoemsAndDreams
* @date 2023-09-27 07:49
*/
public interface UserService {
void test();
}在UserServiceImpl
package com.yutian.service.impl;
import com.yutian.service.UserService;
import org.springframework.annotation.Service;
/**
* @author PoemsAndDreams
* @date 2023-09-27 07:50
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserDao userDao;
@Override
public void test() {
userDao.test();
}
}UserDao
package com.yutian.Dao;
/**
* @author PoemsAndDreams
* @date 2023-09-27 15:00
*/
public interface UserDao {
void test();
}UserDaoImpl
package com.yutian.Dao.Impl;
import com.yutian.Dao.UserDao;
import org.springframework.annotation.Repository;
/**
* @author PoemsAndDreams
* @date 2023-09-27 15:00
*/
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void test() {
System.out.println("Hello spring");
}
}在test
import com.yutian.controller.UserController;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author PoemsAndDreams
* @date 2023-09-27 07:54
*/
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//......
UserController userController = (UserController) context.getBean("uc");
userController.test();
UserController userController1 = (UserController) context.getBean(UserController.class);
userController1.test();
System.out.println(userController1 == userController);
}
}运行如图




