spring-boot启动源码 servlet spring-boot 内嵌tomcat 外置tomcat

Spring_boot_Servlet容器配置修改

Spring_boot默认嵌入 servlet的容器是tomcat,当然还有其他的容器,Jetty、Jboss 等也支持

Servlet容器配置的修改,通过application配置文件进行修改

通过配置一个bean去修改,实现webServerFactoryCustomizer,如果有相同配置,这里的优先级高于配置文件中的全局配置

注册servlet的三大组件

  1. servlet listener filter

Servlet3.0提供的注解方式

@WebServlet

@weblistener

@webfilter

主启动类再添加@servletComponentscan,它就会扫描上面三个注解,并注册

Spring_boot提供的

通过声明注册组件注册bean

 Spring_boot切换其他嵌入式的servlet容器,

排除(exclude)tomcat依赖,使用jetty或者,undertow依赖

tomcat自动配置原理

  1. 为什么可以根据依赖,自动选择到对应的servlet容器,

servlet容器自动配置类

ServletWebServerFactoryAutoConfiguration

//标记为一个配置类

@Configuration(proxyBeanMethods = false)

//配置生效的顺序
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)

//类路径下必须存在ServletRequest类,因为tomcat.jetty,undertow都是用servlet来实现的web容器
@ConditionalOnClass(ServletRequest.class)

//web的类型必须是servlet
@ConditionalOnWebApplication(type = Type.SERVLET)

//这里声明了ServerProperties的属性在这里生效,只要注入了ServerProperties就可以使用配置文件中,server.xxx的值
@EnableConfigurationProperties(ServerProperties.class)

//这里导入了tomcat,jetty,undertow三个容器相关类,所以它只要存在加载当前容器所需要的类,就能进行加载,所以当我们依赖切换为jetty时,只有EmbeddedJetty才会满足创建bean的条件,就能实现自动切换
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
      ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
      ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
      ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

   

需要的条件ConditionalOnClass

@Configuration(proxyBeanMethods = false)

//Tomcat.class, UpgradeProtocol.class 这两个是tomcat容器特有的
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomca

怎么根据配置文件中的server.xxx以及webServerfactoryCustomizer去设置servlet容器?

配置文件中定义一个了ServletWebServerFactoryCustomizer  bean,他也是实现了WebServerFactoryCustomizer接口,来定制servlet容器,spring启动的时候,会去注册所有的WebServerFactoryCustomizer,来共同配置我们的servlet容器,所以我们自定义的servlet容器配置,和它自带的从配置文件读取配置都能生效

public class ServletWebServerFactoryAutoConfiguration {

   @Bean
   public ServletWebServerFactoryCustomizer                                                               servletWebServerFactoryCustomizer(ServerProperties serverProperties,
         ObjectProvider<WebListenerRegistrar> webListenerRegistrars) {
      return new ServletWebServerFactoryCustomizer(serverProperties,
            webListenerRegistrars.orderedStream().collect(Collectors.toList()));
   }

嵌入式servlet 容器是怎么启动的

自动配置中根据不同的依赖,启动了一个对应的Embeddedxxxx,然后配置了一个对应的servlet容器的工厂类,比如Tomcat的工厂类TomcatServletWebServerFactory,

在spring_boot应用启动的时候,就会调用容器的refresh方法(spring的ioc原理),里面调用onRefresh(),然后调用到getWebServer,创建servlet容器tomcat,并启动

容器的启动

使用外部的servlet容器

jar包和war包区别

jar是java普通项目打包,通常是开发时要引用通用类,打成jar包便于存放管理jar包就是单独的项目代码或者资源打包,一般其它引用的三方不打进去。当你使用某些功能时就需要这些jar包的支持,需要导入jar包。war是java web项目打包,把所有的依赖和相关的文件一起全部打包进去,一个包就是一个应用

内嵌servlet容器:spring_boot的web启动器中带有默认的servlet容器Tomcat。只要我们在打包的时候把它一块儿打进去就可以,部署并启动了

外部servlet容器:

  1. 安装Tomcat服务器,配置环境变量等
  2. 打包War-部署我们的APP到服务器

打包方式变为war

排除自带的tomcat

  1. 在开发的时候,绑定到本地的Tomcat

本地开发配置外部的Tomcat启动web应用

因为Tomcat是支持Servlet3.0的,但它和spring_boot没有关系,所以用spring_boot的组件注册器去注册的servlet或其他组件,它是访问不了的

  1. 下载Tomcat,并解压到本地

 创建web项目,并添加webServlet组件

主启动类上添加@ServletComponentScan,如果application与组件不在同一包,注解中要表明路径

配置artifict

idea配置Tomcat

启动后,浏览器访问

外部servlet容器启动spring_boot原理

根据servlet的spi规范,也就是官方定义,它要求servlet的启动类是在META-INF/service下的ServletContainerInitializer文件中,必须绑定它的启动类

Spring_boot的中servlet的启动类SpringServletContainerInitializer

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

@HandlesTypes表示,只要是WebApplicationInitializer的类都会被加载,然后作为参数传入

而我们的spring_boot中也定义了一个SpringBootServletInitializer

SpringBootServletInitializer.createRootApplicationContext()

这里的config()就能到我们自定义的TomcatInit

最终就像我们spring_boot的主启动类中写的,启动spring_boot了

这就是通过外置的tomcat来启动spring_boot项目,

总之,如果不自定义TomcatInit,就不能访问spring_boot配置的controller

Spring_boot使用Freemarker

Freemarker中文网

FreeMarker 中文官方参考手册 (foofun.cn)

Spring_boot作为单体Web应用使用

