SpringBoot 中的 ServletInitializer

(一)代码部分

ServletInitializer 中代码

在   SpringBootServletInitializer  中

在  WebApplicationInitializer  中 

(二)分析

一、对  WebApplicationInitializer  的理解

现在JavaConfig配置方式在逐步取代xml配置方式。而WebApplicationInitializer可以看做是Web.xml的替代,它是一个接口。通过实现WebApplicationInitializer,在其中可以添加servlet,listener等,在加载Web项目的时候会加载这个接口实现类,从而起到web.xml相同的作用。下面就看一下这个接口的详细内容。

     首先打开这个接口,如下:

public interface WebApplicationInitializer {

void onStartup(ServletContext var1) throws ServletException;

}

     只有一个方法,看不出什么头绪。但是,在这个包下有另外一个类,SpringServletContainerInitializer。它的实现如下:

@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);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

    这个类就比较有意思了,先不管其他的,读一下这段代码,可以得到这样的意思。

             先判断webAppInitializerClasses这个Set是否为空。如果不为空的话,找到这个set中不是接口,不是抽象类,并且是

WebApplicationInitializer接口实现类的类,将它们保存到list中。当这个list为空的时候,抛出异常。不为空的话就按照一定的顺序排序,并将它们按照一定的顺序实例化。调用其onStartup方法执行。到这里,就可以解释WebApplicationInitializer实现类的工作过程了。但是,在web项目运行的时候,SpringServletContainerInitializer这个类又是怎样被调用的呢。

           它只有一个接口,ServletContainerInitializer,通过它就可以解释SpringServletContainerInitializer是如何被调用的。它的内容如下,

package javax.servlet;

import java.util.Set;

public interface ServletContainerInitializer {

void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;

}

    首先,这个接口是javax.servlet下的。官方的解释是这样的:为了支持可以不使用web.xml。提供了ServletContainerInitializer,它可以通过SPI机制,当启动web容器的时候,会自动到添加的相应jar包下找到META-INF/services下以ServletContainerInitializer的全路径名称命名的文件,它的内容为ServletContainerInitializer实现类的全路径,将它们实例化。既然这样的话,那么SpringServletContainerInitializer作为ServletContainerInitializer的实现类,它的jar包下也应该有相应的文件。打开查看如下:

                                   

                   

         哈,现在就可以解释清楚了。首先,SpringServletContainerInitializer作为ServletContainerInitializer的实现类,通过SPI机制,在web容器加载的时候会自动的被调用。(这个类上还有一个注解@HandlesTypes,它的作用是将感兴趣的一些类注入到ServletContainerInitializerde), 而这个类的方法又会扫描找到WebApplicationInitializer的实现类,调用它的onStartup方法,从而起到启动web.xml相同的作用。

         然后,我们自己通过一个实例来实现相同的功能,通过一样的方式来访问一个servlet。

         1、定义接口WebParameter,它就相当于WebApplicationInitializer。内容如下:

public interface WebParameter {
 
    void loadOnstarp(ServletContext servletContext);
}

          可以在这里面添加servlet,listener等。

       2、定义Servlet。

public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("TestSetvlet");
    }
}

       3、定义MyWebParameter作为WebParameter的实现类,将Servlet添加到上下文,并设置好映射。

public class MyWebParameter implements WebParameter {
    public void loadOnstarp(ServletContext servletContext) {
        ServletRegistration.Dynamic testSetvelt=servletContext.addServlet("test","com.test.servlet.MyServlet");
        testSetvelt.setLoadOnStartup(1);
        testSetvelt.addMapping("/test");
    }
}

       4、定义好WebConfig作为ServletContainerInitializer的实现类,它的作用是扫描找到WebParameter的实现类,并调用其方法。 

