您当前的位置: 首页 >  容器

Servlet3.0新特性和Web容器如何整合SpringMVC

发布时间:2019-12-25 22:21:08 ,浏览量:5

一、引言

在Servlet3.0之前,我们在使用Spring MVC的时候,必须依赖web.xml配置文件,并且DispatcherServlet必须要在web.xml里配置。而Servlet3.0可以支持全部采用注解驱动,现在启动一个Web容器并不强制依赖于web.xml部署描述文件了,这大大简化了配置web.xml的麻烦。

既然用注解代替了了web.xml配置文件,那如何使用Spring MVC呢,下面通过一个例子来探索其中的原理。

二、创建一个SpringBoot工程

准备一个SpringMVC的Maven工程

写一个最基本的Servlet,然后就可以访问了http://localhost:8080/index:

@WebServlet(urlPatterns = "/index") public class IndexServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("hello from servlet"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } } // 启动类 @SpringBootApplication @ServletComponentScan //启动器启动时,扫描本目录以及子目录带有的webservlet注解的 public class SpringbootHelloworldApplication { public static void main(String[] args) { SpringApplication.run(SpringbootHelloworldApplication.class, args); } } 

在这里插入图片描述 是不是发现比之前采用web.xml配置的方式相比,省事太多了。

三、原理 1、ServletContainerInitializer

之前Web容器要整合其它模块,都是通过web.xml配置文件,但现在使用注解驱动的话,就没有了web.xml配置文件了,那怎么做呢?这就是Servlet3.0带来的特别特别重要的一个类:ServletContainerInitializer。我们用它来整合Web容器和其它模块。这里只做说明,不做解释,说明来源于官方文档。

Servlet容器启动会扫描当前应用里每一个jar包中,指定目录下META-INF/services/javax.servlet.ServletContainerInitializer中的实现类,并回调其中的onStartup()方法。

当然,我们可以自己提供ServletContainerInitializer的实现类,然后自己书写逻辑。但是一定且必须将实现类绑定在META-INF/services/javax.servlet.ServletContainerInitializer这个文件里,文件内容就是ServletContainerInitializer实现类的全类名;

这样Servlet容器在启动的时候就会自动调用我们提供ServletContainerInitializer的实现类,并执行其中的onStartup()方法。

好,下面来证明一下。特此说明,此处如果创建SpringBoot工程,使用内嵌的Tomcat是不会起作用的,亲测。

// 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来, // 但不包括它自己 @HandlesTypes(value = {IndexService.class}) public class MyServletContainerInitializer implements ServletContainerInitializer { /**
     * 应用启动的时候,会运行onStartup方法
     * Set @Override public void onStartup(Set<Class<?>> set, ServletContext sc) throws ServletException { System.out.println("MyServletContainerInitializer...."); System.out.println("感兴趣的类:"); // 这里的set,会把所有我们感兴趣的类型都拿到 for(Class clazz: set){ System.out.println(clazz); } } } 

项目目录结构如下: 在这里插入图片描述 启动容器,我们会看到:Tomcat启动时,会执行我们自定义的MyServletContainerInitializer,并回调其中的onStartup()方法,并把我们用@HandlesTypes(IndexService.class)关心的接口子类型(包含子接口、抽象类、实现类)都放到了onStartup()方法的第一个参数中。但是需要注意:不包含自己。 在这里插入图片描述 下面我们来关注一下,onStartup()方法中的第二个参数:ServletContext。它的作用,不用说了吧。拿到它,是不是可以干点啥了?对,我们可以用编码的方式,在项目启动的时候给ServletContext里面添加组件,即使用ServletContext注册Web组件(Servlet、Filter、Listener)。

//==========================编码形式注册三大组件============================ //注册组件ServletRegistration  ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet()); //配置servlet的映射信息 servlet.addMapping("/user"); //注册Listener sc.addListener(UserListener.class); //注册Filter  FilterRegistration FilterRegistration.Dynamic filter = sc.addFilter("userFilter", UserFilter.class); //配置Filter的映射信息 filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); 

好了,Servlet3.0这个重要的特性说完了,是不是很有用?下面我们要来分析一下,是如何整合SpringMVC框架的。

四、如何整合SpringMVC?

前面已经介绍过ServletContainerInitializer会被Servlet容器自动执行,我们是不是已经猜到Spring是怎么做的了吧?让我们查看一下spring-web的jar包: 在这里插入图片描述很显然的发现,SpringMVC也是通过这种方式和Servlet容器进行整合的。web容器在启动时,就会自动去加载org.springframework.web.SpringServletContainerInitializer这个类。

// 此处更重要的是这个WebApplicationInitializer接口的实现类 @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) { // Be defensive: Some servlet containers 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); // 循环执行它下面所以实现类的onStartup() for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } } 

看一下WebApplicationInitializer继承结构: 在这里插入图片描述 步骤分析:

Web应用启动会加载WebApplicationInitializer接口的下的所有实现类,并为WebApplicationInitializer实现类创建对象。

(1)、AbstractContextLoaderInitializer:

@Override public void onStartup(ServletContext servletContext) throws ServletException { registerContextLoaderListener(servletContext); } protected void registerContextLoaderListener(ServletContext servletContext) { // 创建根容器:createRootApplicationContext();子类实现 WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null) { ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); listener.setContextInitializers(getRootApplicationContextInitializers()); servletContext.addListener(listener); } } 

(2)、AbstractDispatcherServletInitializer:

@Override public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); registerDispatcherServlet(servletContext); } protected void registerDispatcherServlet(ServletContext servletContext) { String servletName = getServletName(); // 创建一个web的IoC容器:createServletApplicationContext(); 子类实现 WebApplicationContext servletAppContext = createServletApplicationContext(); // 创建了DispatcherServlet;createDispatcherServlet(); FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); // 将创建的DispatcherServlet添加到ServletContext中; ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); if (registration == null) { throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " + "Check if there is another servlet registered under the same name."); } registration.setLoadOnStartup(1); registration.addMapping(getServletMappings()); registration.setAsyncSupported(isAsyncSupported()); Filter[] filters = getServletFilters(); if (!ObjectUtils.isEmpty(filters)) { for (Filter filter : filters) { registerServletFilter(servletContext, filter); } } // 空方法 customizeRegistration(registration); } 

(3)、AbstractAnnotationConfigDispatcherServletInitializer:

@Override @Nullable protected WebApplicationContext createRootApplicationContext() { Class<?>[] configClasses = getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(configClasses); return context; } else { return null; } } @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); Class<?>[] configClasses = getServletConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { context.register(configClasses); } return context; } @Nullable protected abstract Class<?>[] getRootConfigClasses(); @Nullable protected abstract Class<?>[] getServletConfigClasses(); 
注解方式配置的DispatcherServlet初始化器

