SpringBoot——Web開發四(配置嵌入式Servlet容器)

1.背景

SpringBoot默認使用Tomcat作爲嵌入式的Servlet容器。

2.如何定製和修改Servlet容器的相關配置

1.修改與server相關的配置

server.port=8081
server.context-path=/crud

server.tomcat.uri-encoding=UTF-8

//通用的Servlet容器設置
server.xxx
//Tomcat的設置
server.tomcat.xxx

2.編寫一個EmbeddedServletContainerCustomizer,2.0以後改爲WebServerFactoryCustomizer:嵌入式的Servlet容器的定製器;來修改Servlet容器的配置


@Configuration
public class MySerConfig {

   
    //配置嵌入式的Servlet的服務器
    @Bean
    //定製嵌入式的Servlet容器相關的規則
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
        return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
            @Override
            public void customize(ConfigurableWebServerFactory factory) {
                factory.setPort(8083);
            }
        };

    }

代碼方式的配置會覆蓋配置文件的配置

3.註冊Servlet的三大組件

由於SpringBoot默認是以jar包的方式啓動嵌入式的Servlet容器來啓動SpringBoot的web應用,沒有web.xml文件。

註冊三大組件用下面的方式:

3.1 Servlet

先編寫一個Servlet類:

public class MyServlet extends HttpServlet {
    //處理get請求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    //處理
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello MyServlet");
    }
}

向容器中添加ServletRegistrationBean:

@Configuration
public class MySerConfig {

    //註冊三大組件

    //註冊Servlet
    @Bean
    public ServletRegistrationBean myServlet(){
        ServletRegistrationBean registrationBean=new ServletRegistrationBean(new MyServlet(),"/myServlet");
        return registrationBean;
    }

3.2 Filter

先自定義一個Filter:

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter process");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

向容器中添加FilterRegistrationBean:還是在上面的MySerConfig中

@Bean
    //註冊Filter
    public FilterRegistrationBean myFilter(){
        FilterRegistrationBean registrationBean=new FilterRegistrationBean();
        registrationBean.setFilter(new MyFilter());
        registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
        return  registrationBean;
    }

3.3 Listener

自定義一個Listener:


public class MyListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized....web應用啓動");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed....當前Web項目銷燬");
    }
}

向容器中添加ServletListenerRegistrationBean:還是在上面的MySerConfig中

 @Bean
    //註冊Listener
    public ServletListenerRegistrationBean myListener(){
        ServletListenerRegistrationBean<MyListener> myListenerServletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener());
        return myListenerServletListenerRegistrationBean;
    }

3.4 前端控制器

SpringBoot幫我們自動註冊SpringMVC的前端控制器DispatcherServlet,查看DispatcherServletAutoConfiguration:

@Configuration(proxyBeanMethods = false)
	@Conditional(DispatcherServletRegistrationCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	@EnableConfigurationProperties(WebMvcProperties.class)
	@Import(DispatcherServletConfiguration.class)
	protected static class DispatcherServletRegistrationConfiguration {

		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
				WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
			DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
					webMvcProperties.getServlet().getPath());
           //查看這個getPath,是/
           //默認攔截: /所有請求,包靜態資源,但是不攔截JSP請求
           //可以通過server.servletPath來修改SpringMVC前端控制器默認攔截器請求路徑
			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
			registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
			multipartConfig.ifAvailable(registration::setMultipartConfig);
			return registration;
		}

	}

4.替換爲其他嵌入式Servlet容器

SpringBoot默認使用的是Tomcat。如果要換成其他的就把Tomcat的依賴排除掉,然後引入其他嵌入式Servlet容器的以來,如Jetty,Undertow。

替換爲Jetty:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <artifactId>spring-boot-starter-jetty</artifactId>
            <groupId>org.springframework.boot</groupId>
        </dependency>

替換爲Undertow:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <artifactId>spring-boot-starter-undertow</artifactId>
            <groupId>org.springframework.boot</groupId>
        </dependency>

5.嵌入式Servlet容器自動配置原理

