搭建 SpringBoot 底层机制开发环境
1、创建 Maven 项目 lzw-springboot
2、导入相关依赖
<?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>com.lzw</groupId>
<artifactId>lzw-springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<!--导入springboot父工程-规定写法-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
创建 src/main/java/com/lzw/springboot/MainApp.java
package com.lzw.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @author LiAng
*/
@SpringBootApplication
public class MainApp {
public static void main(String[] args) {
ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);
}
}
启动项目
@Configuration + @Bean 会发生什么
创建 src/main/java/com/lzw/springboot/bean/Dog.java
package com.lzw.springboot.bean;
/**
* @author LiAng
*/
public class Dog {
}
创建 src/main/java/com/lzw/springboot/config/Config.java
package com.lzw.springboot.config;
import com.lzw.springboot.bean.Dog;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author LiAng
* @Configuration :标识一个配置类, 充当Spring配置文件/容器
* 该配置类,如果在springboot扫描的包/子包下,会被注入到Spring容器
* 在该类中,可以通过@Bean 来注入其它的组件
*/
@Configuration
public class Config {
/**
* 1. 通过@Bean的方式, 将new出来的Bean对象, 放入到Spring容器
* 2. 该bean在Spring容器的name/id 默认就是 方法名
* 3. 通过方法名, 可以得到注入到spring容器中的dog对象
*/
@Bean
public Dog dog() {
return new Dog();
}
}
底层机制分析:实现 Spring 容器那一套机制 IO/文件扫描+注解+反射+集合+映射
SpringBoot 是怎么启动 Tomcat的,并可以支持访问@Controller
创建 src/main/java/com/lzw/springboot/controller/HiController.java
package com.lzw.springboot.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author LiAng
* 因为
* 1. HiController 被 @RestController标注,就是一个控制器
* 2. HiController 在扫描包/子包, 会被注入spring容器
*/
@RestController
public class HiController {
@RequestMapping("/hi")
public String hi(){
return "hi lzw HiController";
}
}
浏览器访问
SpringBoot 是怎么内嵌 Tomcat,并启动 Tomcat 的?
源码分析
Debug SpringApplication.run(MainApp.class, args) 看看 SpringBoot 是如何启动 Tomcat 的。
package com.lzw.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @author LiAng
*/
@SpringBootApplication
public class MainApp {
public static void main(String[] args) {
ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);
/**
* 这里我们开始Debug SpringApplication.run()
* 1. SpringApplication.java
* public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
* return run(new Class<?>[] { primarySource }, args);
* }
*
* 2.SpringApplication.java : 创建返回 ConfigurableApplicationContext对象
* public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
* return new SpringApplication(primarySources).run(args);
* }
*
* 3. SpringApplication.java
*
* public ConfigurableApplicationContext run(String... args) {
* StopWatch stopWatch = new StopWatch();
* stopWatch.start();
* DefaultBootstrapContext bootstrapContext = createBootstrapContext();
* ConfigurableApplicationContext context = null;
* configureHeadlessProperty();
* SpringApplicationRunListeners listeners = getRunListeners(args);
* listeners.starting(bootstrapContext, this.mainApplicationClass);
* try {
* ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
* ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
* configureIgnoreBeanInfo(environment);
* Banner printedBanner = printBanner(environment);
* context = createApplicationContext(); //严重分析: 创建容器
* context.setApplicationStartup(this.applicationStartup);
* prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
* refreshContext(context); //严重分析: 刷新应用程序上下文,比如 初始化默认设置/注入相关Bean/启动tomcat
* afterRefresh(context, applicationArguments);
* stopWatch.stop();
* if (this.logStartupInfo) {
* new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
* }
* listeners.started(context);
* callRunners(context, applicationArguments);* }
* catch (Throwable ex) {
* handleRunFailure(context, ex, listeners);
* throw new IllegalStateException(ex);
* }
*
* try {
* listeners.running(context);
* }
* catch (Throwable ex) {
* handleRunFailure(context, ex, null);
* throw new IllegalStateException(ex);
* }
* return context;
* }
*
* 4. SpringApplication.java : 容器类型很多,会根据你的this.webApplicationType创建对应的容器
* 默认 this.webApplicationType 是 SERVLET 也就是web容器/可以处理servlet
* protected ConfigurableApplicationContext createApplicationContext() {
* return this.applicationContextFactory.create(this.webApplicationType);
* }
*
* 5. ApplicationContextFactory.java
*
* ApplicationContextFactory DEFAULT = (webApplicationType) -> {
* try {
* switch (webApplicationType) {
* case SERVLET://默认是进入到这个分支 ,返回AnnotationConfigServletWebServerApplicationContext容器
* return new AnnotationConfigServletWebServerApplicationContext();
* case REACTIVE:
* return new AnnotationConfigReactiveWebServerApplicationContext();
* default:
* return new AnnotationConfigApplicationContext();
* }* }
* catch (Exception ex) {
* throw new IllegalStateException("Unable create a default ApplicationContext instance, "
* + "you may need a custom ApplicationContextFactory", ex);
* }
* };
*
* 6. SpringApplication.java
* private void refreshContext(ConfigurableApplicationContext context) {
* if (this.registerShutdownHook) {
* shutdownHook.registerApplicationContext(context);
* }
* refresh(context); //严重分析,真正执行相关任务
* }
*
* 7. SpringApplication.java
* protected void refresh(ConfigurableApplicationContext applicationContext) {
* applicationContext.refresh();
* }
*
*
* 8. ServletWebServerApplicationContext.java
* @Override
* public final void refresh() throws BeansException, IllegalStateException {
* try {
* super.refresh();//分析这个方法
* }
* catch (RuntimeException ex) {
* WebServer webServer = this.webServer;
* if (webServer != null) {
* webServer.stop();
* }
* throw ex;
* }
* }
*
* 9. AbstractApplicationContext.java
*
* @Override
* public void refresh() throws BeansException, IllegalStateException {
* synchronized (this.startupShutdownMonitor) {
* StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
*
* // Prepare this context for refreshing.
* prepareRefresh();
*
* // Tell the subclass to refresh the internal bean factory.
* ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
*
* // Prepare the bean factory for use in this context.
* prepareBeanFactory(beanFactory);
*
* try {
* // Allows post-processing of the bean factory in context subclasses.
* postProcessBeanFactory(beanFactory);
*
* StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
* // Invoke factory processors registered as beans in the context.
* invokeBeanFactoryPostProcessors(beanFactory);
*
* // Register bean processors that intercept bean creation.
* registerBeanPostProcessors(beanFactory);
* beanPostProcess.end();
*
* // Initialize message source for this context.
* initMessageSource();
*
* // Initialize event multicaster for this context.
* initApplicationEventMulticaster();
*
* // Initialize other special beans in specific context subclasses.
* onRefresh(); //严重分析,当父类完成通用的工作后,再重新动态绑定机制回到子类
*
* // Check for listener beans and register them.
* registerListeners();
*
* // Instantiate all remaining (non-lazy-init) singletons.
* finishBeanFactoryInitialization(beanFactory);
*
* // Last step: publish corresponding event.
* finishRefresh();
* }
*
* catch (BeansException ex) {
* if (logger.isWarnEnabled()) {
* logger.warn("Exception encountered during context initialization - " +
* "cancelling refresh attempt: " + ex);
* }
*
* // Destroy already created singletons to avoid dangling resources.
* destroyBeans();
*
* // Reset 'active' flag.
* cancelRefresh(ex);
*
* // Propagate exception to caller.
* throw ex;
* }
*
* finally {
* // Reset common introspection caches in Spring's core, since we
* // might not ever need metadata for singleton beans anymore...
* resetCommonCaches();
* contextRefresh.end();
* }
* }
* }
* 10. ServletWebServerApplicationContext.java
* @Override
* protected void onRefresh() {
* super.onRefresh();
* try {
* createWebServer();//看到胜利的曙光,创建webserver 可以理解成会创建指定web服务-Tomcat
* }
* catch (Throwable ex) {
* throw new ApplicationContextException("Unable to start web server", ex);
* } * }
* 11. ServletWebServerApplicationContext.java
*
* private void createWebServer() {
* WebServer webServer = this.webServer;
* ServletContext servletContext = getServletContext();
* if (webServer == null && servletContext == null) {
* StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
* ServletWebServerFactory factory = getWebServerFactory();
* createWebServer.tag("factory", factory.getClass().toString());
* this.webServer = factory.getWebServer(getSelfInitializer());//严重分析,使用TomcatServletWebServerFactory 创建一个TomcatWebServer
* createWebServer.end();
* getBeanFactory().registerSingleton("webServerGracefulShutdown",
* new WebServerGracefulShutdownLifecycle(this.webServer));
* getBeanFactory().registerSingleton("webServerStartStop",
* new WebServerStartStopLifecycle(this, this.webServer));
* }
* else if (servletContext != null) {
* try {
* getSelfInitializer().onStartup(servletContext);
* }
* catch (ServletException ex) {
* throw new ApplicationContextException("Cannot initialize servlet context", ex);
* }
* }
* initPropertySources(); * }
*
* 12. TomcatServletWebServerFactory.java 会创建Tomcat 并启动Tomcat
*
* @Override
* public WebServer getWebServer(ServletContextInitializer... initializers) {
* if (this.disableMBeanRegistry) {
* Registry.disableRegistry();
* }
* Tomcat tomcat = new Tomcat();//创建了Tomcat对象
* File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
* //做了一系列的设置
* tomcat.setBaseDir(baseDir.getAbsolutePath());
*
* Connector connector = new Connector(this.protocol);
* connector.setThrowOnFailure(true);
* tomcat.getService().addConnector(connector);
* customizeConnector(connector);
* tomcat.setConnector(connector);
* tomcat.getHost().setAutoDeploy(false);
* configureEngine(tomcat.getEngine());
* for (Connector additionalConnector : this.additionalTomcatConnectors) {
* tomcat.getService().addConnector(additionalConnector);
* }
* prepareContext(tomcat.getHost(), initializers);
* return getTomcatWebServer(tomcat); //严重分析该方法.
* }
*
* 13. TomcatServletWebServerFactory.java , 这里做了校验创建 TomcatWebServer
* protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
* return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
* }
* 14. TomcatWebServer.java
* public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
* Assert.notNull(tomcat, "Tomcat Server must not be null");
* this.tomcat = tomcat;
* this.autoStart = autoStart;
* this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
* initialize();//分析这个方法.
* }
* 15.TomcatWebServer.java
*
* private void initialize() throws WebServerException {
* logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
* synchronized (this.monitor) {
* try {
* addInstanceIdToEngineName();
*
* Context context = findContext();
* context.addLifecycleListener((event) -> {
* if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
* // Remove service connectors so that protocol binding doesn't
* // happen when the service is started.
* removeServiceConnectors();
* } * });
*
* // Start the server to trigger initialization listeners
* this.tomcat.start(); //启动Tomcat
*
* // We can re-throw failure exception directly in the main thread
* rethrowDeferredStartupExceptions();
*
* try {
* ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
* }
* catch (NamingException ex) {
* // Naming is not enabled. Continue
* }
*
* // Unlike Jetty, all Tomcat threads are daemon threads. We create a
* // blocking non-daemon to stop immediate shutdown
* startDaemonAwaitThread();
* }
* catch (Exception ex) {
* stopSilently();
* destroySilently();
* throw new WebServerException("Unable to start embedded Tomcat", ex);
* }
* }
* }
*/
System.out.println("hello ioc");
}
}
实现 SpringBoot底层机制
阶段1-创建 Tomcat 并启动
修改pom.xml
<?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>com.lzw</groupId>
<artifactId>lzw-springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<!--导入springboot父工程-规定写法-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--因为我们自己要创建Tomcat对象,并启动,
因此我们先排除 内嵌的 spring-boot-starter-tomcat-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--
我们指定tomcat版本,引入tomcat依赖/库
1. 使用指定的tomcat 8.5.75
2. 如果我们引入自己指定的tomcat,一定要记住把前面spring-boot-starter-tomcat排除
3. 如果你不排除,会出现 GenericServlet Not Found错误提示
-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.75</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>8.5.75</version>
</dependency>
</dependencies>
</project>
创建 src/main/java/com/lzw/lzwspringboot/LzwSpringApplication.java
package com.lzw.lzwspringboot;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
/**
* @author LiAng
*/
public class LzwSpringApplication {
//这里会创建tomcat对象,并关联Spring容器, 并启动
public static void run(){
try {
//创建Tomcat对象
Tomcat tomcat = new Tomcat();
//设置9090
tomcat.setPort(9090);
//启动
tomcat.start();
//等待请求接入
System.out.println("======9090====等待请求=====");
tomcat.getServer().await();
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建 src/main/java/com/lzw/lzwspringboot/LzwMainApp.java
package com.lzw.lzwspringboot;
/**
* @author LiAng
* 主程序
*/
public class LzwMainApp {
public static void main(String[] args) {
//启动LzwSpringBoot项目/程序
LzwSpringApplication.run();
}
}
阶段2-创建Spring容器
创建 src/main/java/com/lzw/lzwspringboot/bean/Monster.java
package com.lzw.lzwspringboot.bean;
/**
* @author LiAng
*/
public class Monster {
}
创建 src/main/java/com/lzw/lzwspringboot/controller/LzwHiController.java
package com.lzw.lzwspringboot.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author LiAng
*/
@RestController
public class LzwHiController {
@RequestMapping("/lzwhi")
public String hi(){
return "hi,lzw LzwHiController";
}
}
创建 src/main/java/com/lzw/lzwspringboot/config/LzwConfig.java
package com.lzw.lzwspringboot.config;
import com.lzw.lzwspringboot.bean.Monster;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author LiAng
* 配置类-作为Spring的配置文件
* 容器怎么知道要扫描哪些包呢?
* 在配置类可以指定要扫描的包 @ComponentScan("com.lzw.lzwspringboot")
*/
@Configuration
@ComponentScan("com.lzw.lzwspringboot")
public class LzwConfig {
//注入Bean-monster 对象到Spring容器
@Bean
public Monster monster(){
return new Monster();
}
}
创建 src/main/java/com/lzw/lzwspringboot/LzwWebApplicationInitializer.java
package com.lzw.lzwspringboot;
import com.lzw.lzwspringboot.config.LzwConfig;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
/**
* @author LiAng
* 1. 创建我们的Spring 容器
* 2. 加载/关联Spring容器的配置-按照注解的方式
* 3. 完成Spring容器配置的bean的创建, 依赖注入
* 4. 创建前端控制器 DispatcherServlet , 并让其持有Spring容器
* 5. 当DispatcherServlet 持有容器, 就可以进行分发映射 (SpringMVC底层机制)
* 6. 这里onStartup 是Tomcat调用, 并把ServletContext 对象传入
*/
public class LzwWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("startup...");
//加载Spring web application configuration => 容器
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
//在ac中注册 HspConfig.class 配置类
ac.register(LzwConfig.class);
ac.refresh();//完成bean的创建和配置
//1. 创建注册非常重要的前端控制器 DispatcherServlet
//2. 让DispatcherServlet 持有容器
//3. 这样就可以进行映射分发, 回忆一下SpringMvc机制[自己实现过]
DispatcherServlet dispatcherServlet = new DispatcherServlet(ac);
//返回了ServletRegistration.Dynamic对象
ServletRegistration.Dynamic registration = servletContext.addServlet("app", dispatcherServlet);
//当tomcat启动时,加载 dispatcherServlet
registration.setLoadOnStartup(1);
//拦截请求,并进行分发处理
//提示 / 和 /* 的区别 => java web 中
registration.addMapping("/");
}
}
阶段3-将 Tomcat 和 Spring 容器关联,并启动 Spring 容器
修改 LzwSpringApplication
public static void run(){
try {
//创建Tomcat对象
Tomcat tomcat = new Tomcat();
//1. 让tomcat可以将请求转发到spring web容器,因此需要进行关联
//2. "/lzwboot" 就是我们的项目的 application context , 就是我们原来配置tomcat时,指定的application context
//3. "D:\Study\VIP\AllDemo\SpringBoot\lzw-springboot" 指定项目的目录
tomcat.addWebapp("/lzwboot","D:\\Study\\VIP\\AllDemo\\SpringBoot\\lzw-springboot");
//设置9090
tomcat.setPort(9090);
//启动
tomcat.start();
//等待请求接入
System.out.println("======9090====等待请求=====");
tomcat.getServer().await();
} catch (Exception e) {
e.printStackTrace();
}
}