对于springboot来说,还有两块是比较有意思的,第一就是发现他内置了tomcat,接下来一快就是他对springmvc进行了无缝整合,我们也可以来试试。

一、内嵌tomcat

首先来看下最简单的tomcat集成,新建一个项目springboot-tomcat-embed

1、pom文件如下:

<!--Java语言操作tomcat -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
    <version>8.5.16</version>
</dependency>
<!-- tomcat对jsp支持 -->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jasper</artifactId>
    <version>8.5.16</version>
</dependency>

2、新建立一个servlet

public class IndexServlet extends HttpServlet {
    private static final long serialVersionUID = -2838733088449319556L;
    @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 {
        resp.getWriter().print("this is myself defined index... tomcat");
    }
}

3、有了这个servlet就可以使用tomcat 整合了:

/**
 * 自定义内置Tomcat服务
 */
public class HankinTomcat {
    private static int PORT = 8080;
    private static String CONTEX_PATH = "/hankin";
    private static String SERVLET_NAME = "indexServlet";

    public static void main(String[] args) throws Exception {
        // 创建tomcat服务器
        Tomcat tomcatServer = new Tomcat();
        // 指定端口号
        tomcatServer.setPort(PORT);
        // 是否设置自动部署
        tomcatServer.getHost().setAutoDeploy(false);
        // 创建上下文
        StandardContext standardContex = new StandardContext();
        standardContex.setPath(CONTEX_PATH);
        // 监听上下文
        standardContex.addLifecycleListener(new Tomcat.FixContextListener());
        // tomcat容器添加standardContex
        tomcatServer.getHost().addChild(standardContex);
        // 创建Servlet
        tomcatServer.addServlet(CONTEX_PATH, SERVLET_NAME, new IndexServlet());
        // servleturl映射
        standardContex.addServletMappingDecoded("/index", SERVLET_NAME);
        tomcatServer.start();
        System.out.println("tomcat服务器启动成功..");
        // 异步进行接收请求
        tomcatServer.getServer().await();
    }
}

4、运行main方法测试

在浏览器输入:http://localhost:8080/hankin/index

第三章 Springboot核心原理实战笔记

二、SpringMVC整合

1、pom文件

继续使用上一个项目springboot-tomcat-embed,pom文件如下:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.4.RELEASE</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.0.4.RELEASE</version>
    <scope>compile</scope>
</dependency>

2、新增UserService

@Service
public class UserService {
    public String index() {
        return "springboot 2.0  我正在加载UserService";
    }
}

3、新建RestController

@RestController
public class IndexController {
    @Autowired
    private UserService userService;
    @RequestMapping(value = "/index", produces = "text/html;charset=UTF-8")
    public String index() {
        return userService.index();
    }
}

这样,一些常用业务类已经准备完毕,接下来就是真正的整合了。

4、DispatcherServlet配置类

虽然前面有了service有了controller,但依然没有把这些组建交给spring,对于springmvc来说有个DispatcherServlet,这是springmvc的前端控制器,以前是配置在web.xml中,只是现在用的是注解。

public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    // 加载根配置信息 spring核心
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { RootConfig.class };
    }
    // springmvc 加载 配置信息
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebConfig.class };
    }
    // springmvc 拦截url映射 拦截所有请求
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

RootConfig代码:

@Configuration
@ComponentScan("com.chj")
public class RootConfig {
}

WebConfig代码:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.chj.controller" })
//public class WebConfig extends WebMvcConfigurerAdapter {
public class WebConfig {
}

5、集成tomcat

/**
 * 自定义内嵌Tomcat
 */
public class TomcatApp {
    public static void main(String[] args) throws ServletException, LifecycleException {
        start();
    }
    public static void start() throws ServletException, LifecycleException {
        Tomcat tomcatServer = new Tomcat(); // 创建Tomcat容器
        tomcatServer.setPort(9090);  // 端口号设置
        // 读取项目路径 加载静态资源
        StandardContext ctx = (StandardContext) tomcatServer.addWebapp("/", new File("springboot_mvc/src/main").getAbsolutePath());
        ctx.setReloadable(false);   // 禁止重新载入
        // class文件读取地址
        File additionWebInfClasses = new File("target/classes");
        // 创建WebRoot
        WebResourceRoot resources = new StandardRoot(ctx);
        // tomcat内部读取Class执行
        resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes",
                additionWebInfClasses.getAbsolutePath(), "/"));
        tomcatServer.start();
        // 异步等待请求执行
        tomcatServer.getServer().await();
    }
}

