1.SpringMVC的基本组件
要启动SpringMVC程序最基本的组件如下:
- 内嵌的容器工厂
- DispatcherServlet
- DispatcherServlet注册Bean
1)创建一个WebConfig配置类,配置容器工厂、DispatcherServlet及DispatcherServlet注册Bean:
@Configuration // 配置类
@ComponentScan // 扫描当前包及子包下的组件
public class WebConfig {
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
}
2)创建一个控制器类,用于测试,放在WebConfig同包下,保证组件能被Spring扫描到:
package com.limin.study.springmvc.A01;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Controller
public class Controller01 {
@GetMapping("/test01")
public void test01(HttpServletResponse response) throws IOException {
response.getWriter().print("hello");
}
}
3)编写测试类并启动,web环境下使用AnnotationConfigServletWebServerApplicationContext
package com.limin.study.springmvc.A01;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
public class Test01 {
public static void main(String[] args) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}
}
4)浏览器访问http://127.0.0.1:8080/test01,页面即可响应hello字符串
2.Web容器启动过程
ServletWebServerFactory是一个接口,作用是获取Web容器对象,源码如下:
@FunctionalInterface
public interface ServletWebServerFactory {
WebServer getWebServer(ServletContextInitializer... initializers);
}
我们希望知道getWebServer是何时调用的,在TomcatServletWebServerFactory的getWebServer方法第一行打一个断点,debug模式启动Test01,可以看到调用栈,如下图所示:
从调用链可以看出,Web容器是在Spring启动过程中的onRresh()中创建的,创建容器的核心方法在ServletWebServerApplicationContext#createWebServer,源码如下:
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 1.获取Spring容器中的ServletWebServerFactory组件
ServletWebServerFactory factory = getWebServerFactory();
// 2.调用ServletWebServerFactory接口的getWebServer获取具体的Web容器
this.webServer = factory.getWebServer(getSelfInitializer());
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();
}
protected ServletWebServerFactory getWebServerFactory() {
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
// 从Spring容器中获取ServletWebServerFactory实现类对象
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
createWebServer()中从容器中获取了我们在WebConfig配置类中注册的TomcatServletWebServerFactory,并调用了getWebServer方法获取了TomcatWebServer对象
Tomcat的启动正是在创建TomcatWebServer对象时进行的,源码见TomcatWebServer#getWebServer:
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
// 准备好Tomcat容器
Tomcat tomcat = new 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);
// 创建TomcatWebServer对象
return getTomcatWebServer(tomcat);
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
实例化TomcatWebServer的过程中启动了Tomcat容器,源码见TomcatWebServer构造器:
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
// 省略其他代码...
initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
// 省略其他代码...
this.tomcat.start();
// 省略其他代码...
}
}
Tomcat的启动原理会在Tomcat篇中分析,此处不再赘述
3.DispatcherServletRegistrationBean的作用
DispatcherServletRegistrationBean的作用是在Tomcat容器启动时注册DispatcherServlet和路径映射,这样当匹配到请求路径时,DispatcherServlet这个前端控制器就能够处理请求
Tomcat启动Context应用上下文时会注册Servlet,StandardContext#startInternal源码如下:
protected synchronized void startInternal() throws LifecycleException {
// 省略其他代码...
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
// 调用ServletContainerInitializer的onStartup
entry.getKey().onStartup(entry.getValue(), getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
// 省略其他代码...
}
启动时会调用ServletContainerInitializer的onStartup,继而调用ServletContextInitializer的onStartup
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
// 调用ServletContextInitializer的onStartup
initializer.onStartup(servletContext);
}
}
// 省略其他代码...
}
调用栈如下图所示:
在执行initializer.onStartup时代码行数为-1,找不到源码,这是因为在创建ServletContextInitializer接口对象时使用了lambda表达式,见ServletWebServerApplicationContext#createWebServer
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
// 创建时调用的getSelfInitializer(),而这是一个lamdba表达式
this.webServer = factory.getWebServer(getSelfInitializer());
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();
}
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// 这里调用的是DispatcherServletRegistrationBean.onStartup
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
相当于在ServletWebServerApplicationContext内部创建一个ServletContextInitializer的实现类,即
new ServletContextInitializer() {
public void onStartup(ServletContext servletContext) throws ServletException {
selfInitialize(servletContext)
}
}
因此在执行时initializer.onStartup(servletContext)就会直接调用到selfInitialize方法中
而DispatcherServletRegistrationBean正是ServletContextInitializer的实现类,在selfInitialize方法中调用的就是DispatcherServletRegistrationBean的onStartup方法,最终会调用到DynamicRegistrationBean#register
protected final void register(String description, ServletContext servletContext) {
// 1.注册Servlet,创建Wrapper
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
return;
}
// 2.配置映射路径
configure(registration);
}
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}
register方法中做了2件事:
1)调用addServlet方法将DispatcherServlet添加到Wrapper中,ApplicationContext#addServlet源码如下:
private ServletRegistration.Dynamic addServlet(String servletName, String servletClass,
Servlet servlet, Map<String,String> initParams) throws IllegalStateException {
// 省略其他代码...
// 从Context应用上下文中根据名称查找Wrapper
Wrapper wrapper = (Wrapper) context.findChild(servletName);
if (wrapper == null) {
// 如果没有找到,则创建Wrapper
wrapper = context.createWrapper();
wrapper.setName(servletName);
// 添加到应用上下文中
context.addChild(wrapper);
} else {
if (wrapper.getName() != null &&
wrapper.getServletClass() != null) {
if (wrapper.isOverridable()) {
wrapper.setOverridable(false);
} else {
return null;
}
}
}
ServletSecurity annotation = null;
if (servlet == null) {
wrapper.setServletClass(servletClass);
Class<?> clazz = Introspection.loadClass(context, servletClass);
if (clazz != null) {
annotation = clazz.getAnnotation(ServletSecurity.class);
}
} else {
// 设置DispatcherServlet到Wrapper中
wrapper.setServletClass(servlet.getClass().getName());
wrapper.setServlet(servlet);
if (context.wasCreatedDynamicServlet(servlet)) {
annotation = servlet.getClass().getAnnotation(ServletSecurity.class);
}
}
// 省略其他代码...
return registration;
}
在Tomcat中Context表示应用,Wrapper表示应用中的Servlet容器,如果根据Wrapper名称在Context中没有找到Wrapper,则会创建Wrapper,设置Servlet
2)设置相关属性,例如映射路径、初始化时机等,源码见ServletRegistrationBean#configure
protected void configure(ServletRegistration.Dynamic registration) {
super.configure(registration);
String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
if (urlMapping.length == 0 && this.alwaysMapUrl) {
urlMapping = DEFAULT_MAPPINGS;
}
if (!ObjectUtils.isEmpty(urlMapping)) {
// 添加路径映射
registration.addMapping(urlMapping);
}
registration.setLoadOnStartup(this.loadOnStartup);
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
}
4.通过配置类修改容器启动参数
Tomcat启动端口、上下文路径等参数都可以通过配置文件修改
DispatcherServlet默认是在接收到第一个请求时初始化的,可以通过配置修改
1)在resources下创建application.properties
server.port=8888
server.servlet.context-path=/test
spring.mvc.servlet.load-on-startup=1
2)这些配置在SpringBoot都有对应的配置类,可以进行配置绑定,修改WebConfig
@Configuration
@ComponentScan
@PropertySource("classpath:application.properties") // 指定配置文件
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class}) // 绑定配置类
public class WebConfig {
@Bean
public ServletWebServerFactory servletWebServerFactory(ServerProperties serverProperties) {
Integer port = serverProperties.getPort();
String contextPath = serverProperties.getServlet().getContextPath();
return new TomcatServletWebServerFactory(contextPath, port);
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
return registrationBean;
}
}
DispatcherServletRegistrationBean中的配置会在上文configure方法中将这些属性设置到servlet中
应用启动时调用finishRefresh()会根据loadOnStartup的值判断是否初始化,如下图所示:
3)再启动程序,配置修改完成