文章目录
1、前言
在《SpringMVC学习(五)——零配置实现SpringMVC》这篇文章中我们没有使用Spring的配置实现了一个正常的SpringMVC的功能,里面核心的一个点就是使用了WebApplicationInitializer
,那这篇文章就详细说明一下这个接口的作用。
2、WebApplicationInitializer的定义
从起初的Spring配置文件,到后来的Spring支持注解到后来的SpringBoot,Spring框架在一步步的使用注解的方式来去除Spring的配置的发展过程。WebApplicationInitializer
就是取代web.xml
配置的一个接口。
public interface WebApplicationInitializer {
void onStartup(ServletContext var1) throws ServletException;
}
通过覆盖接口提供的onStartup
方法我们可以往Servlet容器
里面添加我们需要的servlet
、listener
等,并且在Servlet容器
启动的过程中就会加载这个接口的实现类,从而起到和web.xml
相同的中作用,从而可以替代以前在web.xml中所做的配置。
3、实现原理
我们首先可以从Spring源码中找到SpringServletContainerInitializer
实现类。
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
LinkedList initializers = new LinkedList();
Iterator var4;
if(webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class initializer = (Class)var4.next();
if(!initializer.isInterface() && !Modifier.isAbstract(initializer.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(initializer)) {
try {
initializers.add((WebApplicationInitializer)initializer.newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if(initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
var4 = initializers.iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer1 = (WebApplicationInitializer)var4.next();
initializer1.onStartup(servletContext);
}
}
}
}
这个类上面有@HandlesTypes({WebApplicationInitializer.class})
这个注解,这个注解的作用是将其value中配置的一些类放入到ServletContainerInitializer
。
initializers.add((WebApplicationInitializer)initializer.newInstance());
最后通过循环去执行WebApplicationInitializer
中的onStartup
方法来实现里面的具体的具体的逻辑。
while(var4.hasNext()) {
WebApplicationInitializer initializer1 = (WebApplicationInitializer)var4.next();
initializer1.onStartup(servletContext);
}
那问题来了,Tomcat容器怎么知道先执行这个SpringServletContainerInitializer
类?
这里涉及一个知识点SPI机制
,SPI
全称为 Service Provider Interface
,是一种服务发现机制。SPI机制
是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI机制
为我们的程序提供拓展功能。
Tomcat启动过程中查找所有的ServletContainerInitializer
实现类然后添加到StandardContext的initializers
集合中,然后执行里面的onStartup
方法。
Spring中SPI就是通过SpringServletContainerInitializer
类来实现的
关于Web应用的启动过程在《一个基于注解配置的Web项目的启动流程分析》这篇文章写的很好。
4、利用SPI我们能做什么?
可以加一些自己的启动配置信息,把自己的Servlet打成jar包放到Tomcat服务器或者其他工程中执行。我们可以实现一个自己的SPI接口。
4.1、定义一个MyWebAppInitializer
public interface MyWebAppInitializer {
void loadOnStart(ServletContext var1) throws ServletException;
}
4.2、定义一个MySpringServletContainerInitializer
- 实现
ServletContainerInitializer
接口 - 修改
@HandlesTypes
为我们自己定义的MyWebAppInitializer.class
- 实例化
MyWebAppInitializer
的实现类,并且调用接口的loadOnStart
方法
@HandlesTypes(MyWebAppInitializer.class)
public class MySpringServletContainerInitializer implements ServletContainerInitializer{
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletcontext)
throws ServletException {
if (set != null) {
for (Class<?> waiClass : set) {
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
MyWebAppInitializer.class.isAssignableFrom(waiClass)) {
try {
//创建MyWebAppInitializer实现类的对象,并调用loadOnStart方法
((MyWebAppInitializer) waiClass.newInstance()).loadOnStart(servletcontext);
} catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
}
}
4.3、写一个MyWebAppInitializer
的实现类
public class MyWebApplicationInitializerTest implements MyWebAppInitializer{
@Override
public void loadOnStart(ServletContext servletContext){
System.out.println("启动执行MyWebApplicationInitializerTest的loadOnStart方法");
//注册一个为名字call的servlet
ServletRegistration.Dynamic servletReg = servletContext.addServlet("call", CallServlet.class);
servletReg.setLoadOnStartup(1);
servletReg.addMapping("/call");
}
}
其中里面的CallServlet.java
如下
public class CallServlet extends HttpServlet {
private static final long serialVersionUID = 3684613967452881093L;
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
resp.getWriter().write(name + ", if you like me, please call me!");
}
}
4.4、添加配置文件javax.servlet.ServletContainerInitializerr
添加位置为:src -> main -> resources-> META-INF-> services ->javax.servlet.ServletContainerInitializerr
内容:com.leo.spi.MySpringServletContainerInitializer
4.5、启动项目测试
启动日志:
启动执行MyWebApplicationInitializerTest的loadOnStart方法
浏览器测试:http://localhost:8080/springmvc/call?name=leo825
上面也提到了,可以把这个SPI的方式打成jar包在其他项目或者直接在Tomcat容器这种运行。
本文的相关源码请参考:chapter-5-springmvc-zero-configuration
spi代码包路径:com.leo.spi
https://gitee.com/leo825/spring-framework-learning-example.git
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/72699.html