  1. 添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-freemarker</artifactId>
 </dependency>

  1. Application.properties的配置,更多配置参考官网


#配置模板的后缀,
spring.freemarker.suffix=.html
#编码格式 默认其实就是UTF-8
spring.freemarker.charset=utf-8

  1. 定义controller


/**
 * @author cheLei
 * @version 1.0
 * @projectName springboot_application
 * @package com.chenlei.springboot_application.controller
 * @date 2024/2/20 22:32
 * @description TODO
 */
@Controller
@RequestMapping("/index")
public class FreeMarkerController {

    @RequestMapping ("/hello")
    public String hello(Model model){
        model.addAttribute("username","chendalei");
        return "index";
    }
}

  1. 定义模板文件,因为我们配置为.Html后缀,所以得定义相应后缀的文件到固定的路径resources下的templates下,应该也是可以通过配置文件修改路径的,定义index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <h1>${username}</h1>
</head>
<body>

</body>
</html>

  1. 访问 就可以得到结果

Spring_boot集成mybatis

mybatis的场景启动器,是由mybatis提供的,spring自己有数据库连接工具spring date jpa,

Spring提供的官方的启动器,都是由spring-boot开头的

Mybatis的自动生成代码插件,可以自动生成pojo类,mapper接口,mapper.xml,设置的时候记得在插件中添加驱动依赖并指定版本

Spring_boot启动原理

基本概念:

ApplicationContext:sprin容器或者称spring上下文

不同的配置bean的方式使用不同的ApplicationContext的实现去加载bean

比如

xml:ClasssPathXmlApplicationContext(配置文件)

Javaconfig配置类:AnotationConfigApplicationContext(配置类)

Spring_boot则使用:ServletWebServerApplicationContext

 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @since January 19, 2001
 * @see ServletContextAware#setServletContext
 */
public interface WebApplicationContext extends ApplicationContext {

 * @author Phillip Webb
 * @since 2.0.0
 */
public interface ConfigurableWebServerApplicationContext
      extends ConfigurableApplicationContext, WebServerApplicationContext {

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
      implements ConfigurableWebServerApplicationContext {

ServletWebServerApplicationContext 这个容器在spring的ioc的加载过程是没有变的,只是spring_boot做了一些拓展

在做ioc之前,spring_boot做了一些准备工作,解析配置文件,配置环境,创建spring上下文等,它都是通过监听器,比如在一个事件上添加监听,然后通知到其它拓展对象去做处理,(监听器用的观察者设计模式)

Spring_boot启动源码解析

Idea:Ctrl+shift_alt+U查看类的关系图

  1. 初始化springApplication,从springfactories读取listener,Applicationinitializer
  2. 运行run方法
  3. 读取环境变量,配置信息
  4. 创建springapplication 上下文
  5. 与初始化上下文,读取启动类
  6. 调用refresh:加载IOC容器,加载所有的自动配置类,创建servlet容器
  7. 在这整个过程中,spring会调用很多监听器,对外进行拓展
Spring_boot中各种监听器,发布顺序以及作用,官网说明

Core Features (spring.io)

大致如下

启动

//通过主启动类启动springboot

public class SpringbootApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }

}

//static  SpringApplication.run

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
   return run(new Class<?>[] { primarySource }, args);
}

//run()

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}

new SpringApplication()

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

        this.resourceLoader = resourceLoader;

        Assert.notNull(primarySources, "PrimarySources must not be null");

        //把主启动类放入主资源里面

        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

        //根据classpath推测我们的当前web环境,其实就是看类路径下有哪些环境的特有class文件 是servlet还是webflex或者其它

        this.webApplicationType = WebApplicationType.deduceFromClasspath();

        //从spring.factorys文件中 读取Bootstrap注册的配置类 应该需要配置目前为空

        this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();

        //从spring.factorys文件中 读取ApplicationContextInitializer ApplicationContextInitializer是springframework.context提供的一系列的拓展接口 比如我们外置的tomcat,就可以通过SpringBootServletInitializer就可以用来启动springboot

        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

        //从spring.factorys文件中 读取ApplicationListener ApplicationListener是springframework.context提供的各种监听器接口

        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

        //推算主启动类 根据运行栈依次向上查找

        this.mainApplicationClass = deduceMainApplicationClass();

    }

读取到的ApplicationContextInitializer

读取到的ApplicationListener

new SpringApplication().Run()