2.0以下是:ServletWebServerFactoryAutoConfiguration,嵌入式的web服務器自動配置

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
//導入BeanPostProcessorsRegistrar:Spring註解版,給容器中導入一些組件
//在這個裏面導入WebServerFactoryCustomizerBeanPostProcessor:
//後置處理器:bean初始化前後(創建完對象,還沒賦值)執行初始化工作
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

ServletWebServerFactoryConfiguration.EmbeddedTomcat.class:

	@Configuration(proxyBeanMethods = false)
    //判斷當前是否引入Tomcat依賴
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
      /**
      *判斷當前容器沒有用戶自己定義ServletWebServerFactory:嵌入式的web服務器工廠;
      *作用:創建嵌入式的web服務器
      */
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedTomcat {

ServletWebServerFactory:嵌入式的web服務器工廠

@FunctionalInterface
public interface ServletWebServerFactory {
    //獲取嵌入式Servlet容器
    WebServer getWebServer(ServletContextInitializer... initializers);
}

工廠實現類:

WebServer:嵌入式的web服務器實現

以TomcatServletWebServerFactory爲例,下面是TomcatServletWebServerFactory類

  public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        //創建一個Tomcat
        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();

        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }

        this.prepareContext(tomcat.getHost(), initializers);
        //將配置好的Tomcat傳入進去,返回一個WebServer;並且啓動Tomcat服務器
        return this.getTomcatWebServer(tomcat);
    }

6.配置修改原理

ServletWebServerFactoryAutoConfiguration在向容器中添加web容器時還添加了一個組件:

@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class
    ...})

BeanPostProcessorsRegistrar:後置處理器註冊器(也是給容器注入一些組件)

public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

		private ConfigurableListableBeanFactory beanFactory;

		@Override
		public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
			if (beanFactory instanceof ConfigurableListableBeanFactory) {
				this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
			}
		}

		@Override
		public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
				BeanDefinitionRegistry registry) {
			if (this.beanFactory == null) {
				return;
			}
       //註冊下面兩個組件:
			registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
					WebServerFactoryCustomizerBeanPostProcessor.class);
			registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
					ErrorPageRegistrarBeanPostProcessor.class);
		}

		private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
			if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
				RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
				beanDefinition.setSynthetic(true);
				registry.registerBeanDefinition(name, beanDefinition);
			}
		}

	}

WebServerFactoryCustomizerBeanPostProcessor


public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
     
    ...
 
      //在Bean初始化之前
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    //判斷bean是不是WebServerFactory
        if (bean instanceof WebServerFactory) {
            this.postProcessBeforeInitialization((WebServerFactory)bean);
        }

        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
//獲取所有的定製器,調用每一個定製器的customize方法來給Servlet容器進行屬性賦值;
   //這個部分就是調用自定義的WebServerFactoryCustomizer的customizer方法
        ((Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {
            customizer.customize(webServerFactory);
        });
    }

總結:

  1. SpringBoot根據導入的依賴情況,給容器中添加相應的XXXServletWebServerFactory

  2. 容器中某個組件要創建對象就會驚動後置處理器 WebServerFactoryCustomizerBeanPostProcessor只要是嵌入式的是Servlet容器工廠,後置處理器就會工作;

  3. 後置處理器,從容器中獲取所有的WebServerFactoryCustomizer,調用定製器的定製方法給工廠添加配置

7.嵌入式Servlet容器啓動原理

首先設置斷點,進入Debug模式進行調控,下面是它的部分調用棧:

1.SpringBoot應用啓動運行run方法

從main方法進入,調用run方法,直到

2.上面的153行,創建IOC容器對象,根據當前環境創建

3.第156行(第一個步驟),刷新IOC容器

4.(一直調用),刷新IOC容器的272行,onRefresh();web的IOC容器重寫了onRefresh方法,查看ServletWebServerApplicationContext類的onRefresh方法,方法中調用了this.createWebServer()創建Web容器。

95行獲取嵌入式Web容器工廠:

進入這個方法,可以看到這個獲得tomcatServletWebServerFactory:

5.接下來就是在創建Web容器工廠的時候會觸發webServerFactoryCustomizerBeanPostProcessor

6.96行(第四步)使用容器工廠獲取嵌入式的Servlet容器。

