zoukankan      html  css  js  c++  java
  • springboot学习总结(十)WebApplicationInitializer简介

    随着springboot框架的兴起,xml配置已经渐行渐远,基本已经被JavaConfig配置所取代。今天所说的WebApplicationInitializer的作用就是用来替代web开发中最重要的web.xml文件。

    关于WebApplicationInitializer的介绍,最好的莫过于她自身的注释了。

    /*
     * Copyright 2002-2012 the original author or authors.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package org.springframework.web;
    
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    
    /**
     * Interface to be implemented in Servlet 3.0+ environments in order to configure the
     * {@link ServletContext} programmatically -- as opposed to (or possibly in conjunction
     * with) the traditional {@code web.xml}-based approach.
     *
     * <p>Implementations of this SPI will be detected automatically by {@link
     * SpringServletContainerInitializer}, which itself is bootstrapped automatically
     * by any Servlet 3.0 container. See {@linkplain SpringServletContainerInitializer its
     * Javadoc} for details on this bootstrapping mechanism.
     *
     * <h2>Example</h2>
     * <h3>The traditional, XML-based approach</h3>
     * Most Spring users building a web application will need to register Spring's {@code
     * DispatcherServlet}. For reference, in WEB-INF/web.xml, this would typically be done as
     * follows:
     * <pre class="code">
     * {@code
     * <servlet>
     *   <servlet-name>dispatcher</servlet-name>
     *   <servlet-class>
     *     org.springframework.web.servlet.DispatcherServlet
     *   </servlet-class>
     *   <init-param>
     *     <param-name>contextConfigLocation</param-name>
     *     <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
     *   </init-param>
     *   <load-on-startup>1</load-on-startup>
     * </servlet>
     *
     * <servlet-mapping>
     *   <servlet-name>dispatcher</servlet-name>
     *   <url-pattern>/</url-pattern>
     * </servlet-mapping>}</pre>
     *
     * <h3>The code-based approach with {@code WebApplicationInitializer}</h3>
     * Here is the equivalent {@code DispatcherServlet} registration logic,
     * {@code WebApplicationInitializer}-style:
     * <pre class="code">
     * public class MyWebAppInitializer implements WebApplicationInitializer {
     *
     *    @Override
     *    public void onStartup(ServletContext container) {
     *      XmlWebApplicationContext appContext = new XmlWebApplicationContext();
     *      appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
     *
     *      ServletRegistration.Dynamic dispatcher =
     *        container.addServlet("dispatcher", new DispatcherServlet(appContext));
     *      dispatcher.setLoadOnStartup(1);
     *      dispatcher.addMapping("/");
     *    }
     *
     * }</pre>
     *
     * As an alternative to the above, you can also extend from {@link
     * org.springframework.web.servlet.support.AbstractDispatcherServletInitializer}.
     *
     * As you can see, thanks to Servlet 3.0's new {@link ServletContext#addServlet} method
     * we're actually registering an <em>instance</em> of the {@code DispatcherServlet}, and
     * this means that the {@code DispatcherServlet} can now be treated like any other object
     * -- receiving constructor injection of its application context in this case.
     *
     * <p>This style is both simpler and more concise. There is no concern for dealing with
     * init-params, etc, just normal JavaBean-style properties and constructor arguments. You
     * are free to create and work with your Spring application contexts as necessary before
     * injecting them into the {@code DispatcherServlet}.
     *
     * <p>Most major Spring Web components have been updated to support this style of
     * registration.  You'll find that {@code DispatcherServlet}, {@code FrameworkServlet},
     * {@code ContextLoaderListener} and {@code DelegatingFilterProxy} all now support
     * constructor arguments. Even if a component (e.g. non-Spring, other third party) has not
     * been specifically updated for use within {@code WebApplicationInitializers}, they still
     * may be used in any case. The Servlet 3.0 {@code ServletContext} API allows for setting
     * init-params, context-params, etc programmatically.
     *
     * <h2>A 100% code-based approach to configuration</h2>
     * In the example above, {@code WEB-INF/web.xml} was successfully replaced with code in
     * the form of a {@code WebApplicationInitializer}, but the actual
     * {@code dispatcher-config.xml} Spring configuration remained XML-based.
     * {@code WebApplicationInitializer} is a perfect fit for use with Spring's code-based
     * {@code @Configuration} classes. See @{@link
     * org.springframework.context.annotation.Configuration Configuration} Javadoc for
     * complete details, but the following example demonstrates refactoring to use Spring's
     * {@link org.springframework.web.context.support.AnnotationConfigWebApplicationContext
     * AnnotationConfigWebApplicationContext} in lieu of {@code XmlWebApplicationContext}, and
     * user-defined {@code @Configuration} classes {@code AppConfig} and
     * {@code DispatcherConfig} instead of Spring XML files. This example also goes a bit
     * beyond those above to demonstrate typical configuration of the 'root' application
     * context and registration of the {@code ContextLoaderListener}:
     * <pre class="code">
     * public class MyWebAppInitializer implements WebApplicationInitializer {
     *
     *    @Override
     *    public void onStartup(ServletContext container) {
     *      // Create the 'root' Spring application context
     *      AnnotationConfigWebApplicationContext rootContext =
     *        new AnnotationConfigWebApplicationContext();
     *      rootContext.register(AppConfig.class);
     *
     *      // Manage the lifecycle of the root application context
     *      container.addListener(new ContextLoaderListener(rootContext));
     *
     *      // Create the dispatcher servlet's Spring application context
     *      AnnotationConfigWebApplicationContext dispatcherContext =
     *        new AnnotationConfigWebApplicationContext();
     *      dispatcherContext.register(DispatcherConfig.class);
     *
     *      // Register and map the dispatcher servlet
     *      ServletRegistration.Dynamic dispatcher =
     *        container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
     *      dispatcher.setLoadOnStartup(1);
     *      dispatcher.addMapping("/");
     *    }
     *
     * }</pre>
     *
     * As an alternative to the above, you can also extend from {@link
     * org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer}.
     *
     * Remember that {@code WebApplicationInitializer} implementations are <em>detected
     * automatically</em> -- so you are free to package them within your application as you
     * see fit.
     *
     * <h2>Ordering {@code WebApplicationInitializer} execution</h2>
     * {@code WebApplicationInitializer} implementations may optionally be annotated at the
     * class level with Spring's @{@link org.springframework.core.annotation.Order Order}
     * annotation or may implement Spring's {@link org.springframework.core.Ordered Ordered}
     * interface. If so, the initializers will be ordered prior to invocation. This provides
     * a mechanism for users to ensure the order in which servlet container initialization
     * occurs. Use of this feature is expected to be rare, as typical applications will likely
     * centralize all container initialization within a single {@code WebApplicationInitializer}.
     *
     * <h2>Caveats</h2>
     *
     * <h3>web.xml versioning</h3>
     * <p>{@code WEB-INF/web.xml} and {@code WebApplicationInitializer} use are not mutually
     * exclusive; for example, web.xml can register one servlet, and a {@code
     * WebApplicationInitializer} can register another. An initializer can even
     * <em>modify</em> registrations performed in {@code web.xml} through methods such as
     * {@link ServletContext#getServletRegistration(String)}. <strong>However, if
     * {@code WEB-INF/web.xml} is present in the application, its {@code version} attribute
     * must be set to "3.0" or greater, otherwise {@code ServletContainerInitializer}
     * bootstrapping will be ignored by the servlet container.</strong>
     *
     * <h3>Mapping to '/' under Tomcat</h3>
     * <p>Apache Tomcat maps its internal {@code DefaultServlet} to "/", and on Tomcat versions
     * <= 7.0.14, this servlet mapping <em>cannot be overridden programmatically</em>.
     * 7.0.15 fixes this issue. Overriding the "/" servlet mapping has also been tested
     * successfully under GlassFish 3.1.<p>
     *
     * @author Chris Beams
     * @since 3.1
     * @see SpringServletContainerInitializer
     * @see org.springframework.web.context.AbstractContextLoaderInitializer
     * @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
     * @see org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
     */
    public interface WebApplicationInitializer {
    
    	/**
    	 * Configure the given {@link ServletContext} with any servlets, filters, listeners
    	 * context-params and attributes necessary for initializing this web application. See
    	 * examples {@linkplain WebApplicationInitializer above}.
    	 * @param servletContext the {@code ServletContext} to initialize
    	 * @throws ServletException if any call against the given {@code ServletContext}
    	 * throws a {@code ServletException}
    	 */
    	void onStartup(ServletContext servletContext) throws ServletException;
    
    }

    我这边挑出重点指出,回答下面几个问题

    (一)实现了WebApplicationInitializer的类是如何发现的?

    答:利用SPI机制的SpringServletContainerInitializer类。

    在与WebApplicationInitializer类同路径下有个SpringServletContainerInitializer类。这个类的作用就是发现所有实现了WebApplicationInitializer的类。

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

     

    (二)springboot项目debug启动时为何没有进入到SpringServletContainerInitializer的onStartup方法?

    答:WebApplicationInitializer目前只对war启动的项目有效,对jar启动的项目无效。

    关于这个答案,在SpringBootServletInitializer类的注释中有写

     * <p>
     * Note that a WebApplicationInitializer is only needed if you are building a war file and
     * deploying it. If you prefer to run an embedded container then you won't need this at
     * all.
    

      SpringBootServletInitializer的源码如下

    /*
     * Copyright 2012-2018 the original author or authors.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package org.springframework.boot.web.support;
    
    import javax.servlet.Filter;
    import javax.servlet.Servlet;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletException;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.builder.ParentContextApplicationContextInitializer;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
    import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
    import org.springframework.boot.web.servlet.ServletContextInitializer;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationListener;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.AnnotationUtils;
    import org.springframework.core.env.ConfigurableEnvironment;
    import org.springframework.util.Assert;
    import org.springframework.web.WebApplicationInitializer;
    import org.springframework.web.context.ConfigurableWebEnvironment;
    import org.springframework.web.context.ContextLoaderListener;
    import org.springframework.web.context.WebApplicationContext;
    
    /**
     * An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication}
     * from a traditional WAR deployment. Binds {@link Servlet}, {@link Filter} and
     * {@link ServletContextInitializer} beans from the application context to the servlet
     * container.
     * <p>
     * To configure the application either override the
     * {@link #configure(SpringApplicationBuilder)} method (calling
     * {@link SpringApplicationBuilder#sources(Object...)}) or make the initializer itself a
     * {@code @Configuration}. If you are using {@link SpringBootServletInitializer} in
     * combination with other {@link WebApplicationInitializer WebApplicationInitializers} you
     * might also want to add an {@code @Ordered} annotation to configure a specific startup
     * order.
     * <p>
     * Note that a WebApplicationInitializer is only needed if you are building a war file and
     * deploying it. If you prefer to run an embedded container then you won't need this at
     * all.
     *
     * @author Dave Syer
     * @author Phillip Webb
     * @author Andy Wilkinson
     * @since 1.4.0
     * @see #configure(SpringApplicationBuilder)
     */
    public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
    
    	protected Log logger; // Don't initialize early
    
    	private boolean registerErrorPageFilter = true;
    
    	/**
    	 * Set if the {@link ErrorPageFilter} should be registered. Set to {@code false} if
    	 * error page mappings should be handled via the Servlet container and not Spring
    	 * Boot.
    	 * @param registerErrorPageFilter if the {@link ErrorPageFilter} should be registered.
    	 */
    	protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) {
    		this.registerErrorPageFilter = registerErrorPageFilter;
    	}
    
    	@Override
    	public void onStartup(ServletContext servletContext) throws ServletException {
    		// Logger initialization is deferred in case a 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();
    		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(AnnotationConfigEmbeddedWebApplicationContext.class);
    		builder = configure(builder);
    		builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
    		SpringApplication application = builder.build();
    		if (application.getSources().isEmpty() && AnnotationUtils
    				.findAnnotation(getClass(), Configuration.class) != null) {
    			application.getSources().add(getClass());
    		}
    		Assert.state(!application.getSources().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.getSources().add(ErrorPageFilterConfiguration.class);
    		}
    		return run(application);
    	}
    
    	/**
    	 * Returns the {@code SpringApplicationBuilder} that is used to configure and create
    	 * the {@link SpringApplication}. The default implementation returns a new
    	 * {@code SpringApplicationBuilder} in its default state.
    	 * @return the {@code SpringApplicationBuilder}.
    	 * @since 1.3.0
    	 */
    	protected SpringApplicationBuilder createSpringApplicationBuilder() {
    		return new SpringApplicationBuilder();
    	}
    
    	/**
    	 * Called to run a fully configured {@link SpringApplication}.
    	 * @param application the application to run
    	 * @return the {@link WebApplicationContext}
    	 */
    	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;
    	}
    
    	/**
    	 * Configure the application. Normally all you would need to do is to add sources
    	 * (e.g. config classes) because other settings have sensible defaults. You might
    	 * choose (for instance) to add default command line arguments, or set an active
    	 * Spring profile.
    	 * @param builder a builder for the application context
    	 * @return the application builder
    	 * @see SpringApplicationBuilder
    	 */
    	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
    		return builder;
    	}
    
    	private static final class WebEnvironmentPropertySourceInitializer
    			implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
    
    		private final ServletContext servletContext;
    
    		private WebEnvironmentPropertySourceInitializer(ServletContext servletContext) {
    			this.servletContext = servletContext;
    		}
    
    		@Override
    		public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    			ConfigurableEnvironment environment = event.getEnvironment();
    			if (environment instanceof ConfigurableWebEnvironment) {
    				((ConfigurableWebEnvironment) environment)
    						.initPropertySources(this.servletContext, null);
    			}
    		}
    
    		@Override
    		public int getOrder() {
    			return Ordered.HIGHEST_PRECEDENCE;
    		}
    
    	}
    
    }
    

      

     

  • 相关阅读:
    Mysql基础
    Mysql基础2
    Windows CMD命令大全
    python 调试方法
    LDAP
    Linux 内核与模块调试
    Linux tee命令
    Linux kgdb命令
    OpenSSL基础知识
    Linux top命令
  • 原文地址:https://www.cnblogs.com/vincentren/p/10753217.html
Copyright © 2011-2022 走看看