05. 手写Spring核心框架
Posted IT BOY
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了05. 手写Spring核心框架相关的知识,希望对你有一定的参考价值。
目录
05 手写Spring核心框架
在我们还没有开始Spring源码分析之前,先尝试模仿Spring手写一套类似Spring的核心骨架,这套代码能够帮助我们更好的理解Spring源码的实现架构,帮助我们在源码分析时构建更加清晰的思维脉络。所以这部分其实非常重要,当你在后续的源码分析中迷失时,只需要回到这里,看看我们在手写这段框架时关注了那些组件,就可以把我们的焦点拉回到主干这条线上,沿着这条线就不会迷失。
这部分代码,我已经上传到github上,可以参考。
https://github.com/ChenMingMing821/myspring5-action.git
Pt1 手写IoC/DI
Pt1.1 流程设计
IoC + DI是在启动的时候初始化的,负责管理Bean的生命周期和依赖关系。
IoC和DI主要涉及核心类:
-
DispatcherServlet:在web.xml定义的启动类Servlet。负责Web容器初始化,以及拦截客户端请求并完成调度和分发;
-
ApplicationContext:Spring运行上下文。负责读取Spring配置,扫描Bean,保存IoC容器。
-
BeanDefinition:保存Spring Bean的定义信息。
-
BeanWrapper:Spring对BeanDefinition的代理,包含了Bean定义和实例化对象信息。
-
BeanDefinitionReader:负责加载Spring配置,读取Bean定义。
IoC和DI是在Spring启动的过程中完成的,其中DispatcherServlet是入口,初始化整个流程是在ApplicationContext进行控制的。 过程大体分为以下几个步骤:
-
加载/解析Spring配置文件,扫描Bean;
-
将读取的Bean定义封装成BeanDefinition;
-
将Bean注册到IoC容器(未实例化);
-
完成依赖注入(自动);
Pt1.2 基础配置
application.properties
Spring IoC的自动扫描需要配置scanPackage,即扫描根路径,Spring会扫描根路径下所有Bean定义。当然在Spring中,该配置是在Spring的xml中配置的,这里我们直接以properties文件的形式进行配置,简化代码读取的逻辑,本质上是一样的。
# 配置类扫描包路径
scanPackage=com.demo.spring.simulation.v5.test
pom.xml
从依赖上可以看出,核心只有Servlet,引入日志、Lombok和Junit是用来简化开发和输出一些验证信息。没有任何的Spring组件依赖,我们要自己手写Spring的核心流程,实现上还是尽量干净一些。
<?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>org.spring5</groupId>
<artifactId>myspring5-action</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>myspring5-action Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>myspring5-action</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
web.xml
配置非常简单,定义了Web容器启动Servlet(MyDispatcherServlet)和配置路径。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>myspring5-action</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>com.demo.spring.simulation.v5.servlet.MyDispatcherServlet</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>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
Pt1.3 注解定义
Spring注解比较多,根据模拟过程中的需要选择性的实现部分。
@MyController
package com.demo.spring.simulation.v5.annotation;
import java.lang.annotation.*;
/**
* 自定义Controller注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
String value() default "";
}
@MyService
package com.demo.spring.simulation.v5.annotation;
import java.lang.annotation.*;
/**
* 自定义Service注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
@MyAutowired
package com.demo.spring.simulation.v5.annotation;
import java.lang.annotation.*;
/**
* 自定义Autowired注解
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
String value() default "";
}
@MyRequestMapping
package com.demo.spring.simulation.v5.annotation;
import java.lang.annotation.*;
/**
* 自定义RequestMapping注解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
String value() default "";
}
@MyRequestParam
package com.demo.spring.simulation.v5.annotation;
import java.lang.annotation.*;
/**
* 自定义RequestParam注解
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
String value() default "";
}
Pt1.4 核心代码
DispatcherServlet
DispatcherServlet作为启动的整个入口,init()负责初始化IoC、DI、MVC和AOP的环境。这里先介绍IoC和DI的加载过程,从代码可以看出,初始化过程是在ApplicationContext中完成的。
DispatherServlet#init是入口,我们从这里开始看整个流程的处理。
/**
* DispatcherServlet负责请求调度和分发。
*/
public class MyDispatcherServlet extends HttpServlet {
// Spring配置文件路径
private static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";
// Spring上下文,Spring IoC容器
private MyApplicationContext applicationContext;
@Override
public void init(ServletConfig config) throws ServletException {
log.info("DispatcherServlet -> Create Web Server Starting.");
// 1、初始化ApplicationContext。ApplicationContext包含了Spring核心IoC容器,完成Bean扫描、初始化和DI。
log.info("DispatcherServlet -> Init Spring IoC/DI Starting.");
applicationContext = new MyApplicationContext(config.getInitParameter(CONTEXT_CONFIG_LOCATION));
log.info("DispatcherServlet -> Init Spring IoC/DI Finished.");
// TODO
log.info("DispatcherServlet -> Create Web Server Finished.");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO
}
}
ApplicationContext
DispatcherServlet#init直接调用ApplicationContext的构造器执行IoC初始化,根据ApplicationContext构造器的逻辑一步一步来看代码逻辑。
package com.demo.spring.simulation.v5.context;
import com.demo.spring.simulation.v5.annotation.MyAutowired;
import com.demo.spring.simulation.v5.annotation.MyController;
import com.demo.spring.simulation.v5.annotation.MyService;
import com.demo.spring.simulation.v5.aop.MyJdkDynamicAopProxy;
import com.demo.spring.simulation.v5.aop.config.MyAopConfig;
import com.demo.spring.simulation.v5.aop.support.MyAdvisedSupport;
import com.demo.spring.simulation.v5.beans.MyBeanWrapper;
import com.demo.spring.simulation.v5.beans.config.MyBeanDefinition;
import com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* 完成Bean的扫描、创建和DI。
*/
@Slf4j
public class MyApplicationContext {
// 负责读取Bean配置
private MyBeanDefinitionReader reader;
// 存储注册Bean定义的IoC容器
private Map<String, MyBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, MyBeanDefinition>();
// 存放单例的IoC容器
private Map<String, Object> factoryBeanObjectCache = new HashMap<String, Object>();
// 通用的IoC容器
private Map<String, MyBeanWrapper> factoryBeanInstanceCache = new HashMap<String, MyBeanWrapper>();
/**
* Spring上下文环境初始化
*
* @param configLocations 配置文件路径
*/
public MyApplicationContext(String... configLocations) {
// 1、加载、解析配置文件,扫描相关的类。
reader = new MyBeanDefinitionReader(configLocations);
log.info("ApplicationContext -> 1、加载、解析配置文件,扫描相关的类。");
try {
// 2、将扫描的Bean封装成BeanDefinition。
List<MyBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
log.info("ApplicationContext -> 2、将扫描的Bean封装成BeanDefinition。");
// 3、注册,把BeanDefintion缓存到容器。
doRegistBeanDefinition(beanDefinitions);
log.info("ApplicationContext -> 3、注册,把BeanDefintion缓存到容器。");
// 4、完成自动依赖注入。
doAutowrited();
log.info("ApplicationContext -> 4、完成自动依赖注入。");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 完成Bean的实例化和自动依赖注入(非延迟加载的场景)。
*/
private void doAutowrited() {
// 到这步,所有的Bean并没有真正的实例化,还只是配置阶段。
for (Map.Entry<String, MyBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
String beanName = beanDefinitionEntry.getKey();
// getBean才真正完成依赖注入
getBean(beanName);
}
}
/**
* 把BeanDefintion缓存起来
*
* @param beanDefinitions 通过扫描配置文件获取的Bean定义
* @throws Exception
*/
private void doRegistBeanDefinition(List<MyBeanDefinition> beanDefinitions) throws Exception {
log.info("ApplicationContext -> 缓存BeanDefinition信息。");
for (MyBeanDefinition beanDefinition : beanDefinitions) {
// Bean在IoC容器中名称必须唯一
if (this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
throw new Exception("The " + beanDefinition.getFactoryBeanName() + "is exists");
}
// 分别用两种名称存储,便于查找
beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
}
}
/**
* Bean的实例化和DI是从这个方法开始的。
*
* @param beanName
* @return
*/
public Object getBean(String beanName) {
log.info("ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。");
// 1、先拿到BeanDefinition配置信息
MyBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
// 2、反射实例化newInstance();
Object instance = instantiateBean(beanName, beanDefinition);
// 3、封装成一个叫做BeanWrapper
MyBeanWrapper beanWrapper = new MyBeanWrapper(instance);
// 4、保存到IoC容器
factoryBeanInstanceCache.put(beanName, beanWrapper);
// 5、执行依赖注入
populateBean(beanName, beanDefinition, beanWrapper);
// 6、返回对象
return beanWrapper.getWrapperInstance();
}
public Object getBean(Class<?> beanClass) {
return getBean(beanClass.getName());
}
/**
* DI核心逻辑。
*
* @param beanName
* @param beanDefinition
* @param beanWrapper
*/
private void populateBean(String beanName, MyBeanDefinition beanDefinition, MyBeanWrapper beanWrapper) {
log.info("ApplicationContext -> 完成依赖注入核心逻辑。");
// TODO 可能涉及到循环依赖待解决,如果依赖对象还未实例化,注入的示例为Null引发后续问题,这里需要考虑如何解决。
// 1、拿到当前Bean实例化对象
Object instance = beanWrapper.getWrapperInstance();
// 2、拿到当前Bean的类信息
Class<?> clazz = beanWrapper.getWrapperClass();
// 3、只有注解的类,才执行依赖注入
if (!(clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyService.class))) {
return;
}
// 把所有的包括private/protected/default/public 修饰字段都取出来
// TODO 这里只考虑接口注入的方式,实际还要考虑构造器注入和Setter注入。
for (Field field : clazz.getDeclaredFields()) {
// 是否被Autowired标记为自动注入
if (!field.isAnnotationPresent(MyAutowired.class)) {
continue;
}
MyAutowired autowired = field.getAnnotation(MyAutowired.class);
// 如果用户没有自定义的beanName,就默认根据类型注入
String autowiredBeanName = autowired.value().trim();
if ("".equals(autowiredBeanName)) {
// field.getType().getName() 获取字段的类型的全限定名
autowiredBeanName = toLowerFirstCase(field.getType().getSimpleName());
}
// 暴力访问
field.setAccessible(true);
try {
// 获取对应名称的bean实例对象 TODO
// 此处没有考虑Bean的实例化顺序,可能需要注入的对象此时还没有完成实例化,在IoC容器中无法正确获取。不过除了在初始化时触发DI,
// 在实际调用的时候,通过getBean()获取对象时,仍然会触发DI操作。
if (this.factoryBeanInstanceCache.get(autowiredBeanName) == null) {
continue;
}
// ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实例
field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
} catch (IllegalAccessException e) {
e.printStackTrace();
continue;
}
}
}
/**
* 创建真正的实例对象
*
* @param beanName
* @param beanDefinition
* @return
*/
private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
log.info("ApplicationContext -> 通过反射创建Bean实例。");
String className = beanDefinition.getBeanClassName();
Object instance = null;
try {
Class<?> clazz = Class.forName(className);
instance = clazz.newInstance();
// 默认的类名首字母小写
this.factoryBeanObjectCache.put(beanName, instance);
} catch (Exception e) {
e.printStackTrace();
}
return instance;
}
/**
* 已注册所有Bean的名称
*
* @return
*/
public String[] getBeanDefinitionNames() {
return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
}
/**
* 已注册Bean的数量
*
* @return
*/
public int getBeanDefinitionCount() {
return this.beanDefinitionMap.size();
}
public Properties getConfig() {
return this.reader.getConfig();
}
/**
* 将大写字母转换为小写
*
* @param simpleName
* @return
*/
private String toLowerFirstCase(String simpleName) {
char[] chars = simpleName.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
BeanDefinition
package com.demo.spring.simulation.v5.beans.config;
import lombok.Data;
/**
* Spring Bean定义信息
*/
@Data
public class MyBeanDefinition {
// Bean全路径类名
private String beanClassName;
// Bean在IoC容器中名称
private String factoryBeanName;
}
BeanDefinitionReader
package com.demo.spring.simulation.v5.beans.support;
import com.demo.spring.simulation.v5.beans.config.MyBeanDefinition;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* 扫描配置文件,读取Bean定义
*/
@Slf4j
public class MyBeanDefinitionReader {
// 保存扫描的结果
private List<String> regitryBeanClasses = new ArrayList<String>();
// 保存配置信息
private Properties contextConfig = new Properties();
public MyBeanDefinitionReader(String... configLocations) {
log.info("BeanDefinitionReader -> 构造器执行开始。");
// 1、读取配置信息。
doLoadConfig(configLocations[0]);
log.info("BeanDefinitionReader -> 1、读取配置信息。");
// 2、扫描配置文件中的配置的相关的类。
doScanner(contextConfig.getProperty("scanPackage"));
log.info("BeanDefinitionReader -> 2、扫描配置文件中的配置的相关的类。");
log.info("BeanDefinitionReader -> 构造器执行完成。");
}
/**
* 将Bean封装为BeanDefinition
*
* @return
*/
public List<MyBeanDefinition> loadBeanDefinitions() {
log.info("BeanDefinitionReader -> 将扫描的Bean信息封装成BeanDefinition。");
List<MyBeanDefinition> result = new ArrayList<MyBeanDefinition>();
try {
for (String className : regitryBeanClasses) {
Class<?> beanClass = Class.forName(className);
// 接口不能实例化
if (beanClass.isInterface()) {
continue;
}
// BeanName有三种情况:
// 1、默认是类名首字母小写
// 2、自定义名称
// 3、接口注入
result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getSimpleName()), beanClass.getName()));
// 如果是多个实现类,只能覆盖
for (Class<?> i : beanClass.getInterfaces()) {
result.add(doCreateBeanDefinition(i.getName(), beanClass.getName()));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private MyBeanDefinition doCreateBeanDefinition(String beanName, String beanClassName) {
MyBeanDefinition beanDefinition = new MyBeanDefinition();
beanDefinition.setFactoryBeanName(beanName);
beanDefinition.setBeanClassName(beanClassName);
return beanDefinition;
}
/**
* 从配置文件中加载Spring配置信息
*
* @param contextConfigLocation
*/
private void doLoadConfig(String contextConfigLocation) {
log.info("BeanDefinitionReader -> 加载Spring配置文件。");
InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation.replaceAll("classpath:", ""));
try {
contextConfig.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 根据配置的basePackage扫描获取Bean定义
*
* @param scanPackage
*/
private void doScanner(String scanPackage) {
log.info("BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。");
//jar 、 war 、zip 、rar
URL url = this.getClass().getClassLoader().getResource(scanPackage.replaceAll("\\\\.", "/"));
File classPath = new File(url.getFile());
//当成是一个ClassPath文件夹
for (File file : classPath.listFiles()) {
if (file.isDirectory()) {
doScanner(scanPackage + "." + file.getName());
} else {
if (!file.getName().endsWith(".class")) {
continue;
}
//全类名 = 包名.类名
String className = (scanPackage + "." + file.getName().replace(".class", ""));
//Class.forName(className);
regitryBeanClasses.add(className);
}
}
}
/**
* 将大写字母转换为小写
*
* @param simpleName
* @return
*/
private String toLowerFirstCase(String simpleName) {
char[] chars = simpleName.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
/**
* 获取配置信息
*
* @return
*/
public Properties getConfig() {
return this.contextConfig;
}
}
BeanWrapper
package com.demo.spring.simulation.v5.beans;
/**
* Spring IoC容器对Bean生成的代理类
*/
public class MyBeanWrapper {
// Bean的实例化对象
private Object wrappedInstance;
// Bean的Class信息
private Class<?> wrapperClass;
public MyBeanWrapper(Object wrappedInstance) {
this.wrappedInstance = wrappedInstance;
this.wrapperClass = wrappedInstance.getClass();
}
public Object getWrapperInstance() {
return this.wrappedInstance;
}
// 返回代理Class
public Class<?> getWrapperClass() {
return this.wrapperClass;
}
}
Pt1.5 功能验证
IoC测试我们直接以main启动ApplicationContext的模式来验证,推荐使用DEBUG模式看每一步数据的处理,这里为了简单描述,我直接输出测试代码和结果。
-
测试启动类
/**
* Spring IoC和DI测试类。
*/
public class MyApplicationContextTest {
public static void main(String[] args) {
MyApplicationContext applicationContext = new MyApplicationContext("classpath:application.properties");
// IoC容器初始化时,通过执行getBean完成DI。此处再次调用getBean防止有未被注入的属性。
DemoController demoController = (DemoController) applicationContext.getBean(DemoController.class);
demoController.say();
}
}
-
业务处理入口
@MyController
public class DemoController {
@MyAutowired()
private DemoService demoService;
public void say() {
demoService.say();
}
}
-
业务处理核心类
@MyService
public class DemoService {
public void say() {
System.out.println("执行自定义Service方法。");
}
}
-
测试结果输出
启动测试类,看输出结果。
17:02:30.638 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 构造器执行开始。
17:02:30.645 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 加载Spring配置文件。
17:02:30.648 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 1、读取配置信息。
17:02:30.648 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
17:02:30.649 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
17:02:30.652 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 根据scanPackage路径逐层扫描,获取Bean定义。
17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 2、扫描配置文件中的配置的相关的类。
17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 构造器执行完成。
17:02:30.653 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 1、加载、解析配置文件,扫描相关的类。
17:02:30.653 [main] INFO com.demo.spring.simulation.v5.beans.support.MyBeanDefinitionReader - BeanDefinitionReader -> 将扫描的Bean信息封装成BeanDefinition。
17:02:30.658 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 2、将扫描的Bean封装成BeanDefinition。
17:02:30.658 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 缓存BeanDefinition信息。
17:02:30.658 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 3、注册,把BeanDefintion缓存到容器。
17:02:30.659 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.659 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.674 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.689 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.689 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.690 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.710 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.712 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.713 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.713 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.714 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.715 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.715 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.715 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.720 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.725 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.725 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.726 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.726 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.726 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.733 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.734 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.734 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.735 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.735 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.735 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.736 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.737 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.738 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.738 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.738 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.747 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.753 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.753 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.755 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.755 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.755 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.758 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.760 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.761 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.764 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.764 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.764 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.767 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.767 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.767 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.770 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.771 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.771 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.773 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.776 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.777 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.779 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.779 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.780 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.782 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
17:02:30.784 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 4、完成自动依赖注入。
17:02:30.785 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 执行getBean(),将BeanDefinition保存到IoC容器并完成自动依赖注入。
17:02:30.785 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 通过反射创建Bean实例。
17:02:30.818 [main] INFO com.demo.spring.simulation.v5.context.MyApplicationContext - ApplicationContext -> 完成依赖注入核心逻辑。
执行自定义Service方法。
Process finished with exit code 0
Pt2 手写MVC
Pt2.1 流程设计
MVC概念中,M为Model,代表数据;V为View,代表展现层,比如JSP、html等;C是控制层,负责整体业务逻辑的处理和调度。
在SpringMVC中,Handler是核心逻辑的处理器,即MVC中的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法。在Controller层中@RequestMapping标注的所有方法都可以看成是一个Handler,只要可以实际处理请求就可以是Handler。
用户在客户端发起请求,请求URL对应C层的具体Handler(处理器),Handler完成逻辑处理后,输出结果数据(即M层),然后包装成View(V层)返回给客户端完成展现。Spring MVC核心类有以下:
-
DispatcherServlet 请求调度
-
HandlerMapping 请求映射
-
HandlerAdapter 请求方法适配器
-
ModelAndView 页面数据封装
-
ViewResolver 视图解析器
-
View 自定义模板引擎
Pt2.2 MVC九大组件
在实现Spring MVC手写源码之前,先来介绍下Spring九大组件,这也是我们在Spring MVC初始化过程中要完成的操作。
【1. HandlerMapping】
HandlerMapping是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler(即Controller)处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事。
【2. HandlerAdapter】
从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。
小结:Handler(即Controller)是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。
【3. HandlerExceptionResolver】
其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。
【4. ViewResolver】
ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。
【5. RequestToViewNameTranslator】
ViewName是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。
【6. LocaleResolver】
解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
【7. ThemeResolver】
用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。
【8. MultipartResolver】
用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。
【9. FlashMapManager】
用来管理FlashMap的,FlashMap主要用在redirect中传递参数。
Pt2.3 基础配置
在配置文件中,我们配置静态资源的根路径。
# 静态资源路径
templateRoot=/webapp/WEB-INF/view
Pt2.4 核心代码
DispatcherServlet
在DispatcherServlet中完成了IoC、DI和MVC的初始化动作。
package com.demo.spring.simulation.v5.servlet;
import com.demo.spring.simulation.v5.annotation.MyController;
import com.demo.spring.simulation.v5.annotation.MyRequestMapping;
import com.demo.spring.simulation.v5.context.MyApplicationContext;
import lombok.extern.slf4j.Slf4j;
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.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* DispatcherServlet负责请求调度和分发。
*/
@Slf4j
public class MyDispatcherServlet extends HttpServlet {
// Spring配置文件路径
private static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";
// Spring上下文,Spring IoC容器
private MyApplicationContext applicationContext;
// 保存请求URL和处理方法的映射关系
private List<MyHandlerMapping> handlerMappings = new ArrayList<MyHandlerMapping>();
// 保存请求映射和处理Handler的关系
private Map<MyHandlerMapping, MyHandlerAdapter> handlerAdapters = new HashMap<MyHandlerMapping, MyHandlerAdapter>();
// 保存所有View解析器
private List<MyViewResolver> viewResolvers = new ArrayList<MyViewResolver>();
@Override
public void init(ServletConfig config) throws ServletException {
log.info("DispatcherServlet -> Create Web Server Starting.");
// 1、初始化ApplicationContext。ApplicationContext包含了Spring核心IoC容器,完成Bean扫描、初始化和DI。
log.info("DispatcherServlet -> Init Spring IoC/DI Starting.");
applicationContext = new MyApplicationContext(config.getInitParameter(CONTEXT_CONFIG_LOCATION));
log.info("DispatcherServlet -> Init Spring IoC/DI Finished.");
// 2、初始化Spring MVC九大组件
log.info("DispatcherServlet -> Init Spring MVC Starting.");
initStrategies(applicationContext);
log.info("DispatcherServlet -> Init Spring MVC Finished.");
log.info("DispatcherServlet -> Create Web Server Finished.");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
log.info("DispatcherServlet -> Receive client request.");
try {
// 3、委派,根据URL去找到一个对应的Method并通过response返回
doDispatch(req, resp);
} catch (Exception e) {
try {
processDispatchResult(req, resp, new MyModelAndView("500"));
} catch (Exception e1) {
e1.printStackTrace();
resp.getWriter().write("500 Exception,Detail : " + Arrays.toString(e.getStackTrace()));
}
}
log.info("DispatcherServlet -> Return client response.");
}
/**
* 完成Spring MVC组件的初始化。
*
* @param context
*/
private void initStrategies(MyApplicationContext context) {
// 1、多文件上传的组件 TODO
// initMultipartResolver(context);
// log.info("DispatcherServlet -> 1、多文件上传的组件");
// 2、初始化本地语言环境 TODO
// initLocaleResolver(context);
// log.info("DispatcherServlet -> 2、初始化本地语言环境");
// 3、初始化模板处理器 TODO
// initThemeResolver(context);
// log.info("DispatcherServlet -> 3、初始化模板处理器");
// 4、初始化HandlerMapping,必须实现。
initHandlerMappings(context);
log.info("DispatcherServlet -> 4、初始化HandlerMapping,必须实现。");
// 5、初始化参数适配器,必须实现。
initHandlerAdapters(context);
log.info("DispatcherServlet -> 5、初始化参数适配器,必须实现。");
// 6、初始化异常拦截器 TODO
// initHandlerExceptionResolvers(context);
// log.info("DispatcherServlet -> 6、初始化异常拦截器");
// 7、初始化视图预处理器 TODO
// initRequestToViewNameTranslator(context);
// log.info("DispatcherServlet -> 7、初始化视图预处理器");
// 8、初始化视图转换器,必须实现。
initViewResolvers(context);
log.info("DispatcherServlet -> 8、初始化视图转换器,必须实现。");
// 9、初始化FlashMap管理器 TODO
// initFlashMapManager(context);
// log.info("DispatcherServlet -> 9、初始化FlashMap管理器");
}
/**
* HandlerMapping:保存请求URL和处理方法的映射关系。
*
* @param context
*/
private void initHandlerMappings(MyApplicationContext context) {
log.info("DispatcherServlet -> 解析和缓存HandlerMapping");
if (this.applicationContext.getBeanDefinitionCount() == 0) {
return;
}
for (String beanName : this.applicationContext.getBeanDefinitionNames()) {
Object instance = applicationContext.getBean(beanName);
Class<?> clazz = instance.getClass();
// 1、Controller注解的类才具备URL配置
if (!clazz.isAnnotationPresent(MyController.class)) {
continue;
}
// 2、提取 class上配置的base_url
String baseUrl = "";
if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
baseUrl = clazz.getAnnotation(MyRequestMapping.class).value();
}
// 3、获取public的方法
for (Method method : clazz.getMethods()) {
if (!method.isAnnotationPresent(MyRequestMapping.class)) {
continue;
}
// 4、提取每个方法上面配置的url
MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
// 5、拼接URL
String regex = ("/" + baseUrl + "/" + requestMapping.value().replaceAll("\\\\*", ".*")).replaceAll("/+", "/");
Pattern pattern = Pattern.compile(regex);
// 6、保存HandlerMapping映射关系
handlerMappings.add(new MyHandlerMapping(pattern, instance, method));
}
}
}
/**
* 初始化参数适配器。
*
* @param context
*/
private void initHandlerAdapters(MyApplicationContext context) {
log.info("DispatcherServlet -> 创建HandlerAdapter处理类。");
// HandlerAdapter调用具体的方法对用户发来的请求来进行处理,所以每个HandlerMapping都对应一个HandlerAdapter。
for (MyHandlerMapping handlerMapping : handlerMappings) {
this.handlerAdapters.put(handlerMapping, new MyHandlerAdapter());
}
}
/**
* 根据请求URL找对对应处理Handler完成请求,并返回Response。
*
* @param req
* @param resp
* @throws Exception
*/
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
log.info("DispatcherServlet -> 请求分发");
// 1、通过从Request获得请以上是关于05. 手写Spring核心框架的主要内容,如果未能解决你的问题,请参考以下文章
从零开始手写 spring ioc 框架,深入学习 spring 源码