7.嵌入式Servlet容器創建對象並啓動Servlet容器(從上面我們知道獲得是TomcatServletWebServerFactory,所以調用的是它的getWebServer,這個源碼上面章節有,可以看到在這裏的是創建Tomcat並啓動)。

8.嵌入式Servlet容器啓動後,再將IOC容器中剩下沒有創建出的對象全取出來(Controller,Service等)。

8.使用外置的Servlet容器

嵌入式Servlet容器:應用打成可執行的jar

優點:簡單、便攜;

缺點:默認不支持JSP、優化定製比較複雜(使用定製器【ServerProperties、自定義 WebServerFactoryCustomizer】,自己編寫嵌入式Servlet容器的創建工廠 );

外置的Servlet容器:外面安裝Tomcat---應用war包的方式打包;

8.1 使用方法

1.創建一個項目,將項目的打包方式改爲war

2.創建成功後,會發現沒有webapp目錄,這個需要創建。在idea中點擊:

選擇Moudles,創建webapp(點擊Web項目下的Web,一直執行就會出現webapp)和web.xml(可以看到圖中有web.xml創建的提醒標誌,這個web.xml就是位於創建的webapp目錄下WEB-INF):

3.編寫一個類繼承SpringBootServletInitializer,並重寫configure方法,調用參數source方法SpringBoot啓動類傳遞過去然後返回:


public class ServletInitializer extends SpringBootServletInitializer {

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

}

4.然後把tomcat的依賴範圍改爲provided

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

5.最後可以把項目打包成war包放在Tomcat中,這個可以在IDEA中配置:要記得選擇Deployment將項目的war放進入

6.在IDEA中使用Spring Initializer創建選擇打包方式爲war包,2,3步驟會自動創建。

如果啓動tomcat,報了一大堆錯誤,不妨把Tomcat改爲更高的版本試試,如果你項目中的Filter是繼承了HttpFilter,請使用tomcat9版本,9以下好像沒有HttpFilter

8.2 原理

jar包:執行SpringBoot主類的main方法,啓動ioc容器,創建嵌入式的Servlet容器;

war包:啓動服務器,服務器啓動SpringBoot應用SpringBootServletInitializer】,啓動ioc容器;

servlet3.0Spring註解版): 查看8.2.4 Shared libraries / runtimes pluggability的規則:

(1)服務器啓動(web應用啓動)會創建當前web應用裏面每一個jar包裏面ServletContainerInitializer實例:

(2)ServletContainerInitializer的實現放在jar包的META-INF/services文件夾下,有一個名爲 javax.servlet.ServletContainerInitializer的文件,內容就是ServletContainerInitializer的實現類的全類名

(3)還可以使用@HandlesTypes,在應用啓動的時候加載我們感興趣的類;

流程:

1.啓動Tomcat容器

2.在spring-web-xxx.jar包中的META-INF/services下有javax.servlet.ServletContainerInitializer這個文件,文件中的類是:

對應的類是:

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

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList();
        Iterator var4;
        if (webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }

        }
    }
}

3.SpringServletContainerInitializer將@HandlesTypes(WebAplicationInitializer.class)標註的所有這個類型的類都傳入到onStartup方法的Set<Class<?>>集合中。爲這些WebApplicationInitializer類型創建實例。

4.每一個WebApplicationInitializer都調用自己的onStartup方法。

5.WebApplicationInitializer的實現類:

6.相當於我們的SpringBootServletInitializer的類被創建,並執行onStartup方法(因爲我們自己編寫的繼承了這個)

7.SpringBootServletInitializer實例執行的時候會執行createRootApplicationContext方法,創建容器:

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
   //1.創建SpringApplicationBuilder
        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)});
        }

        builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
        builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
      //調用configure方法,SpringBootServletInitializer沒有實現這個方法,子類重寫了這個方法,將SpringBoot主程序類傳入進來了
        builder = this.configure(builder);
        builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
        //使用builder創建一個Spring應用
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
            application.addPrimarySources(Collections.singleton(this.getClass()));
        }

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

        application.setRegisterShutdownHook(false);
        //啓動Spring應用
        return this.run(application);
    }

8.Spring應用就啓動了並創建IOC容器。

 public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

啓動Servlet容器,再啓動SpringBoot應用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章