public ConfigurableApplicationContext run(String... args) {

    //用来记录启动时间

        StopWatch stopWatch = new StopWatch();

        stopWatch.start();

        //b把之前读从spring.factorys中取到的 ApplicationContextInitializer放到

        DefaultBootstrapContext bootstrapContext = createBootstrapContext();

        //s声明了一个spring的上下文,之前提到的xml上下文,或者配置文件上下文,都是实现了ConfigurableApplicationContext

        ConfigurableApplicationContext context = null;

        configureHeadlessProperty();

        //从spring容器中拿到所有的SpringApplicationRunListener组件 用来发布时间,运行监听器

        SpringApplicationRunListeners listeners = getRunListeners(args);

        listeners.starting(bootstrapContext, this.mainApplicationClass);

        try {

            //创建一个args参数管理对象

            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

            //预初始化环境变量,根据我们的WEB应用类型,读取系统环境变量和java环境变量以及配置文件

            //读取的顺序也是在一个sourcesList定义了的,先读取的优先级低,会被覆盖

            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

            //如果配置了忽略bean (实现了BeanInfo接口) 则将忽略的bean也添加到环境中 便于后面创建ioc容器

            configureIgnoreBeanInfo(environment);

            //spring——boot启动打印横幅

            Banner printedBanner = printBanner(environment);

            //根据文本applicationtype创建了spring上下文

            context = createApplicationContext();

            //设置启动类

            context.setApplicationStartup(this.applicationStartup);

            //预初始化上下文

            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

            //加载spring ioc容器 spring的AbstractApplicationContext中的refresh

            refreshContext(context);

            //预留的一个接口 目前没有处理

            afterRefresh(context, applicationArguments);

            //结束时间记录

            stopWatch.stop();

            if (this.logStartupInfo) {

                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);

            }

            //发布了ApplicationStartedEvent 事件 在里面又发布了AvailabilityChangeEvent事件

            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;

    }

SpringApplication.getRunListeners()

private SpringApplicationRunListeners getRunListeners(String[] args) {

        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };

        return new SpringApplicationRunListeners(logger,

                getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),

                this.applicationStartup);

    }

    prepareEnvironment

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,

    DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {

// Create and configure the environment

//根据webapplicationType创建environment 创建就会读取java环境变量和系统环境变量

ConfigurableEnvironment environment = getOrCreateEnvironment();

//将命令行参数读取到环境变量中

configureEnvironment(environment, applicationArguments.getSourceArgs());

//将@propertiesSources的配置信息放在第一位 因为它优先级别最低

ConfigurationPropertySources.attach(environment);

//发布ApplicationEnvironmentPreparedEvent的监听 读取全局配置文件

listeners.environmentPrepared(bootstrapContext, environment);

//将所有spring.main开头的配置信息绑定到SpringApplication

DefaultPropertiesPropertySource.moveToEnd(environment);

bindToSpringApplication(environment);

if (!this.isCustomEnvironment) {

    environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,

            deduceEnvironmentClass());

}

ConfigurationPropertySources.attach(environment);

return environment;

}

SpringApplication.getRunListeners()

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,

            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,

            ApplicationArguments applicationArguments, Banner printedBanner) {

        context.setEnvironment(environment);

        postProcessApplicationContext(context);

        //拿到所有的ApplicationContextInitializer的组件,循环调用initialize方法

        applyInitializers(context);

        //这里发布了ApplicationContextInitializedEvent 事件

        listeners.contextPrepared(context);

        bootstrapContext.close(context);

        if (this.logStartupInfo) {

            logStartupInfo(context.getParent() == null);

            logStartupProfileInfo(context);

        }

        // Add boot specific singleton beans

        //获取上下文的beanFactory (负责创建bean getbean())

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);

        if (printedBanner != null) {

            beanFactory.registerSingleton("springBootBanner", printedBanner);

        }

        //在spring的情况下,出现两个重名的bean后者会覆盖前者 这里关闭了这个功能 如果重名则抛出异常

        if (beanFactory instanceof DefaultListableBeanFactory) {

            ((DefaultListableBeanFactory) beanFactory)

                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);

        }

        //设置当前spring容器 是否需要懒加载 默认false

        if (this.lazyInitialization) {

            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());

        }

        // Load the sources

        Set<Object> sources = getAllSources();

        Assert.notEmpty(sources, "Sources must not be empty");

        //读取主启动类 后续要根据配置类解析所有bean

        load(context, sources.toArray(new Object[0]));

        //读取玩配置类 发布ApplicationPreparedEvent事件

        listeners.contextLoaded(context);

    }

Spring_boot自定义starters

Spring_boot主要就是把我们常用的场景整合了一个一个的starter(场景启动器),starter会为我们维护自动配置类,自动配置类中就会配置常用的bean,所以我们进行少量的配置就能在不同场景下应用。

  1. 编写自动配置类,Starter的核心就是自动配置类,
  2. 在MET_INF的目录下,创建spring.factories文件,里面的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration
  3. 建立启动器项目,官方建议一个starter,一个autoConfigure

Starter:主要用于管理依赖,当前启动器需要的核心依赖,这些依赖可能用于自动装配或者其它类库,包括引入autoConfigure

autoConfigure:主要就是对自动配置类的管理,专心实现自动配置类的逻辑

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值