启动TomcatApp,在地址栏输入:http://localhost:9090/index

第三章 Springboot核心原理实战笔记

6、对JSP支持

要支持JSP,回忆学习springmvc中的内容,需要用到一个试图解析器

6.1、修改WebConfig

增加对jsp的支持

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.chj.controller" })
//public class WebConfig extends WebMvcConfigurerAdapter {
public class WebConfig implements WebMvcConfigurer {
    // 创建SpringMVC视图解析器
    public void configureViewResolvers(ViewResolverRegistry registry) {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        // 可以在JSP页面中通过${}访问beans
        viewResolver.setExposeContextBeansAsAttributes(true);
        registry.viewResolver(viewResolver);
    }
}

6.1、新增controller

@Controller
public class UserController {
    @RequestMapping("/pageIndex")
    public String pageIndex() {
        return "pageIndex";
    }
}

6.3、增加JSP

在resources里面新增WEB-INF\views\pageIndex.jsp,重启tomcat访问:http://localhost:9090/pageIndex

三、SpringBoot启动流程分析

1、Tomcat加载流程

问题1:tomcat哪启动的?

spring-boot-autoconfigure-2.1.3.RELEASE.jar!\META-INF\spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\

ServletWebServerFactoryAutoConfiguration这类里面有个TomcatServletWebServerFactoryCustomizer这个类实现了WebServerFactoryCustomizer。

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
      ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
      ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
      ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
      ServerProperties serverProperties) {
   return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}

public class TomcatServletWebServerFactoryCustomizer implements  WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {

流程整理如下:

EmbeddedTomcat->TomcatServletWebServerFactory()

->TomcatServletWebServerFactory.getWebServer()

->getTomcatWebServer->TomcatWebServer->启动tomcat

@Configuration
class ServletWebServerFactoryConfiguration {
   @Configuration
   @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
   @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
   public static class EmbeddedTomcat {
      @Bean
      public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
         return new TomcatServletWebServerFactory();
      }
   }

TomcatServletWebServerFactory.getWebServer()源码如下:

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
   Tomcat tomcat = new Tomcat();
   File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   Connector connector = new Connector(this.protocol);
   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);
}

TomcatServletWebServerFactory.getTomcatWebServer(tomcat)方法代码:

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
         return new TomcatWebServer(tomcat, getPort() >= 0);
    }

启动tomcat,TomcatWebServer(tomcat, getPort() >= 0)

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
   Assert.notNull(tomcat, "Tomcat Server must not be null");
   this.tomcat = tomcat;
   this.autoStart = autoStart;
   initialize();
}

initialize()方法:

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();
         rethrowDeferredStartupExceptions();
         try {
            ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
         } catch (NamingException ex) {
         }
         startDaemonAwaitThread();
      } catch (Exception ex) {
         stopSilently();
         throw new WebServerException("Unable to start embedded Tomcat", ex);
      }
   }
}

问题2:getWebServer谁调用的?

第三章 Springboot核心原理实战笔记

ServletWebServerApplicationContext.createWebServer()代码:

private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
      ServletWebServerFactory factory = getWebServerFactory();
      this.webServer = factory.getWebServer(getSelfInitializer());
   } else if (servletContext != null) {
      try {
         getSelfInitializer().onStartup(servletContext);
      }catch (ServletException ex) {
         throw new ApplicationContextException("Cannot initialize servlet context",ex);
      }
   }
   initPropertySources();
}

2、准备工作

ApplicationContextInitializer:Context初始化后调用的类

SpringApplicationRunListener:SpringBoot运行监听的类

ApplicationRunner

CommandLineRunner 

上面着两个几乎可以等价,用于启动后做客户自定义的操作

1)新建java类MyCommandLineRunner

@Component
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {

System.out.println("MyCommandLineRunner.run()执行了");

}
}

2)新建java类MyApplicationRunner

@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("MyApplicationRunner.run()执行了");
    }
}

3)新建java类MyApplicationContextInitializer

public class MyApplicationContextInitializer implements

  ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("MyApplicationContextInitializer.initialize()执行了"+applicationContext);
    }
}

