- pom.xml 内容如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.qsm</groupId>
<artifactId>learn</artifactId>
<version>1.0.0</version>
</parent>
<groupId>com.qs.demo</groupId>
<artifactId>demo-077</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.28</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
- com.qs.demo.MyWebApplicationInitializer 内容如下
package com.qs.demo;
import com.qs.demo.root.AppConfig;
import com.qs.demo.sub.DispatcherConfig;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
/**
* @author qs
* @date 2024/09/24
*/
public class MyWebApplicationInitializer implements WebApplicationInitializer {
// 这个例子来自于 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);
// 一个 DispatcherServlet 包圆了所有的请求
// 可以搞多个 DispatcherServlet 分别处理不同的请求
dispatcher.addMapping("/");
}
}
- com.qs.demo.root.AppConfig 内容如下
package com.qs.demo.root;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author qs
* @date 2024/09/24
*/
@Configuration
@ComponentScan("com.qs.demo.root")
public class AppConfig {}
- com.qs.demo.root.AppleService 内容如下
package com.qs.demo.root;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import java.util.UUID;
/**
* @author qs
* @date 2024/09/24
*/
@Service
public class AppleService implements ApplicationContextAware {
private ApplicationContext applicationContext;
public String a() {
System.out.println(applicationContext);
return UUID.randomUUID().toString();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
- com.qs.demo.sub.DispatcherConfig 内容如下
package com.qs.demo.sub;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* @author qs
* @date 2024/09/24
*/
@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "com.qs.demo.sub")
public class DispatcherConfig {}
- com.qs.demo.sub.BananaService 内容如下
package com.qs.demo.sub;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import java.util.UUID;
/**
* @author qs
* @date 2024/09/24
*/
@Service
public class BananaService implements ApplicationContextAware {
public String a() {
return UUID.randomUUID().toString();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println(applicationContext);
}
}
- com.qs.demo.sub.Demo01Controller 内容如下
package com.qs.demo.sub;
import com.qs.demo.root.AppleService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author qs
* @date 2024/09/24
*/
@RestController
public class Demo01Controller {
@Resource private AppleService appleService;
@Resource private BananaService bananaService;
@GetMapping("/01")
public String a() {
return "01";
}
@GetMapping("/02")
public String b() {
return appleService.a();
}
@GetMapping("/03")
public String c() {
return bananaService.a();
}
}
以上就是全部代码
写这个例子主要是为了介绍 WebApplicationInitializer 这个接口。简单点儿讲就是这个接口等价于 web.xml 这个配置文件。
写了这个接口就不用写 web.xml 配置文件了。
下面重点看下这个接口的 javadoc
/**
* <p>
* 下面这段话的关键点:
* 1、适用于 servlet 3.0+ 环境
* 2、该接口可以看做是传统的 web.xml 的替代品
* </p>
*
* Interface to be implemented in Servlet 3.0+ environments in order to configure the
* {@link ServletContext} programmatically -- as opposed to (or possibly in conjunction
* with) the traditional {@code web.xml}-based approach.
*
* <p>
* 下面这段话的意思:
* 1、该接口的实现会自动被 SpringServletContainerInitializer 检测到
* 2、而 SpringServletContainerInitializer 又会被 servlet 3.0 容器自动带起来
* </p>
*
* <p>Implementations of this SPI will be detected automatically by {@link
* SpringServletContainerInitializer}, which itself is bootstrapped automatically
* by any Servlet 3.0 container. See {@linkplain SpringServletContainerInitializer its
* Javadoc} for details on this bootstrapping mechanism.
*
* <p>注意下面这个例子。看本接口是怎样替换 web.xml 配置的</p>
*
* <h2>Example</h2>
* <h3>The traditional, XML-based approach</h3>
* Most Spring users building a web application will need to register Spring's {@code
* DispatcherServlet}. For reference, in WEB-INF/web.xml, this would typically be done as
* follows:
* <pre class="code">
* {@code
* <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>}</pre>
*
* <h3>The code-based approach with {@code WebApplicationInitializer}</h3>
* Here is the equivalent {@code DispatcherServlet} registration logic,
* {@code WebApplicationInitializer}-style:
* <pre class="code">
* 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("/");
* }
*
* }</pre>
*
* <p>
* 上面的例子是实现 WebApplicationInitializer 接口。
* 也可以继承 AbstractDispatcherServletInitializer 类。
* </p>
*
* As an alternative to the above, you can also extend from {@link
* org.springframework.web.servlet.support.AbstractDispatcherServletInitializer}.
*
* As you can see, thanks to Servlet 3.0's new {@link ServletContext#addServlet} method
* we're actually registering an <em>instance</em> of the {@code DispatcherServlet}, and
* this means that the {@code DispatcherServlet} can now be treated like any other object
* -- receiving constructor injection of its application context in this case.
*
* <p>This style is both simpler and more concise. There is no concern for dealing with
* init-params, etc, just normal JavaBean-style properties and constructor arguments. You
* are free to create and work with your Spring application contexts as necessary before
* injecting them into the {@code DispatcherServlet}.
*
* <p>Most major Spring Web components have been updated to support this style of
* registration. You'll find that {@code DispatcherServlet}, {@code FrameworkServlet},
* {@code ContextLoaderListener} and {@code DelegatingFilterProxy} all now support
* constructor arguments. Even if a component (e.g. non-Spring, other third party) has not
* been specifically updated for use within {@code WebApplicationInitializers}, they still
* may be used in any case. The Servlet 3.0 {@code ServletContext} API allows for setting
* init-params, context-params, etc programmatically.
*
* <p>
* 在上面的例子中,web.xml 被替换了,但是 dispatcher-config.xml 依然存在。
* 下面的例子就彻底摆脱 xml 配置了。
* </p>
*
* <h2>A 100% code-based approach to configuration</h2>
* In the example above, {@code WEB-INF/web.xml} was successfully replaced with code in
* the form of a {@code WebApplicationInitializer}, but the actual
* {@code dispatcher-config.xml} Spring configuration remained XML-based.
* {@code WebApplicationInitializer} is a perfect fit for use with Spring's code-based
* {@code @Configuration} classes. See @{@link
* org.springframework.context.annotation.Configuration Configuration} Javadoc for
* complete details, but the following example demonstrates refactoring to use Spring's
* {@link org.springframework.web.context.support.AnnotationConfigWebApplicationContext
* AnnotationConfigWebApplicationContext} in lieu of {@code XmlWebApplicationContext}, and
* user-defined {@code @Configuration} classes {@code AppConfig} and
* {@code DispatcherConfig} instead of Spring XML files. This example also goes a bit
* beyond those above to demonstrate typical configuration of the 'root' application
* context and registration of the {@code ContextLoaderListener}:
* <pre class="code">
* 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("/");
* }
*
* }</pre>
*
* As an alternative to the above, you can also extend from {@link
* org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer}.
*
* Remember that {@code WebApplicationInitializer} implementations are <em>detected
* automatically</em> -- so you are free to package them within your application as you
* see fit.
*
* <p>
* 多个 WebApplicationInitializer 之间可以指定顺序。但是这种一般不常用。一个 WebApplicationInitializer 就够了。
* </p>
*
* <h2>Ordering {@code WebApplicationInitializer} execution</h2>
* {@code WebApplicationInitializer} implementations may optionally be annotated at the
* class level with Spring's @{@link org.springframework.core.annotation.Order Order}
* annotation or may implement Spring's {@link org.springframework.core.Ordered Ordered}
* interface. If so, the initializers will be ordered prior to invocation. This provides
* a mechanism for users to ensure the order in which servlet container initialization
* occurs. Use of this feature is expected to be rare, as typical applications will likely
* centralize all container initialization within a single {@code WebApplicationInitializer}.
*
* <h2>Caveats</h2>
*
* <p>
* 注意 web.xml 和 WebApplicationInitializer 不是相互排斥的。
* 二者可以同时存在。
* </p>
*
* <h3>web.xml versioning</h3>
* <p>{@code WEB-INF/web.xml} and {@code WebApplicationInitializer} use are not mutually
* exclusive; for example, web.xml can register one servlet, and a {@code
* WebApplicationInitializer} can register another. An initializer can even
* <em>modify</em> registrations performed in {@code web.xml} through methods such as
* {@link ServletContext#getServletRegistration(String)}. <strong>However, if
* {@code WEB-INF/web.xml} is present in the application, its {@code version} attribute
* must be set to "3.0" or greater, otherwise {@code ServletContainerInitializer}
* bootstrapping will be ignored by the servlet container.</strong>
*
* <h3>Mapping to '/' under Tomcat</h3>
* <p>Apache Tomcat maps its internal {@code DefaultServlet} to "/", and on Tomcat versions
* <= 7.0.14, this servlet mapping <em>cannot be overridden programmatically</em>.
* 7.0.15 fixes this issue. Overriding the "/" servlet mapping has also been tested
* successfully under GlassFish 3.1.<p>
*
* @author Chris Beams
* @since 3.1
* @see SpringServletContainerInitializer
* @see org.springframework.web.context.AbstractContextLoaderInitializer
* @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
* @see org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
*/
多说一嘴,这个例子中用到了 DispatcherServlet 的有参构造(DispatcherServlet只有2个构造方法,一个无参一个有参,无参构造在其他文章中介绍了)。
这里就顺便详细看下这个有参构造的 javadoc
/**
* 使用给定的 web 应用上下文来创建一个 DispatcherServlet。
* 无参的构造是在 DispatcherServlet 内部自己创建维护 web 应用上下文。
* 这个有参的构造是用外部传过来的 web 应用上下文。
* 这个构造方法主要是用在 servlet 3.0+ 的环境中。
* 用了这个构造方法以后,setContextClass setContextConfigLocation setContextAttribute setNamespace 这4个方法就无效了。
* 对应的 contextClass contextConfigLocation contextAttribute namespace 等四个 servlet init-param 也无效了。
* 传进来的 web 应用上下文可能已经 refresh() 了,也可能没有 refresh()。
* 建议是不要 refresh()。
* 如果不刷新的话,将会发生这些事情:
* 1、如果传进来的web应用上下文还没有设置父应用上下文,则将 root web 应用上下文设置为它的父应用上下文。
* 2、如果传进来的web应用上下文还没有设置id,将会给它设置一个id。
* 3、将ServletContext和ServletConfig存到web应用上下文中。
* 4、postProcessWebApplicationContext 方法会被调用。
* 5、ApplicationContextInitializer 会被调用。可以用这个东西对web应用上下文进行自定义配置,其他文章也有提过。
* 6、如果传进来的web应用上下文实现了ConfigurableApplicationContext的话,refresh()方法将会被调用。
* 如果传进来的web应用上下文已经 refresh() 过了,上面提到的几点都不会发生。
*
* 另外,这个有参构造怎么用,可以参考 WebApplicationInitializer 接口。这不就跟本文写的代码对应上了么。
*
* Create a new {@code DispatcherServlet} with the given web application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based registration
* of servlets is possible through the {@link ServletContext#addServlet} API.
* <p>Using this constructor indicates that the following properties / init-params
* will be ignored:
* <ul>
* <li>{@link #setContextClass(Class)} / 'contextClass'</li>
* <li>{@link #setContextConfigLocation(String)} / 'contextConfigLocation'</li>
* <li>{@link #setContextAttribute(String)} / 'contextAttribute'</li>
* <li>{@link #setNamespace(String)} / 'namespace'</li>
* </ul>
* <p>The given web application context may or may not yet be {@linkplain
* ConfigurableApplicationContext#refresh() refreshed}. If it has <strong>not</strong>
* already been refreshed (the recommended approach), then the following will occur:
* <ul>
* <li>If the given context does not already have a {@linkplain
* ConfigurableApplicationContext#setParent parent}, the root application context
* will be set as the parent.</li>
* <li>If the given context has not already been assigned an {@linkplain
* ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
* <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
* the application context</li>
* <li>{@link #postProcessWebApplicationContext} will be called</li>
* <li>Any {@code ApplicationContextInitializer}s specified through the
* "contextInitializerClasses" init-param or through the {@link
* #setContextInitializers} property will be applied.</li>
* <li>{@link ConfigurableApplicationContext#refresh refresh()} will be called if the
* context implements {@link ConfigurableApplicationContext}</li>
* </ul>
* If the context has already been refreshed, none of the above will occur, under the
* assumption that the user has performed these actions (or not) per their specific
* needs.
* <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
* @param webApplicationContext the context to use
* @see #initWebApplicationContext
* @see #configureAndRefreshWebApplicationContext
* @see org.springframework.web.WebApplicationInitializer
*/
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
setDispatchOptionsRequest(true);
}