前面我们已经知道了servlet3.0之后给我们留下的两个扩展ServletContainerInitializer和ServletContextListener,而且在上一篇内容里面我们已经看到过springmvc为了加载根容器,已经使用过ServletContextListener了,那现在来看一看ServletContainerInitializer有没有被使用。
SpringServletContainerInitializer是目前唯一的实现,而且在第一篇内容里面就已经讲过了他的具体用法,果然springmvc用了
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
ners provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
看看他传进来的class都有些啥,看实现一共有4个类,其中AbstractReactiveWebInitializer是响应式编程需要用到的,我们现在不关心,剩下另外三个抽象类都会被传进来,但是在onstartup方法里面明确说明了不会执行抽象类里面的onstartup方法,哪这个实现还有什么意义呢,我们去看一下。首先是AbstractContextLoaderInitializer,他在onstartup方法里面首先调用了一个创建Root容器的方法,只不过这个方法是在子类AbstractAnnotationConfigDispatcherServletInitializer里面实现的,如果创建成功了,就会去调用ContextLoaderListener的有参构造方法,ContextLoaderListener是我们上一篇才讲过的,大家应该不会陌生,只不过上一篇那个地方是通过默认的无参构造创建的,然后里面那个WebApplicationContext也就是root容器自然就是null,所以会默认帮我们创建一个xmlWebApplicationContext类型的容器,但是这里主动穿了一个容器进去,所以那里面的逻辑就会改变了
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
这一步就会直接跳过了,取而代之就是这里传进去的WebApplicationContext,而且我们知道ServletContainerInitializer的方法是先于ServletContextListener执行的,所以后续还是会执行到这个initWebApplicationContext里面来,而此时里面的root容器已经有值了,所以再回过头来看这个方法为什么叫createRootApplicationContext,想必大家已经懂了吧。
然后现在就该来看一看这个子类AbstractAnnotationConfigDispatcherServletInitializer是如何创建root的了,然后发现它其实也没干啥,调用了一个未实现的方法,那不用想,这个方法肯定是有我们自己来实现的,所以意思就是我们可以实现这个方法来创建我们自己的父容器,通过代码的方式,那样就不需要spring-config.xml了,那还有个springmvc-config.xml呢,这个嘛,不要慌,看AbstractDispatcherServletInitializer这个类。他重写了onstart方法,就多做了一步注册DispatcherServlet,同时注意到这几个类的继承关系会发现,只要我们继承了最下面这个抽象类AbstractAnnotationConfigDispatcherServletInitializer,那么onstartup就会被调用,这样一来,springmvc-config.xml也不需要了。而且由于有下面两行代码
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
通过动态注册的方式,我们连web.xml都不需要了,因为之前他存在的意义就只是为了声明一个ContextLoaderListener。到这里知道该怎么办了吧,所有xml统统不要了。
下面就来实现一下
/**
* 扫描 web子容器的bean
*/
@Configuration
@ComponentScan(basePackages = "me.ppx.mvc.springmvc.controller")
public class WebMvcConfig {
}
/**
* 扫描root容器中的bean
*/
@Configuration
@ComponentScan(basePackages = "me.ppx.mvc.springmvc.service")
public class RootConfig {
}
package me.ppx.mvc.springmvc.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* 通过继承AbstractAnnotationConfigDispatcherServletInitializer来注册Spring root 容器
* 同时在父类 AbstractDispatcherServletInitializer中自动又注册了DispatcherServlet
* 而且还注册了 ContextLoaderListener
* 所以到这里可以抛弃所有XMl配置文件
*
* 该class会自动被WebApplicationInitializer扫描
*/
public class WithOutXml extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 创建 root 容器 扫描除开controller的bean
*
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
/**
* 创建 web子容器 扫描controller
*
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
package me.ppx.mvc.springmvc.controller;
import me.ppx.mvc.springmvc.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
@RestController
@RequestMapping("/mvc")
public class Controller {
@Autowired
private TestService service;
@PostConstruct
public void init() {
System.out.println(service.getDate());
}
@GetMapping("/date")
public String getDate() {
System.out.println("通过mvc请求到controller");
return service.getDate();
}
}
只需要继承AbstractAnnotationConfigDispatcherServletInitializer并重写两个提供容器的方法,这样一个抛弃web.xml的基本的web服务就完成了