@HandlesTypes({WebParameter.class})
public class WebConfig implements ServletContainerInitializer {
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        Iterator var4;
        if (set!=null){
            var4=set.iterator();
            while(var4.hasNext()){
                Class<?> clazz= (Class<?>) var4.next();
                if (!clazz.isInterface()&& !Modifier.isAbstract(clazz.getModifiers())&&WebParameter.class.isAssignableFrom(clazz)){
                    try {
                        ((WebParameter) clazz.newInstance()).loadOnstarp(servletContext);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

         5、根据SPI机制,定义一个META-INF/services文件夹,并在其下定义相关文件名称,并将WebConfig的类全名称填入其中。

              

              至此,相关内容就完成了,因为我用的maven,通过install将其作为jar包上传到本地仓库。从另外一个web项目调用这个包进行访问。

         6、最终结果:

                     

                 相关代码请转至我的github点击打开链接

转自:Spring中WebApplicationInitializer的理解

①、这里面会涉及到一些注解,例如: @HandlesTypes ,可以参考文章:Servlet3.0特性详解-笔记  ,里面有详细的解释!!

②、SPI机制:参考  Java的SPI机制浅析与简单示例   (或是你们可以搜索其他的文章学习SPI机制)

       这里先说下SPI的一个概念,SPI英文为Service Provider Interface单从字面可以理解为Service提供者接口,正如从SPI的名字去理解SPI就是Service提供者接口;我对SPI的定义:提供给服务提供厂商与扩展框架功能的开发者使用的接口。

      通过Java SPI机制我们就可以在不修改Jar包或框架的时候为Api提供新实现。

二、对   SpringBootServletInitializer  的理解

使用嵌入式Servlet容器:

     优点:   简单,便携

     缺点:   默认不支持jsp,优化定制比较复杂

使用外置Servlet容器的步骤:

  1  必须创建war项目,需要建好web项目的目录结构

  2  嵌入式Tomcat依赖scope指定provided

  3  编写SpringBootServletInitializer类子类,并重写configure方法

 

public class ServletInitializer extends SpringBootServletInitializer {  
  
    @Override  
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {  
        return application.sources(SpringBoot04WebJspApplication.class);  
    }  
} 

       4  启动服务器

jar包和war包启动区别

    jar包:执行SpringBootApplication的run方法,启动IOC容器,然后创建嵌入式Servlet容器

 war包:  先是启动Servlet服务器,服务器启动Springboot应用(springBootServletInitizer),然后启动IOC容器

Servlet 3.0+规则

  1  服务器启动(web应用启动),会创建当前web应用里面所有jar包里面的ServletContainerlnitializer实例

  2 ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下

  3  还可以使用@HandlesTypes注解,在应用启动的时候加载指定的类。

外部Tomcat流程以及原理

  ①  启动Tomcat

    ②  根据上述描述的Servlet3.0+规则,可以在Spring的web模块里面找到有个文件名为javax.servlet.ServletContainerInitializer的文件,而文件的内容为org.springframework.web.SpringServletContainerInitializer,用于加载SpringServletContainerInitializer类

  ③看看SpringServletContainerInitializer定义

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

        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 {
                //为所有的WebApplicationInitializer类型创建实例,并加入集合中
                        initializers.add((WebApplicationInitializer) 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);
      //调用每一个WebApplicationInitializer实例的onstartup方法
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

}

 在上面一段长长的注释中可以看到,SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有WebApplicationInitializer这个类型的类都传入到onStartup方法的Set参数中,并通过反射为这些WebApplicationInitializer类型的类创建实例;

  ④  方法最后,每一个WebApplicationInitilizer实现调用自己onstartup方法

  ⑤  而WebApplicationInitializer有个抽象实现类SpringBootServletInitializer(记住我们继承了该抽象类),则会调用每一个WebApplicationInitializer实例(包括SpringBootServletInitializer)的onStartup方法:

public abstract class SpringBootServletInitializer implements WebApplicationInitializer {

	protected Log logger; // Don't initialize early

	private boolean registerErrorPageFilter = true;


	protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) {
		this.registerErrorPageFilter = registerErrorPageFilter;
	}

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		// Logger initialization is deferred in case an ordered
		// LogServletContextInitializer is being used
		this.logger = LogFactory.getLog(getClass());
		WebApplicationContext rootAppContext = createRootApplicationContext(
				servletContext);
		if (rootAppContext != null) {
			servletContext.addListener(new ContextLoaderListener(rootAppContext) {
				@Override
				public void contextInitialized(ServletContextEvent event) {
					// no-op because the application context is already initialized
				}
			});
		}
		else {
			this.logger.debug("No ContextLoaderListener registered, as "
					+ "createRootApplicationContext() did not "
					+ "return an application context");
		}
	}

	protected WebApplicationContext createRootApplicationContext(
			ServletContext servletContext) {
		SpringApplicationBuilder builder = createSpringApplicationBuilder();
		StandardServletEnvironment environment = new StandardServletEnvironment();
		environment.initPropertySources(servletContext, null);
		builder.environment(environment);
		builder.main(getClass());
		ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
		if (parent != null) {
			this.logger.info("Root context already created (using as parent).");
			servletContext.setAttribute(
					WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
			builder.initializers(new ParentContextApplicationContextInitializer(parent));
		}
		builder.initializers(
				new ServletContextApplicationContextInitializer(servletContext));
		builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
		builder = configure(builder);
		SpringApplication application = builder.build();
		if (application.getAllSources().isEmpty() && AnnotationUtils
				.findAnnotation(getClass(), Configuration.class) != null) {
			application.addPrimarySources(Collections.singleton(getClass()));
		}
		Assert.state(!application.getAllSources().isEmpty(),
				"No SpringApplication sources have been defined. Either override the "
						+ "configure method or add an @Configuration annotation");
		// Ensure error pages are registered
		if (this.registerErrorPageFilter) {
			application.addPrimarySources(
					Collections.singleton(ErrorPageFilterConfiguration.class));
		}
		return run(application);
	}


	protected SpringApplicationBuilder createSpringApplicationBuilder() {
		return new SpringApplicationBuilder();
	}


	protected WebApplicationContext run(SpringApplication application) {
		return (WebApplicationContext) application.run();
	}

	private ApplicationContext getExistingRootWebApplicationContext(
			ServletContext servletContext) {
		Object context = servletContext.getAttribute(
				WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
		if (context instanceof ApplicationContext) {
			return (ApplicationContext) context;
		}
		return null;
	}


	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		return builder;
	}

}

      SpringBootServletInitializer实例执行onStartup方法的时候会通过createRootApplicationContext方法来执行run方法,接下来的过程就同以jar包形式启动的应用的run过程一样了,在内部会创建IOC容器并返回,只是以war包形式的应用在创建IOC容器过程中,不再创建Servlet容器了。

参考:spring boot中servlet启动原理 

注:SpringBoot异常处理之ErrorPageFilter   

ErrorPageFilter是SpringBoot在1.4.0版本提供的一个类,本质上是一个Filter。 它的作用主要有两方面:

  1. 提供应用程序注册ErrorPage的接口,此时它的角色是:ErrorPageRegistry
  2. 处理应用程序异常,根据异常的类型转发到对应的ErrorPage页, 从而不依赖部署的容器错误处理机制

三、了解深入了解SpringBootServletInitializer

熟悉了SpringApplication的原理之后,我们再来了解SpringBootServletInitializer的原理就比较容易了。

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(DemoWarApplication.class);
    }
}

SpringBootServletInitializer就是一个org.springframework.web.context.WebApplicationContext,容器启动时会调用其onStartup(ServletContext servletContext)方法,接下来我们就来看一下这个方法:

public void onStartup(ServletContext servletContext) throws ServletException {
        this.logger = LogFactory.getLog(this.getClass());
        final WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
        if(rootAppContext != null) {
            servletContext.addListener(new ContextLoaderListener(rootAppContext) {
                public void contextInitialized(ServletContextEvent event) {
                }
            });
        } else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }

    }

这里的核心方法就是createRootApplicationContext(servletContext):

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {

        //创建SpringApplicationBuilder,并用其生产出SpringApplication对象
        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
        builder.main(this.getClass());

        ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
        if(parent != null) {
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
            builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
        }

        //初始化并封装SpringApplicationBuilder对象,为SpringApplication对象增加ApplicationContextInitializer和ApplicationListener做准备
        builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
        builder.listeners(new ApplicationListener[]{new ServletContextApplicationListener(servletContext)});
        //指定创建的ApplicationContext类型
        builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);

        //传递入口类,并构建SpringApplication对象
        //可以通过configure()方法对SpringBootServletInitializer进行扩展
        builder = this.configure(builder);
        SpringApplication application = builder.build();

        if(application.getSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
            application.getSources().add(this.getClass());
        }

        Assert.state(!application.getSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        if(this.registerErrorPageFilter) {
            application.getSources().add(ErrorPageFilter.class);
        }

        //最后调用SpringApplication的run方法
        return this.run(application);
    }

说明
SpringBootServletInitializer的执行过程,简单来说就是通过SpringApplicationBuilder构建并封装SpringApplication对象,并最终调用SpringApplication的run方法的过程。


扩展SpringBootServletInitializer

与扩展SpringApplication类似,ApplicationContextInitializerApplicationListener可以基于SpringApplicationBuilder提供的public方法进行扩展

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        application.initializers(MyApplicationContextInitializer1,MyApplicationContextInitializer2);
        application.listeners(MyApplicationListener1,MyApplicationListener2)
        return application.sources(DemoWarApplication.class);
    }

}

参考: 飘逸峰    Spring Boot学习笔记03--深入了解SpringBoot的启动过程   

展开阅读全文

没有更多推荐了,返回首页