1:web.xml是怎么没的?
1.1:Servlet3.0之前
在servlet3.0以前,编写web程序的流程大概如下:
1:编写servlet,并通过<servlet>标签注册到web.xml文件中
2:编写filter,并通过<filter>标签注册到web.xml文件中
3:编写lisner,并通过<listener>标签注册到web.xml文件中
用的比较多,这里就不再提供具体实例了。
web.xml文件是web容器和应用程序的纽带,相关的信息都需要注册在这里。web容器通过web.xml读取信息。但是这个过程比较繁琐,作为开发的我们,如果能通过编程的方式
来完成这个过程的话就能实现不依赖于web.xml
了。所幸,我们最终迎来了servlet3.0时代,让我们这个需求能够得到满足。
1.2:Servlet3.0
目前有两种非web.xml方式注册servlet,filter等组件到容器中,第一种是使用@WebServlet
,@WebFilter
等注解,第二种方式是使用ServletContext
(这是web容器封装servlet,filter,listener等组件的上下文对象),主要API如下:
- 添加servlet相关API
public ServletRegistration.Dynamic addServlet(String servletName,
Class<? extends Servlet> servletClass);
public ServletRegistration.Dynamic addServlet(String servletName, String className);
public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet);
调用以上方法,相当于在web.xml中配置<servlet>
标签。
- 添加filter相关API
public FilterRegistration.Dynamic addFilter(String filterName,
Class<? extends Filter> filterClass);
public FilterRegistration.Dynamic addFilter(String filterName, Filter filter);
public FilterRegistration.Dynamic addFilter(String filterName, String className);
调用以上方法相当于在web.xml中配置<filter>
标签。
- 添加listener相关API
public void addListener(String className);
public <T extends EventListener> void addListener(T t);
public void addListener(Class<? extends EventListener> listenerClass);
调用以上方法相当于在web.xml中配置<listener>
标签。
现在,向web容器注册各种组件的API有了,想要完成注册,该怎么做呢?此时,就需要接口来提供规范了,这个接口是javax.servlet.ServletContainerInitializer
,源码如下:
// 通过在META-INF/services/文件夹下创建javax.servlet.ServletContainerInitializer文件
// 然后将自己提供的实现类按照一行一个的格式配置到文件中,web容器在启动的时候会通过SPI来加载,并执行
// onStartup方法,执行自定义的注册逻辑,完成相关组件的注册
public interface ServletContainerInitializer {
// 参数c:期望处理的class类型集合
// 参数ctx:servlet上线文对象,我们通过该对象中来完成各种web组件注册工作
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
接下来我们新建一个servlet项目,当然也可以直接下载源码。来测试一下。
- servlet
public class MyServletContainerInitializerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello from servlet register by ServerContainerInitializer!!!");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// post也走get
this.doGet(req, resp);
}
}
- filter
public class MyServletContainerInitializerFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("MyServletContainerInitializerFilter.init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyServletContainerInitializerFilter.doFilter");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("MyServletContainerInitializerFilter.destroy");
}
}
- ServletContainerInitializer实现类
public class MyServletContainerInitializer implements ServletContainerInitializer {
private final static String JAR_HELLO_URL = "/hello";
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
System.out.println("注册servlet到web容器开始!");
ServletRegistration.Dynamic dynamicServlet = servletContext.addServlet(MyServletContainerInitializerServlet.class.getSimpleName(), MyServletContainerInitializerServlet.class);
// 添加映射
dynamicServlet.addMapping(JAR_HELLO_URL);
System.out.println("注册servlet到web容器结束!");
System.out.println("注册filter到web容器开始!");
FilterRegistration.Dynamic dynamicFilter = servletContext.addFilter(MyServletContainerInitializerFilter.class.getSimpleName(), MyServletContainerInitializerFilter.class);
EnumSet<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class);
dispatcherTypes.add(DispatcherType.REQUEST);
dispatcherTypes.add(DispatcherType.FORWARD);
dynamicFilter.addMappingForUrlPatterns(dispatcherTypes, true, JAR_HELLO_URL);
System.out.println("注册filter到web容器结束!");
}
}
现在准备工作都完成了,但是,web容器此时还无法识别到我们的ServletContainerInitializer,根据规范,还需要配置SPI,因此我们在META-INF/services
下创建文件javax.servlet.ServletContainerInitializer
,并填入实现类内容,如下:
接下来我们启动查看日志:
...snip...
注册servlet到web容器开始!
注册servlet到web容器结束!
注册filter到web容器开始!
注册filter到web容器结束!
...snip...
访问测试:
C:\Users\Administrator>curl http://192.168.10.119:10080/test_servlet3_without_webxml/hello
hello from servlet register by ServerContainerInitializer!!!
2:springMVC如何集成servlet3.0
我们自己开发程序来集成servlet3.0是通过提供ServletContainerInitializer
的实现类,并通过SPI来配置,供web服务器读取,springMVC也是如此,springmvc配置如下图:
其提供的实现类是org.springframework.web.SpringServletContainerInitializer
,源码如下详细看注释!!!
:
org.springframework.web.SpringServletContainerInitializer
// 该类设计的目的是让开发人员基于编码的方式来支持servlet容器,看到@HandlesTypes(WebApplicationInitializer.class),代表该类设置web容器在回调时,将classpath下的WebApplicationInitializer的实现类作为参数传递到方法onStartup的参数webAppInitializerClasses
// 中,这种是和web.xml对立的方式(也可能和web.xml方式混合使用)
// 操作机制:当支持servlet3的web容器启动的时候,会通过jar servicec API(ServiceLoader.load(xxx))从classpath下的spring-web.jar包中读取META-INF/services/javax.servlet.ServletContainerInitializer文件,在该文件中配置的实现类正是该类,
// 然后就会调用该类的onStartup方法,并将classpath下WebApplicationInitializer的实现类作为参数传递到webAppInitializerClasses参数中
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
// 参数webAppInitializerClasses:classpath下WebApplicationInitializer的实现类
// 参数servletContext:web容器servlet上下文对象
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
// web容器传进来的webAppInitializerClasses不为空
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// 因为一些web容器会将@HandlesTypes指定的类型外的一些类传进来,所以再进一步做个判断,可以说是因为web容器的bug而不得不写的代码
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
// 添加到initializers集合中
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
// initializers为空,即在classpath下没有WebApplicationInitializer的子类,简单的给出日志提示,并return
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
// 日志记录在classpath下发现了多少个WebApplicationInitializer的子类
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
// 排序
AnnotationAwareOrderComparator.sort(initializers);
// 循环调用onoStartup方法,注册web组件到ServletContext中
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
到此处,我们就知道,想要在无web.xml文件的情况下使用springMVC,只需要提供一个org.springframework.web.WebApplicationInitializer
的实现类,然后在其onStartup
方法中注册web容器相关组件就可以了,其源码如下详细看注释!!!
:
// 该接口用来在servlet3.0环境中通过编程的方式配置ServletContext(注册Servlet,Filter,Listener等),与之对应的是基于web.xml的配置方式(二者有时候也可以混用)。具体的实现类会通过SpringServletContainerInitializer(web容器的构子类)类加载并调用。
// 一般开发人员会通过web.xml方式来注册DispatcherServlet(当然还有其他组件)到web容器的。可能如下:
/*
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
*/
// 如果是使用WebApplicationInitializer的等价编程配置方式的话,可能如下:
/*
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(appContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
*/
// 例子中是直接实现WebApplicationInitializer接口,实际中可以通过继承AbstractDispatcherServletInitializer类来完成操作,
// 该类已经完成了注册DispatcherServlet等基础工作,我们只需要实现其抽象方法提供IOC容器就可以了
// 程序中“appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");”还是使用了xml文件,我们可以使用基于注解的spring IOC容器来改造代码,实现零xml配置,代码可能如下:
/*
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(AppConfig.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherContext =
new AnnotationConfigWebApplicationContext();
dispatcherContext.register(DispatcherConfig.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
*/
public interface WebApplicationInitializer {
// 在该方法中可以对servletContext进行servlets,fitlers,listeners,context-params以及属性信息的配置。
void onStartup(ServletContext servletContext) throws ServletException;
}
既然是springMVC程序,首先当然得定义一个处理请求的handler了,如下:
@Controller
@RequestMapping("/testinterceptor")
public class HelloController {
@PostConstruct
public void xxx() {
System.out.println("HelloController.xxx");
}
@RequestMapping("/hi")
@ResponseBody
public String sayHi(HttpServletResponse response) {
System.out.println("HelloController.sayHi");
String msg = "testinterceptor hi";
System.out.println(msg);
return msg;
}
}
然后我们定义WebApplicationInitializer的子类,如下:
public class MyWebXml implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("MyWebXml 加载开始!");
// new springmvc的容器对象
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
// 注册控制器
ctx.register(HelloController.class);
// 设置servlet上线文对象
ctx.setServletContext(servletContext);
String dispatcherServletName = DispatcherServlet.class.getSimpleName();
// 注册springmvc分发请求的DispatcherServlet
/*
相当于在web.xml中配置代码:
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
*/
ServletRegistration.Dynamic dynamicDispatcherServlet = servletContext.addServlet(dispatcherServletName, new DispatcherServlet(ctx));
dynamicDispatcherServlet.addMapping("/");
dynamicDispatcherServlet.setLoadOnStartup(1);
System.out.println("MyWebXml 加载结束!");
}
}
此时我们就可以启动程序来访问我们的接口了,如下:
C:\Users\Administrator>curl http://localhost:10080/springmvc_without_webxml_war_exploded/testinterceptor/hi
testinterceptor hi
当然我们也可以通过实现WebApplicationInitializer的抽象子类AbstractDispatcherServletInitializer
,该类已经完成了DispatcherServlet的注册工作,可以修改MyWebXml
如下:
public class MyWebXml extends AbstractDispatcherServletInitializer {
private static AnnotationConfigWebApplicationContext servletAc = new AnnotationConfigWebApplicationContext();
private static AnnotationConfigWebApplicationContext rootAc = new AnnotationConfigWebApplicationContext();
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
// 注册handler
servletAc.register(HelloController.class);
}
/*
创建 dispatcherservlet的Spring IOC容器
*/
@Override
protected WebApplicationContext createServletApplicationContext() {
return servletAc;
}
/*
返回DispatcherServlet的servletmapping信息
*/
@Override
protected String[] getServletMappings() {
List<String> servletMappingList = new ArrayList<>();
servletMappingList.add("/");
return StringUtils.toStringArray(servletMappingList);
}
// 创建root 的spring IOC容器
@Override
protected WebApplicationContext createRootApplicationContext() {
return rootAc;
}
}
效果是完全一样的。测试源码从这里下载。
3:springboot如何使用servlet
测试源码从这里下载。
3.1:通过servlet3注解+@ServletComponentScan
通过servlet3.0中定义的@WebXxx
相关注解,然后通过@ServletComponentScan
配置要扫描的包路径,如下测试:
- 定义servlet
@WebServlet("/myservlet")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("res from spring boot servlet!!!");
}
}
- 启动类
@SpringBootApplication
@ServletComponentScan(basePackages = { "com.example.springbootintegarationspringmvc" })
public class SpringbootIntegrationSpringmvcApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootIntegrationSpringmvcApplication.class, args);
}
}
- 启动测试
C:\Users\Administrator>curl http://localhost:8080/myservlet
res from spring boot servlet!!!
3.2:通过RegistrationBean
org.springframework.boot.web.servlet.RegistrationBean
是springboot提供的抽象类,实现了ServletContextInitializer
接口,负责将servlet,filter,listener等注册到spring IOC容器中。其源码如下:
// 用于在servlet3.0+环境中程序化方式配置servletContext的接口。与实现了WebApplicationInitializer接口
// 的类不同,不会被SpringServletContainerInitializer自动获取,因此不会被web容器自动加载。
// 但是其扮演的角色和ServletContainerInitializer类似,不同之处在于通过spring来完成生命周期
// 的管理,而非像ServletContainerInitializer是通过web容器管理生命周期。
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
private static final Log logger = LogFactory.getLog(RegistrationBean.class);
private int order = Ordered.LOWEST_PRECEDENCE;
private boolean enabled = true;
// 这里实现具体的逻辑,完成web组件,如servlet,filter,listener等向servletcontext中
// 注册的工作,但是注意这个过程是spring在启动web容器的过程中完成的,因为此时web容器只是
// springboot的一个组件而已。
// 启动过程是"springboot main->启动web容器->调用该方法配置servletContext"
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();
if (!isEnabled()) {
logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
return;
}
register(description, servletContext);
}
protected abstract String getDescription();
protected abstract void register(String description, ServletContext servletContext);
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return this.enabled;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
}
首先来定义一个servlet:
public class MyServlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello from my servlet 1");
}
}
定义Java config类:
@Configuration
public class WebComponentConfiguration {
@Bean
public ServletRegistrationBean myServlet1() {
ServletRegistrationBean myServlet1 = new ServletRegistrationBean();
myServlet1.addUrlMappings("/myservlet1");
myServlet1.setServlet(new MyServlet1());
return myServlet1;
}
}
启动类:
@SpringBootApplication
@ServletComponentScan(basePackages = { "com.example.springbootintegarationspringmvc" })
public class SpringbootIntegrationSpringmvcApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootIntegrationSpringmvcApplication.class, args);
}
}
启动测试:
$ curl.exe --silent http://192.168.10.119:8080/myservlet1
hello from my servlet 1
接下来,我们看下springboot是如何最终通过调用我们的RegistrationBean来完成ServletContext的设置的。
3.3:调用过程分析
基于springboot V1.5.4RELEASE版本分析。
程序最终会执行到org.springframework.boot.context.embedded.EmbeddedWebApplicationContext
,这是AbstractApplicationContext的一个子类,在AbstractApplicationContext类中refresh方法中会调用一个空方法onRefresh
,该方法的作用是供子类注册特殊场景下的bean,EmbeddedWebApplication类正是通过重写onRefresh方法来加入到了spring的生命周期中,我们就从这个方法开始分析,源码如下:
org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#onRefresh
protected void onRefresh() {
super.onRefresh();
try {
// <202106031347>
// 创建servlet容器
createEmbeddedServletContainer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start embedded container",
ex);
}
}
<202106031347>
处是创建servlet容器,源码如下:
org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#createEmbeddedServletContainer
private void createEmbeddedServletContainer() {
// localContainer为null
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
// localServletContext为null
ServletContext localServletContext = getServletContext();
// if为true
if (localContainer == null && localServletContext == null) {
// 获取内嵌web容器工厂类
// <202106031553>
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
// <202106031416>
// 创建内嵌web容器对象
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());
}
...snip...
initPropertySources();
}
<202106031553>
处获取的类是通过springboot的autoconfigurationorg.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration
定义的,源码如下:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
...snip...
}
<202106031416>
处重点看下方法getSelfInitializer()
,源码如下:
org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#getSelfInitialize
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
// 返回ServletContextInitializer匿名实现类
// 注意这里返回的是ServletContextInitializer
return new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
selfInitialize(servletContext);
}
};
}
org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#selfInitialize
// 上述匿名实现类调用的方法
private void selfInitialize(ServletContext servletContext) throws ServletException {
...snip...
// <202106031436>
// 获取所有的ServletContextInitializer实现类,并循环调用其onStartup方法
// 这里其实就会获取到我们通过Javaconfig注册的RegistrationBean了
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
// 重点!!!
// 包括但不限制于完成DispatahcerServlet注册到ServletContext的工作,以及自定义的RegsitrationBean实现类的相关web组件的注册工作
beans.onStartup(servletContext);
}
}
<202106031436>
处重点看下getServletContextInitializerBeans()
,源码如下:
org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#getServletContextInitializerBeans
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
// <202106031440>
return new ServletContextInitializerBeans(getBeanFactory());
}
<202106031440>
处源码如下:
org.springframework.boot.web.servlet.ServletContextInitializerBeans#ServletContextInitializerBeans
public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
...snip...
// <202106031443>
addServletContextInitializerBeans(beanFactory);
...snip...
}
<202106031443>
处源码如下:
org.springframework.boot.web.servlet.ServletContextInitializerBeans#addServletContextInitializerBeans
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
// <202106031447>
for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
beanFactory, ServletContextInitializer.class)) {
// 将servletcontextinitializer实现类添加到private final MultiValueMap<Class<?>, ServletContextInitializer> initializers集合中
//, 后续会循环遍历该集合调用onStartup方法
addServletContextInitializerBean(initializerBean.getKey(),
initializerBean.getValue(), beanFactory);
}
}
<202106031447>
处getOrderedBeansOfType方法就获取IOC容器中的ServletContextInitializer的实现类,如下图就是我们通过Java config定义的以及springboot用于注册dispatcherservler注册的:
注意到此处springboot会获取到IOC容器中所有的ServletContextInitializer的实现类,并调用其onStartup方法,因此我们也可以通过提供ServletContextInitializer的实现类的Java config的方式来注册web组件到内嵌的web容器中,如下定义实现类:
public class MyServlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello from servlet 2");
}
}
@Configuration
public class MyServletContextInitializer implements ServletContextInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addServlet(MyServlet2.class.getSimpleName(), MyServlet2.class).addMapping("/myservlet2");
}
}
获取到该java config如下图:
启动测试:
$ curl.exe --silent http://192.168.10.119:8080/myservlet2
hello from servlet 2
4:ServletWebServerApplicationContext
ServletWebServerApplicationContext方法签名如下:
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext
可以看出这是spring的ApplicationContext容器的一个子类,是当springboot使用内置web容器时的IOC容器类。会将内置web服务器生命周期加入到spring容器的生命周期中,重点看下其父接口org.springframework.boot.web.context.WebServerApplicationContext
,源码如下:
// 管理内置web服务器对象WebServer生命周期的接口
public interface WebServerApplicationContext extends ApplicationContext {
/**
* Returns the {@link WebServer} that was created by the context or {@code null} if
* the server has not yet been created.
* @return the web server
*/
WebServer getWebServer();
String getServerNamespace();
static boolean hasServerNamespace(ApplicationContext context, String serverNamespace) {
return (context instanceof WebServerApplicationContext) && ObjectUtils
.nullSafeEquals(((WebServerApplicationContext) context).getServerNamespace(), serverNamespace);
}
}
有了该接口,就拥有了控制内置web服务器的能力了,这很重要!!!接下来我们看下其加入到SpringIOC容器生命周期而重写的重要方法。
4.1:refresh
ServletWebServerApplicationContext复写了父类AbstractApplicationContext的refresh方法,增加了当刷新容器的过程中发生异常停止web服务器的操作,源码如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh
@Override
public final void refresh() throws BeansException, IllegalStateException {
try {
super.refresh();
}
catch (RuntimeException ex) {
// <202106041055>
// 发生异常,停止并释放web服务器资源
stopAndReleaseWebServer();
throw ex;
}
}
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#stopAndReleaseWebServer
private void stopAndReleaseWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
try {
// 停止web服务器
webServer.stop();
// 置空web服务器
this.webServer = null;
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
4.2:postProcessBeanFactory
ServletWebServerApplicationContext重写了AbstractApplicationContext的模板方法postProcessBeanFactory,执行该方法时,bean工厂对象已经创建完毕,所有的bean定义也都加载完毕,但是还没有进行任何spring bean的创建,源码如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#postProcessBeanFactory
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// <202106041124>
beanFactory.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(this));
// <202106041759>
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
// 该处代码先忽略
registerWebApplicationScopes();
}
<202106041124>
处添加的WebApplicationContextServletContextAwareProcessor
是ServletContextAwareProcessor
类的子类,构造函数设置的是ServletWebServerApplicationContext类的实例,后续就可以通过该实例来获取ServletContext了。在bean生命周期初始化bean的过程中如果该bean实现了ServletContextAware接口,则会调用其对应的setServetContext方法设置ServletContext对象,这样在bean中就可以拿到内嵌web容器的ServletContext对象了,如下图是调用时debug信息:
代码位置org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization
:
<202106041759>
处ignoreDependencyInterface忽略ServletContextAware类型的属性注入,因为会通过WebApplicationContextServletContextAwareProcessor
处理属性,这里有个疑问,ServletContext外的其他属性怎么办呢?也不需要自动注入吗?
。
4.3:onRefresh
在AbstractApplicationContext类中refresh方法中会调用一个空方法onRefresh
,该方法的作用是供子类注册特殊场景下的bean,接下来我们看下web容器对应的ApplicationContext类通过该方法实现了什么功能。源码如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
@Override
protected void onRefresh() {
super.onRefresh();
try {
// <202106060911>
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
<202106060911>
处是创建web服务器,注意此时只是创建,还没有启动web服务器,具体参考4.3.1:createWebServer
。
4.3.1:createWebServer
源码如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
private void createWebServer() {
// 获取webServer
WebServer webServer = this.webServer;
// 获取servletcontext
ServletContext servletContext = getServletContext();
// webserver为null,servletcontext也为null时
if (webServer == null && servletContext == null) {
// <202106061108>
ServletWebServerFactory factory = getWebServerFactory();
// <202106061208>
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
...snip...
}
// 初始化PropertySource
initPropertySources();
}
<202106061108>
处获取用于创建webserver的工厂对象,源码如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#getWebServerFactory
protected ServletWebServerFactory getWebServerFactory() {
// 从IOC容器中获取类型ServletWebServerFactory的的spring bean的名称
// 的数组,bean是在org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration类中配置的,如下:
/*
@Configuration
class ServletWebServerFactoryConfiguration {
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
}
*/
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
// bean的个数不是1个就抛出异常,0个和大于1个时异常信息稍有不同
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));
}
// 执行到此处,说明只有一个类型为ServletWebServerFactory的bean
// 直接根据bean名称从spring IOC容器中获取spring bean对象,并返回
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
<202106061208>
处是通过webserver的工厂对象,获取webserver实例,其中的getSelfInitializer()
,主要是返回ServletContextInitializer对象,用于设置内置web容器的servletcontext对象,在3:springboot如何集成spring MVC
中已经详细分析过了,源码如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#getSelfInitializer
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
// 匿名实现类,将this作为接口方法的参数,并调用其selfInitialize方法
/*
return new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
selfInitialize(servletContext);
}
};
*/
// 这种写法可以有效提高生产力,值得在工作中使用
return this::selfInitialize;
}
关于selfInitialize方法具体看4.3.2:selfInitialize
。
4.3.2:selfInitialize
源码如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize
private void selfInitialize(ServletContext servletContext) throws ServletException {
// <202106061603>
prepareWebApplicationContext(servletContext);
// 忽略
registerApplicationScope(servletContext);
// 忽略
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// 逐个调用ServletContextInitializer,比如DispatcherServlet
// 的注册就是通过这里的方法调用完成的,这个我们在"3:springboot如何集成spring MVC"
// 已经分析过了
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
<202106061603>
处是注册ROOT IOC容器到servletcontext中,该处执行的逻辑和常规springMVC程序中ContextLoaderListener
执行的操作类似,都是IOC容器和servletcontext的关联,源码如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#prepareWebApplicationContext
protected void prepareWebApplicationContext(ServletContext servletContext) {
// 获取ROOT IOC 容器
Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
// 如果是IOC容器不为空
if (rootContext != null) {
// 如果是已存在的和当前的相等
if (rootContext == this) {
// 是否多个ServletContextInititalizer导致重复的异常信息提示
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - "
+ "check whether you have multiple ServletContextInitializers!");
}
// 已存在,且不是当前的,则直接return
return;
}
Log logger = LogFactory.getLog(ContextLoader.class);
// 该日志在springboot程序启动时会看到,打印初始化内嵌web应用的IOC容器开始信息
// ,是springboot启动过程中的重要节点
servletContext.log("Initializing Spring embedded WebApplicationContext");
try {
// 设置IOC容器到servletcontext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name ["
+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
// 调用servletcontext属性的写方法设置到全局变量
setServletContext(servletContext);
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - getStartupDate();
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
4.4:finishRefresh
源码:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#finishRefresh
@Override
protected void finishRefresh() {
super.finishRefresh();
// <202106061625>
WebServer webServer = startWebServer();
// 启动成功,发布事件
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
<202106061625>
处是启动web服务器,源码如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#startWebServer
private WebServer startWebServer() {
// 获取web服务器对象WebServer
WebServer webServer = this.webServer;
if (webServer != null) {
// <202106061632>
// 启动
webServer.start();
}
// 返回
return webServer;
}
<202106061632>
处执行完毕后会输出如下的日志信息,也是日志信息中比较关键的,如下图:
4.5:onClose
源码如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onClose
@Override
protected void onClose() {
super.onClose();
// <202106061643>
stopAndReleaseWebServer();
}
<202106061643>
处源码如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#stopAndReleaseWebServer
private void stopAndReleaseWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
try {
webServer.stop();
this.webServer = null;
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}