对于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
二、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
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谁调用的?
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();
@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的答案就在此
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();
}