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

一、引言

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

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

二、创建一个SpringBoot工程

准备一个SpringMVCMaven工程

写一个最基本的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<Class<?>> set:感兴趣的类型的所有子类型;
     * ServletContext sc:代表当前Web应用的ServletContext;
     */
	@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容器启动的三种方式

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

止步前行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值