前言
深度模仿 Spring 源码,完成 IOC \ DI 操作,较上一个版本,内容更加贴近 Spring,主要也是先学习设计思想,为后续学习源码做铺垫。通过简单易懂的代码学习思想,带着思想去理解 Spring,加深学习效果,更加容易吸收。
一、从 Servlet 到 ApplicationContext
在上一篇中,我们已经知道 SpringMVC 的入口是 DispatcherServlet,我们通过自己手写的DispatcherServlet类继承了 HttpServlet类,并在 init 方法中完成了一系列的初始化动作。而在我们使用的 Spring 经验中,我们见的最多的就是 ApplicationContext,似乎Spring 托管的所有的实例 Bean都可以通过getBean()方法来获得。那么ApplicationContext又是从何而来的呢?从 Spring 的源码中,我们可以通过类图看到DispatcherServlet的关系网如下:
DispatcherServlet 继承了FrameworkServlet,FrameworkServlet继承了 HttpServletBean,HttpServletBean继承了 HttpServlet。在HttpServletBean的 init()方法中调用了FrameworkServlet中的 initServletBean()方法,在initServletBean()方法中初始化了 webApplicationContext实例。在初始化webApplicationContext实例的同时,又调用了DispatcherServlet重写的onRefresh()方法,在DispatcherServlet重写的onRefresh()方法中通过调用 initStrategies()完成了 初始化SpringMVC的九大组件。
其实,上面复杂的调用关系,我们可以简单的得出一个结论:就是在 Servlet的init()方法中初始化了IOC容器和SpringMVC所依赖的九大组件。
二、项目环境搭建
application.properties配置【使用 properties 文件只是为了代码里面好操作】
#定义需要扫描的包路径 scanPackage=com.mmt.imitate
pom.xml 配置
只引入一个 servlet 依赖的包
<properties>
<servlet.api.version>2.4</servlet.api.version>
</properties>
<dependencies>
<!-- required -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet.api.version}</version>
<scope>provided</scope>
</dependency>
<!-- required -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
web.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>mmt imitate Web Application</display-name>
<servlet>
<servlet-name>mmt-imitate</servlet-name>
<servlet-class>com.mmt.imitate.spring.framework.webmvc.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mmt-imitate</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
实现我们自己的 DispatcherServlet类
public class DispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
}
三、IOC 顶层结构设计
annotation 模块【自定义注解】
annotation 注解与上一篇稳重中的版本一致,可直接使用mini 版里面写的,这里直接复制过来
自定义 @MmtController
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MmtController {
String value() default "";
}
自定义 @MmtService
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MmtService {
String value() default "";
}
自定义 @MmtAutowired
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MmtAutowired {
String value() default "";
}
自定义 @MmtRequestMapping
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MmtRequestMapping {
String value() default "";
}
自定义 @MmtRequestParam
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MmtRequestParam {
String value() default "";
}
Beans模块【配置封装】
BeanDefinition
package com.mmt.imitate.spring.framework.beans.config;
import lombok.Data;
@Data
public class BeanDefinition {
private String factoryBeanName;
private String beanClassName;
}
BeanWrapper
package com.mmt.imitate.spring.framework.beans;
public class BeanWrapper {
/**
* 原始对象
*/
private Object wrapperInstance;
/**
* 对应的 class
*/
private Class<?> wrappedClass;
public BeanWrapper(Object instance) {
this.wrapperInstance = instance;
this.wrappedClass = instance.getClass();
}
public Object getWrapperInstance() {
return wrapperInstance;
}
/**
* 返回代理以后的 class,有可能是 $Proxy0
* @return
*/
public Class<?> getWrappedClass() {
return wrappedClass;
}
}
context 模块【IOC 容器】
ApplicationContext
package com.mmt.imitate.spring.framework.context;
/**
* 完成 bean 的创建和 DI
*/
public class ApplicationContext {
// TODO 先将类创建好,后面再完善
}
DispatcherServlet 完善
同上一篇 mini 版一样,我们在上一版的基础上进行优化,这次,我们使用ApplicationContext 来完成 Bean 的初始化过程,具体看代码吧!
package com.mmt.imitate.spring.framework.webmvc.servlet;
import com.mmt.imitate.spring.framework.context.ApplicationContext;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
/**
* 高仿 spring - mvc 入口类
* <p>
* 委派模式
* 职责:负责任务调度,请求分发
*
*/
public class DispatcherServlet extends HttpServlet {
private ApplicationContext applicationContext;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//TODO 委派,根据URL去找到一个对应的Method并通过response返回
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("500 Exception,Detail : " + Arrays.toString(e.getStackTrace()));
}
}
/**
* 通过 init方法来进行 ioc 容器的初始化动作 和 初始化 MVC 相关的 9 大组件
*
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
// 初始化 IOC 相关
applicationContext = new ApplicationContext(config.getInitParameter("contextConfigLocation"));
//TODO 初始化 MVC 部分,下次继续
}
/**
* mvc 响应
*
* @param req
* @param resp
* @throws Exception
*/
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
//TODO
}
}
注意 init 方法,在 init 方法中,进行了 IOC 容器的初始化以及 MVC 相关的初始化动作,这里需要完善一下 ApplicationContext 类:
package com.mmt.imitate.spring.framework.context;
import com.mmt.imitate.spring.framework.annotation.MmtAutowired;
import com.mmt.imitate.spring.framework.annotation.MmtController;
import com.mmt.imitate.spring.framework.annotation.MmtService;
import com.mmt.imitate.spring.framework.beans.BeanWrapper;
import com.mmt.imitate.spring.framework.beans.config.BeanDefinition;
import com.mmt.imitate.spring.framework.beans.support.BeanDefinitionReader;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 完成 bean 的创建和 DI
*
*/
public class ApplicationContext {
private BeanDefinitionReader reader;
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();
private Map<String, Object> factoryBeanObjectCache = new HashMap<String, Object>();
private Map<String, BeanWrapper> factoryBeanInstanceCache = new HashMap<String, BeanWrapper>();
public ApplicationContext(String... configLocations) {
//1、 加载配置文件
reader = new BeanDefinitionReader(configLocations);
try {
//2、解析配置文件,封装成BeanDefinition
List<BeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
//3、把BeanDefintion缓存起来
doRegistBeanDefinition(beanDefinitions);
//4、自动装配
doAutowrited();
} catch (Exception e) {
e.printStackTrace();
}
}
private void doRegistBeanDefinition(List<BeanDefinition> beanDefinitions) throws Exception {
for (BeanDefinition beanDefinition : beanDefinitions) {
if (this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
throw new Exception("The " + beanDefinition.getFactoryBeanName() + "is exists");
}
beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
}
}
/**
* 自动装配
*/
private void doAutowrited() {
//调用getBean()
//这一步,所有的Bean并没有真正的实例化,还只是配置阶段
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
String beanName = beanDefinitionEntry.getKey();
getBean(beanName);
}
}
/**
* Bean的实例化,DI是从而这个方法开始的
*
* @param beanName
* @return
*/
public Object getBean(String beanName) {
//1、先拿到BeanDefinition配置信息
BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
//2、反射实例化newInstance();
Object instance = instantiateBean(beanName, beanDefinition);
//3、封装成一个叫做BeanWrapper
BeanWrapper beanWrapper = new BeanWrapper(instance);
//4、保存到IoC容器
factoryBeanInstanceCache.put(beanName, beanWrapper);
//5、执行依赖注入
populateBean(beanName, beanDefinition, beanWrapper);
return beanWrapper.getWrapperInstance();
}
/**
* 创建真正的实例对象
*
* @param beanName
* @param beanDefinition
* @return
*/
private Object instantiateBean(String beanName, BeanDefinition beanDefinition) {
String className = beanDefinition.getBeanClassName();
Object instance = null;
try {
Class<?> clazz = Class.forName(className);
//2、默认的类名首字母小写
instance = clazz.newInstance();
this.factoryBeanObjectCache.put(beanName, instance);
} catch (Exception e) {
e.printStackTrace();
}
return instance;
}
private void populateBean(String beanName, BeanDefinition beanDefinition, BeanWrapper beanWrapper) {
//可能涉及到循环依赖?
//A{ B b}
//B{ A b}
//用两个缓存,循环两次
//1、把第一次读取结果为空的BeanDefinition存到第一个缓存
//2、等第一次循环之后,第二次循环再检查第一次的缓存,再进行赋值
Object instance = beanWrapper.getWrapperInstance();
Class<?> clazz = beanWrapper.getWrappedClass();
//在Spring中@Component,这里只是单纯的处理了 controller 和 service 两个注解
if (!(clazz.isAnnotationPresent(MmtController.class) || clazz.isAnnotationPresent(MmtService.class))) {
return;
}
//把所有的包括private/protected/default/public 修饰字段都取出来
for (Field field : clazz.getDeclaredFields()) {
if (!field.isAnnotationPresent(MmtAutowired.class)) {
continue;
}
MmtAutowired autowired = field.getAnnotation(MmtAutowired.class);
//如果用户没有自定义的beanName,就默认根据类型注入
String autowiredBeanName = autowired.value().trim();
if ("".equals(autowiredBeanName)) {
//field.getType().getName() 获取字段的类型
autowiredBeanName = field.getType().getName();
}
//暴力访问
field.setAccessible(true);
try {
if (this.factoryBeanInstanceCache.get(autowiredBeanName) == null) {
continue;
}
//ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实例
field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
} catch (IllegalAccessException e) {
e.printStackTrace();
continue;
}
}
}
}
由于代码太多,不一一贴于此处,如有需要源码的小伙伴,可到Gitee进行下载:https://gitee.com/heliang086/imitate_spring.git
后续更新,初始化 SpringMVC 9大组件及使用。