说在前头: 笔者本人为大三在读学生,书写文章的目的是为了对自己掌握的知识和技术进行一定的记录,同时乐于与大家一起分享,因本人资历尚浅,发布的文章难免存在一些错漏之处,还请阅读此文章的大牛们见谅与斧正。若在阅读时有任何的问题,也可通过评论提出,本人将根据自身能力对问题进行一定的解答。
手撸Spring系列是笔者本人首次尝试的、较为规范的系列博客,将会围绕Spring框架分为IOC/DI 思想
、Spring MVC
、AOP 思想
、Spring JDBC
四个模块,并且每个模块都会分为理论篇
、源码篇
、实战篇
三个篇章进行讲解(大约12篇文章左右的篇幅)。从原理出发,深入浅出,一步步接触Spring源码并手把手带领大家一起写一个 迷你版的Spring框架 ,促进大家进一步了解Spring的本质!
由于源码篇涉及到源码的阅读,可能有小伙伴没有成功构建好Spring源码的阅读环境,笔者强烈建议:想要真正了解Spring,一定要构建好源码的阅读环境再进行研究,具体构建过程可查看笔者此前的博客:《如何构建Spring5源码阅读环境》
前言
经过前面两篇分别关于IOC和DI的源码篇博客后,终于迎来了我们 手撸Spring系列 的重头戏了——实战篇!
实战篇的源码我将会开源到码云 Gitee 上,仓库地址:https://gitee.com/bosen-once/mini-spring
实战篇的代码的重心会放在IOC/DI、MVC、AOP等思想的具体实现上,实现迷你版的Spring(除注解外,每个类对应的包与Spring源码保持一致),对于其扩展性不会做特别的考虑!!
那么废话不多说,直接上车吧!!!
一、常用注解的编写
在源码篇中,笔者通过AnnotationConfigApplicationContext
来各位读者朋友解读的Spring源码,因此,实战篇中,笔者会先从使用最多、编写也最简单的注解类开始编写,包括(@Indexed
、@Component
、@Controller
、@Service
、@Repository
、@Autowired
、@ComponentScan
)。
在正式开始编写前,还请各位读者朋友先看看整个程序的架构:
那么就让我们开始正式的编写吧!!
1.@Indexed
package org.springframework.annotation;
import java.lang.annotation.*;
/**
* <p>为Spring的模式注解添加索引</p>
* @author Bosen
* @date 2021/9/10 14:30
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Indexed {}
2.@Component
package org.springframework.annotation;
import java.lang.annotation.*;
/**
* <p>通用组件模式注解</p>
* @author Bosen
* @date 2021/9/10 14:31
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
3.@Controller
package org.springframework.annotation;
import java.lang.annotation.*;
/**
* <p>Web控制器模式注解</p>
* @author Bosen
* @date 2021/9/10 14:19
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
String value() default "";
}
4.@Service
package org.springframework.annotation;
import java.lang.annotation.*;
/**
* <p>服务模式注解</p>
* @author Bosen
* @date 2021/9/10 14:18
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
String value() default "";
}
5.@Repository
package org.springframework.annotation;
import java.lang.annotation.*;
/**
* <p>数据仓库模式注解</p>
* @author Bosen
* @date 2021/9/10 14:26
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
String value() default "";
}
6.@Autowired
package org.springframework.annotation;
import java.lang.annotation.*;
/**
* <p>自动注入注解</p>
* @author Bosen
* @date 2021/9/10 14:23
*/
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String value() default "";
}
7.@ComponentScan
package org.springframework.annotation;
import java.lang.annotation.*;
/**
* <p>配置需要扫描的包</p>
* @author Bosen
* @date 2021/9/11 14:10
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScan {
String value() default "";
}
二、beans模块的编写
1.BeanFactory
知识温故: 在理论篇和源码篇中,我们反复强调了BeanFactroy
是spring的顶层接口,定义了工厂的基本功能,而在我们的迷你版spring
中,会将其简化为只有getBean
方法的接口~
package org.springframework.beans.factory;
/**
* <p>spring顶层接口</p>
* @author Bosen
* @date 2021/9/10 14:35
*/
public interface BeanFactory {
/**
* <p>通过bean名称获取bean实例</p>
*/
Object getBean(String beanName);
}
2.DefaultListableBeanFactory
知识温故: DefaultListableBeanFactory
是BeanFactory
三个子类接口的默认实现类!(迷你版中,我们不编写这三个子接口,因此我们让该类直接继承BeanFactory
即可)
package org.springframework.beans.factory.support;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* <p>bean工厂的实现类</p>
* @author Bosen
* @date 2021/9/10 15:28
*/
public class DefaultListableBeanFactory implements BeanFactory {
/**
* <p>用于存放bd的map</p>
*/
public final Map<String, BeanDefinition> beanDefinitionMap =
new ConcurrentHashMap<>();
@Override
public Object getBean(String beanName) {
return null;
}
}
3.BeanDefinition
知识温故: BeanDefinition
用于存储一个bean
的信息,是bean
的原料!
package org.springframework.beans.factory.config;
/**
* <p>保存bean定义相关的信息</p>
* @author Bosen
* @date 2021/9/10 14:41
*/
public class BeanDefinition {
/**
* <p>bean对应的全类名</p>
*/
private String beanClassName;
/**
* <p>是否懒加载</p>
*/
private boolean lazyInit = false;
/**
* <p>保存在IOC容器时的key值</p>
*/
private String factoryBeanName;
public String getBeanClassName() {
return beanClassName;
}
public void setBeanClassName(String beanClassName) {
this.beanClassName = beanClassName;
}
public boolean isLazyInit() {
return lazyInit;
}
public void setLazyInit(boolean lazyInit) {
this.lazyInit = lazyInit;
}
public String getFactoryBeanName() {
return factoryBeanName;
}
public void setFactoryBeanName(String factoryBeanName) {
this.factoryBeanName = factoryBeanName;
}
}
4.BeanWrapper
知识温故: BeanWrapper
主要用于封装创建后的对象实例(bean
)。
package org.springframework.beans;
/**
* <p>bean的包装类</p>
* @author Bosen
* @date 2021/9/10 14:48
*/
public class BeanWrapper {
/**
* <p>回由该对象包装的bean实例</p>
*/
private Object wrappedInstance;
public BeanWrapper(Object wrappedInstance) {
this.wrappedInstance = wrappedInstance;
}
/**
* <p>返回包装的bean实例的类型</p>
*/
private Class<?> wrappedClass;
public Object getWrappedInstance() {
return wrappedInstance;
}
public void setWrappedInstance(Object wrappedInstance) {
this.wrappedInstance = wrappedInstance;
}
public Class<?> getWrappedClass() {
return wrappedClass;
}
public void setWrappedClass(Class<?> wrappedClass) {
this.wrappedClass = wrappedClass;
}
}
5.BeanDefinitionReader
知识温故: BeanDefinitionReader
从名称就知道是一个BeanDefinition
的读取器,完成对BeanDefinition
读取的具体工作!
package org.springframework.beans.factory.support;
import org.springframework.beans.factory.config.BeanDefinition;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* <p>用于扫描bd</p>
* @author Bosen
* @date 2021/9/11 14:00
*/
public class BeanDefinitionReader {
/**
* <p>存储扫描出来的bean的全类名</p>
*/
private List<String> registryBeanClasses = new ArrayList<>();
public BeanDefinitionReader(String scanPackage) throws Exception {
doScan(scanPackage);
}
/**
* <p>扫描包下的类</p>
* @param scanPackage 包名
*/
public void doScan(String scanPackage) throws Exception {
// 将包名转为文件路径
URL url = this.getClass().getResource("/" + scanPackage.replaceAll("\\.", "/"));
if (url == null) {
throw new Exception("包" + scanPackage + "不存在!");
}
File classPath = new File(url.getFile());
for (File file : classPath.listFiles()) {
if (file.isDirectory()) {
doScan(scanPackage + "." +file.getName());
} else {
if (!file.getName().endsWith(".class")) {
// 如果不是class文件则跳过
continue;
}
String className = scanPackage + "." + file.getName().replace(".class", "");
registryBeanClasses.add(className);
}
}
}
/**
* <p>将扫描到的类信息转化为bd对象</p>
*/
public List<BeanDefinition> loadBeanDefinitions() {
List<BeanDefinition> result = new ArrayList<>();
try {
for (String className : registryBeanClasses) {
Class<?> beanClass = Class.forName(className);
if (beanClass.isInterface()) {
// 如果是接口则跳过
continue;
}
result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getSimpleName()), beanClass.getName()));
Class<?>[] interfaces = beanClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
result.add(doCreateBeanDefinition(anInterface.getName(), beanClass.getName()));
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return result;
}
/**
* <p>将类信息转化为beanDefinition</p>
*/
public BeanDefinition doCreateBeanDefinition(String factoryBeanName, String beanClassName) {
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanClassName(beanClassName);
beanDefinition.setFactoryBeanName(factoryBeanName);
return beanDefinition;
}
/**
* <p>将类名首字母小写</p>
*/
private String toLowerFirstCase(String simpleName) {
char[] chars = simpleName.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
三、context模块的编写
1.ApplicationContext
在迷你版中,我们只让ApplicationContext
定义关键方法refresh
即可。
package org.springframework.context;
import org.springframework.beans.factory.BeanFactory;
/**
* <p>容器顶层接口</p>
* @author Bosen
* @date 2021/9/10 15:18
*/
public interface ApplicationContext extends BeanFactory {
void refresh() throws Exception;
}
2.AbstractApplicationContext
将接口ApplicationContext
定义完成后,紧接着的工作当然是编写其子类啦!~~在ApplicationContext
众多子类中,完成主要工作的就是AbstractApplicationContext
(IOC容器的创建、beanDefinition
的扫描创建、bean
的创建、依赖注入等都在这里完成)
注意:以下编写的代码只实现了IOC(控制反转)功能,并未实现DI(依赖注入)功能,DI功能的编写将放在文章后半段,前半段完成IOC的编写和测试!
package org.springframework.context.support;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import java.util.List;
import java.util.Map;
/**
* <p>容器抽象类</p>
* @author Bosen
* @date 2021/9/10 15:19
*/
public abstract class AbstractApplicationContext extends DefaultListableBeanFactory implements ApplicationContext {
protected BeanDefinitionReader reader;
/**
* <p>保存单例对象</p>
*/
private Map<String, Object> factoryBeanObjectCache = new HashMap<>();
/**
* <p>保存包装对象</p>
*/
private Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>();
@Override
public void refresh() throws Exception {
// 扫描需要扫描的包,并把相关的类转化为beanDefinition
List<BeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
// 注册,将beanDefinition放入IOC容器存储
doRegisterBeanDefinition(beanDefinitions);
// 将非懒加载的类初始化
doAutowired();
}
/**
* <p>将beanDefinition放入IOC容器存储</p>
*/
private void doRegisterBeanDefinition(List<BeanDefinition> beanDefinitions) throws Exception {
for (BeanDefinition beanDefinition : beanDefinitions) {
if (super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
throw new Exception(beanDefinition.getFactoryBeanName() + "已经存在!");
}
super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
}
}
/**
* <p>将非懒加载的类初始化</p>
*/
private void doAutowired() {
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : super.beanDefinitionMap.entrySet()) {
String beanName = beanDefinitionEntry.getKey();
if (!beanDefinitionEntry.getValue().isLazyInit()) {
getBean(beanName);
}
}
}
@Override
public Object getBean(String beanName) {
return null;
}
}
3.AnnotationConfigApplicationContext
知识温故: 之前我们讲述了,ApplicationContext
的子类是有多种的(迷你版中只实现一种),比如我们最熟知的ClassPathXmlApplicationContext
和AnnotationConfigApplicationContext
,前者的配置基于xml文件,后者基于注解,他们对于配置的解读也会有不同的方式,但他们其他任务的处理流程基本都是一致的。因此,为了让我们编写的代码有更高的可用性,我们可以将他们不同部分的代码放在他们自己内部中完成,而相同逻辑部分的代码则交给他们共同的父类AbstractApplicationContext
来完成。
弱弱的说一句:这不明摆着"坑爹"嘛~~!!。
package org.springframework.context.annotation;
import org.springframework.annotation.ComponentScan;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.context.support.AbstractApplicationContext;
/**
* <p>基于注解作为配置的容器</p>
* @author Bosen
* @date 2021/9/10 15:32
*/
public class AnnotationConfigApplicationContext extends AbstractApplicationContext {
public AnnotationConfigApplicationContext(Class annotatedClass) throws Exception {
// 初始化父类bdw
super.reader = new BeanDefinitionReader(getScanPackage(annotatedClass));
refresh();
}
@Override
public void refresh() throws Exception {
// 交给父类完成
super.refresh();
}
/**
* <p>获取@ComponentScan中的value值</p>
*/
public String getScanPackage(Class annotatedClass) throws Exception {
// 判断是否有ComponentScan注解
if (!annotatedClass.isAnnotationPresent(ComponentScan.class)) {
throw new Exception("请为注解配置类加上@ComponentScan注解!");
}
ComponentScan componentScan =
(ComponentScan) annotatedClass.getAnnotation(ComponentScan.class);
return componentScan.value().trim();
}
}
至此,迷你版Spring的IOC功能已经实现了,接下来我们做一个测试看看Bean工厂是否可以正常运作!
四、迷你版IOC功能测试
测试很简单,只需要编写一个配置类ApplicationConfig
并且加上注解@Component
,以及随便的编写几个类即可(如:TestController
、TestService
、TestDAO
)
1.配置类ApplicationConfig
package org.springframework.test.config;
import org.springframework.annotation.ComponentScan;
/**
* <p>配置类</p>
* @author Bosen
* @date 2021/9/11 14:11
*/
@ComponentScan("org.springframework.test")
public class ApplicationConfig {}
2.启动类ApplicationTest
package org.springframework.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.config.ApplicationConfig;
/**
* <p>测试启动类</p>
* @author Bosen
* @date 2021/9/12 0:25
*/
public class ApplicationTest {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(ApplicationConfig.class);
applicationContext.getBean("");
}
}
3.开始迷你IOC的测试
在如下位置打个断点,并debug一下
断点运行后,查看控制台的信息,目光看到beanDefinition
中,会发现,在配置类中配置的包org.springframework.test
下的所有类都已经被注册为beanDefinition
,并存入了beanDefinitionMap
中。这表明,我们的IOC功能已经成功实现了!!
IOC功能完成后,我们将继续来完成接下来的DI功能~~!
五、实现DI依赖注入
在源码篇中,我们介绍了Spring DI的入口是ApplicationContest
中调用的getBean
方法,因此,我们迷你版Spring的DI实现也放到这里来实现。
回到上述的IOC实现中的AbstractApplicationContext
,重写其getBean
方法,实现DI。(主要工作: 1.通过工厂中的beanDefinition
实例化bean
->2.将实例化后的bean
使用beanWrapper
包装->3.利用反射机制对bean
进行依赖注入的操作,具体代码如下)
@Override
public Object getBean(String beanName) {
BeanDefinition beanDefinition = super.beanDefinitionMap.get(beanName);
try {
// 通过bd实例化bean
Object instance = instantiateBean(beanDefinition);
if (instance == null) {
return null;
}
// 将实例化后的bean使用bw包装
BeanWrapper beanWrapper = new BeanWrapper(instance);
this.factoryBeanInstanceCache.put(beanDefinition.getBeanClassName(), beanWrapper);
// 开始注入操作
populateBean(instance);
return instance;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* <p>通过bd,实例化bean</p>
*/
private Object instantiateBean(BeanDefinition beanDefinition) {
Object instance = null;
String className = beanDefinition.getBeanClassName();
try {
// 先判断单例池中是否存在该类的实例
if (this.factoryBeanObjectCache.containsKey(className)) {
instance = this.factoryBeanObjectCache.get(className);
} else {
Class<?> clazz = Class.forName(className);
instance = clazz.newInstance();
this.factoryBeanObjectCache.put(beanDefinition.getFactoryBeanName(), instance);
}
} catch (Exception e) {
e.printStackTrace();
}
return instance;
}
/**
* <p>开始注入操作</p>
*/
public void populateBean(Object instance) {
Class clazz = instance.getClass();
// 判断是否有Controller、Service、Component、Repository等注解标记
if (!(clazz.isAnnotationPresent(Component.class) ||
clazz.isAnnotationPresent(Controller.class) ||
clazz.isAnnotationPresent(Service.class) ||
clazz.isAnnotationPresent(Repository.class))) {
return;
}
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 如果属性没有被Autowired标记,则跳过
if (!field.isAnnotationPresent(Autowired.class)) {
continue;
}
String autowiredBeanName = field.getType().getName();
field.setAccessible(true);
try {
field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
六、迷你版DI功能测试
1.TestDAO
编写一个DAO
层对象用于测试
package org.springframework.test.dao;
import org.springframework.annotation.Repository;
/**
* @author Bosen
* @date 2021/9/11 22:29
*/
@Repository
public class TestDAO {
public String echo() {
return "This is TestDAO#echo!!!";
}
}
2.TestService
编写一个service
层对象用于测试,其中依赖DAO
层对象TestDAO
。
package org.springframework.test.service;
import org.springframework.annotation.Autowired;
import org.springframework.annotation.Service;
import org.springframework.test.dao.TestDAO;
/**
* @author Bosen
* @date 2021/9/11 22:30
*/
@Service
public class TestService {
@Autowired
TestDAO testDAO;
public void echo() {
System.out.println(testDAO.echo());
}
}
3.编写测试类
编写一个测试类,通过getBean
方式获取service
层的对象,并调用该对象的echo方法~~!
package org.springframework.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.config.ApplicationConfig;
import org.springframework.test.service.TestService;
/**
* <p>测试启动类</p>
* @author Bosen
* @date 2021/9/12 0:25
*/
public class ApplicationTest {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(ApplicationConfig.class);
TestService service = (TestService) applicationContext.getBean("testService");
service.echo();
}
}
4.开始测试
直接运行测试类中的main
方法,查看控制台输出信息如下:
可以看到,在DAO
层echo
方法中的字符串 “This is TestDAO#echo!!!” 已经通过service
的调用成功输出了出来,表明testDAO
对象已经成功的注入到testService
对象中,我们的DI功能已经成功实现了~~!!