您现在的位置是:首页 >学无止境 >SpringMVC启动流程方式分析-三种方式网站首页学无止境

SpringMVC启动流程方式分析-三种方式

勿念勿扰 2023-05-23 06:48:29
简介SpringMVC启动流程方式分析-三种方式

SpringMVC的启动方式

本文所叙述的是springmvc放入Tomcat servlet容器的启动方式

第一种Web.xml文件配置

使用传统的web.xml配置文件, 指定DispatchServlet ,当然如果想要父子容器的效果指定一个ContextLoaderListener 上下文加载监听器就行, 他们都要分别指定各自的配置文件。

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

Servlet 3.0之后就已经支持listner、servlet 这些类的配置注解化,不使用web.xml。因此我们得找到一个Tomcat 也就是web容器启动的生命周期函数扩展接口。ServletContainerInitializer 即SPI服务器发现机制

如果不知道SPI服务发现机制的请自行百度,既然学到了这些经典框架,spi是必先掌握的知识

第二种实现WebApplicationInitializer接口

第一记住整个接口时SpringMvc自己的接口, 不属于Servlet规范的接口。所以SpringMvc怎么指定了自己的接口给开发者使用,如何和Servlet规范接口绑定联系我们得找出来。
一个小提示: 当我们不熟悉整个接口可以按照SpringMvc官方文档然后实现接口。可以使用Debug查看调用栈,梳理调用关系

public class MyWebApplicationInitializer implements WebApplicationInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {


        AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
        webApplicationContext.setConfigLocation("/spring/springmvc.xml");
        
        webApplicationContext.refresh();

        DispatcherServlet dispatcherServlet = new DispatcherServlet(webApplicationContext);


        ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
        
        dynamic.setLoadOnStartup(1);
        dynamic.addMapping("/**");


    }
}

在这里插入图片描述

所以去看看这个时什么玩意:

@HandlesTypes(WebApplicationInitializer.class) //这个注解告诉Servlet容器, onStartup这个方法的set集合传入实现了WebApplicationInitializer这个接口的Class
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = Collections.emptyList();

		if (webAppInitializerClasses != null) {
			initializers = new ArrayList<>(webAppInitializerClasses.size());
			for (Class<?> waiClass : webAppInitializerClasses) {
				// 应为我们要反射调用所以这里是waiClass必须是一个类,不能是接口抽象的,以及必须是WebApplicationInitializer的子类
				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) {
		//调用自己的WebApplicationInitializer 接口,这个就是留给开发者使用的。
			initializer.onStartup(servletContext);
		}
	}

}

看到这里我们已经看到了真像,等待不是说SPI, 熟悉Java官方的SPI的都知道, 所以Spring需要遵循ServletContainerInitializer的使用规范。
SPI
到这里Spring把自己的扩展接口WebApplicationInitializer 成功与Servlet容器启动规范接口绑定起来了。回调的WebApplicationInitializer 的onStartup接口里面我们可以拿到ServletContext 应用上下文。这样我们就可以注册Servlet、Filter、Listener代替掉第一种方式的Web.xml。

WebApplicationInitializer 这个接口使用过于抽象了, 这个接口留给开发了比较多的和业务无关的工作量, 比如需要考虑使用什么ApplicationContext注册到Servlet容器,各种Servlet、Listner、Filter 都需要编写,如果要使用父子容器还需要我们自己编写逻辑。是否可以进一步具体一下,即第三种方式。

第三种 AbstractAnnotationConfigDispatcherServletInitializer

这个抽象类,实现很多大部分的通用代码逻辑,比如父子容器的赋值逻辑、注册,我们指需要实现他留下的方法指定一下一些关键配置位置、映射路径等。

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    /*父容器配置类*/
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    /*子容器的配置类*/
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[0];
    }

    /*dipathceryServlet映射路径*/
    @Override
    protected String[] getServletMappings() {
        return new String[0];
    }
    
    /*filter*/
    protected Filter[] getServletFilters() {
        return null;
    }
}

从这里我们就很简单的可以编写配置性的代码,无需关注如何注册DispathServlet、已经父容器、子容器等一些和业务无关的配置代码细节,具体封装细节可以自己看看,了解一下别人的设计思想,比较既然想学框架一些架构性的思想,总不能停留到会使用的层面

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。