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应用

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