说明:虽然父类只有两个abstract抽象方法(createRootApplicationContext()、createServletApplicationContext())要求子类必须实现。但是父类的设计都是可以扩展的,若你想定制化自己的需求,都是可以通过重写父类的protected方法进行扩展。比如:你想定制化自己的DispatcherServlet(父类默认值是单纯的new一下),那么你就可以通过重写createDispatcherServlet()去定制。

定制的时候知道继承哪个抽象类么?当然是继承树的最底层AbstractAnnotationConfigDispatcherServletInitializer啦。

Spring容器推荐使用父子容器的概念: 可以参考Spring的官网查看:

https://docs.spring.io/spring/docs/5.2.2.RELEASE/spring-framework-reference/web.html#mvc-servlet-context-hierarchy 在这里插入图片描述

/**
 * 自己实现 基于注解驱动的ServletInitializer来初始化DispatcherServlet
 */ public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /**
     * 根容器的配置类:(Spring的配置文件)父容器;
     */ @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[]{RootConfig.class}; } /**
     * web容器的配置类(SpringMVC配置文件)子容器;
     */ @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[]{AppConfig.class}; } //获取DispatcherServlet的映射信息 // 注意:  //	/:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp; //  /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的; @Override protected String[] getServletMappings() { return new String[]{"/"}; } // 若你想定制化父类的一些默认行为,这里都是可以复写父类的protected方法的 // SpringMVC 也推荐你这么干 @Override protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) { DispatcherServlet dispatcherServlet = (DispatcherServlet) super.createDispatcherServlet(servletAppContext); return dispatcherServlet; } } 

测试类:

@Controller public class HelloController { @Autowired HelloService helloService; @ResponseBody @RequestMapping("/hello") public String hello() { System.out.println(helloService); return "hello..."; } } 

这样我们就可以正常访问controller的请求了。

注意: (1)、子容器能得到父容器的Bean,但是父容器得不到子容器的Bean; (2)、父子容器中,属性值都不是互通的,@Value注入的时候需要注意;

经过上面的分析,是不是已经知道web容器是怎么集成Spring框架的了?哈哈。。。。

五、总结

从Spring3.2开始,就推荐全部使用注解来驱动应用了。在当下流行的SpringBoot环境中,注解驱动可以说体现的淋漓尽致,完全摒弃了之前的xml配置文件,化简为繁。

额外多说一句,在SpringBoot中不同的在于:是Spring容器驱动web容器(默认情况下,提供内嵌的web容器)。而本文说的是web容器驱动Spring容器。

本文精髓:Servlet3.0特性的运用。

启动Spring容器有三种方式:可以参看下面的这篇文章:

spring容器启动的三种方式

关注
打赏
1688896170
查看更多评论

暂无认证

  • 5浏览

    0关注

    115984博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0390s