4)新建java类MySpringApplicationRunListener

public class MySpringApplicationRunListener implements SpringApplicationRunListener {
    //必须有的构造器
    public MySpringApplicationRunListener (App application, String[] args){
    }
    @Override
    public void starting() {
        System.out.println("MySpringApplicationRunListener.starting()执行了");
    }
    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        System.out.println("MySpringApplicationRunListener.environmentPrepared()执行了");
    }
    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener.contextPrepared()执行了");
    }
    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener.contextLoaded()执行了");
    }
    @Override
    public void started(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener.started()执行了");
    }
    @Override
    public void running(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener.running()执行了");
    }
    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("MySpringApplicationRunListener.failed()执行了");
    }
}

5)在resources/META-INF/spring.factories增加

org.springframework.context.ApplicationContextInitializer=\
com.chj.listener.MyApplicationContextInitializer
org.springframework.boot.SpringApplicationRunListener=\
com.chj.listener.MySpringApplicationRunListener

为什么如此,后面源代码分析。

3、创建SpringApplication

@SpringBootApplication
@MapperScan("com.chj.dao")
@EnableTransactionManagement
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

 SpringApplication.run(App.class, args)方法代码如下:

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

其实SpringBoot启动就着两个步骤,先创建ConfigurableApplicationContext,然后再调用Run方法。

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

SpringApplication.SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)构造方法:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //保存主类
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //判断当前是什么类型项目
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //从类路径下找到META-INF/spring.factories配置的所有ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

可见着一步非常简单,只是把一些相关的类都加载了而已,并没执行。

4、Run方法分析

真的重要是run方法,

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    //从类路径下META‐INF/spring.factories,取得SpringApplicationRunListeners;
    SpringApplicationRunListeners listeners = getRunListeners(args);
    //回调所有的获取SpringApplicationRunListener.starting()方法
    listeners.starting();
    try { //封装命令行参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //准备环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        //创回调SpringApplicationRunListener.environmentPrepared();
        //表示环境准备完成打印Banner
        Banner printedBanner = printBanner(environment);
        //根据环境创建context
        context = createApplicationContext();
        //错误的异常报表
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        //准备上下文环境将environment保存到ioc中;
        //applyInitializers()调用所有的ApplicationContextInitializer的initialize方法
        //调用所有的SpringApplicationRunListener的contextPrepared();
        prepareContext(context, environment, listeners, applicationArguments,printedBanner);
        //SpringApplicationRunListener的contextLoaded
        //刷新容器扫描,创建,加载所有组件;
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch);
        }
        //所有的SpringApplicationRunListener回调started方法
        listeners.started(context);
        //获取所有的ApplicationRunner和CommandLineRunner进行调用
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }
    try { //所有的SpringApplicationRunListener的running();
        listeners.running(context);
    }catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

刷新容器、扫描、创建,加载所有组件:refreshContext(context);

AbstractApplicationContext.refresh()方法代码:

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // 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);
         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);
         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);
         // 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 {
         resetCommonCaches();
      }
   }
}

AbstractApplicationContext.onRefresh();

第三章 Springboot核心原理实战笔记

@Override
protected void onRefresh() {
   super.onRefresh();
   try {
      createWebServer();
   }catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}

ServletWebServerApplicationContext.createWebServer()创建web服务:

private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
      ServletWebServerFactory factory = getWebServerFactory();
      this.webServer = factory.getWebServer(getSelfInitializer());
   }else if (servletContext != null) {
      try {
         getSelfInitializer().onStartup(servletContext);
      }catch (ServletException ex) {
         throw new ApplicationContextException("Cannot initialize servlet context",ex);
      }
   }
   initPropertySources();
}

Tomcat启动方法分析:问题2的答案就在此

第三章 Springboot核心原理实战笔记

 

TomcatServletWebServerFactory.getWebServer(ServletContextInitializer... initializers);

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
   Tomcat tomcat = new Tomcat();
   File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   Connector connector = new Connector(this.protocol);
   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);
}

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
   return new TomcatWebServer(tomcat, getPort() >= 0);
}

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
   Assert.notNull(tomcat, "Tomcat Server must not be null");
   this.tomcat = tomcat;
   this.autoStart = autoStart;
   initialize();
}

相关文章: