Spring Boot核心技术系统学习总结(六)web开发03

Spring Boot核心技术系统学习总结(六)web开发03
    
主要内容:
    1.嵌入式Servlet容器配置修改
    2.注册servlet三大组件
    3.切换其他嵌入式Servlet容器
    4.嵌入式Servlet容器自动配置原理
    5.嵌入式Servlet容器启动原理
    6.使用外部Servlet容器&JSP支持
    7.外部Servlet容器启动SpringBoot应用原理

    
一.嵌入式Servlet容器配置修改
    1.Spring Boot内嵌的tomcat配置依赖图:(快捷键ctrl+shift+alt+u查看依赖树)
    
    内嵌tomcat依赖图:
  
    2.如何定制修改自带的tomcat容器属性配置?
        第一种方式:配置文件中配置容器属性,具体查看SpringBoot的Server配置文件ServerProperties.java
            server.port=8081
            server.servlet.context-path=/crud
            
            修改tomcat属性配置:
            server.tomcat.uri-encoding=UTF-8
            
            
        配置tomcat源码:

void customizeTomcat(ServerProperties serverProperties,
		TomcatEmbeddedServletContainerFactory factory) {
	if (getBasedir() != null) {
		factory.setBaseDirectory(getBasedir());
	}
	factory.setBackgroundProcessorDelay(Tomcat.this.backgroundProcessorDelay);
	customizeRemoteIpValve(serverProperties, factory);
	if (this.maxThreads > 0) {
		customizeMaxThreads(factory);
	}
	if (this.minSpareThreads > 0) {
		customizeMinThreads(factory);
	}
	int maxHttpHeaderSize = (serverProperties.getMaxHttpHeaderSize() > 0
			? serverProperties.getMaxHttpHeaderSize() : this.maxHttpHeaderSize);
	if (maxHttpHeaderSize > 0) {
		customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize);
	}
	if (this.maxHttpPostSize != 0) {
		customizeMaxHttpPostSize(factory, this.maxHttpPostSize);
	}
	if (this.accesslog.enabled) {
		customizeAccessLog(factory);
	}
	if (getUriEncoding() != null) {
		factory.setUriEncoding(getUriEncoding());
	}
	if (serverProperties.getConnectionTimeout() != null) {
		customizeConnectionTimeout(factory,
				serverProperties.getConnectionTimeout());
	}
	if (this.redirectContextRoot != null) {
		customizeRedirectContextRoot(factory, this.redirectContextRoot);
	}
	if (this.maxConnections > 0) {
		customizeMaxConnections(factory);
	}
	if (this.acceptCount > 0) {
		customizeAcceptCount(factory);
	}
	if (!ObjectUtils.isEmpty(this.additionalTldSkipPatterns)) {
		factory.getTldSkipPatterns().addAll(this.additionalTldSkipPatterns);
	}
	if (serverProperties.getError()
			.getIncludeStacktrace() == ErrorProperties.IncludeStacktrace.NEVER) {
		customizeErrorReportValve(factory);
	}
}

        第二种方式:在MvcConfig类中添加组件
        
二.注册servlet三大组件
    1.Servlet三大组件(容器级别的过滤):servlet\listener\filter
    2.容器配置类

package com.springboot.web.serverconfg;

import com.springboot.web.filter.MyFilter;
import com.springboot.web.listener.MyLisener;
import com.springboot.web.servlet.MyServlet;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;

/**
 * @author Jupiter
 * @date 2019/6/22-20:28
 * @description 关于内嵌容器的相关配置
 */
@Configuration
public class MyServerConfig {

    //注册三大组件:Listener, Servlet, Filter

    @Bean
    public ServletListenerRegistrationBean myLister(){
        //监听容器级别的上下文
        ServletListenerRegistrationBean<MyLisener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyLisener());
        return servletListenerRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean myFilter(){

        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new MyFilter());
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
        return filterRegistrationBean;
    }

    @Bean
    public ServletRegistrationBean myServletRegisteryBean(){
        //设置过滤的请求,访问localhost:8089/crud/myServlet
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet");
        servletRegistrationBean.setLoadOnStartup(1);
        return servletRegistrationBean;
    }

    @Bean
    public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
        //添加内嵌容器的属性,该bean的配置会覆盖application.properties的配置
        EmbeddedServletContainerCustomizer containerCustomizer = new EmbeddedServletContainerCustomizer() {
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                container.setPort(8089);//application.properties的配置为server.port=8081,实际启动为8089
                //如果application.properties配置了ContextPath,而这里没配置,那么application里配置的也无效
                container.setContextPath("/crud");
            }
        };
        return containerCustomizer;
    }
}

    自定义servlet:

public class MyServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //这是容器级别的过滤,不会走到mvc的拦截器登录认证,就会返回该响应
        resp.getWriter().write("MyServlet!");
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

    自定义filter:

public class MyFilter implements javax.servlet.Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("到了过滤器");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}

    自定义lisener:

public class MyLisener implements javax.servlet.ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("监听到容器启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("监听到容器销毁");
    }
}

    3.Spring Boot的默认容器配置源码:Spring Boot在自动MVC配置之前,默认配置了前端控制器:DispatcherServlet

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {

    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

    //静态内部类DispatcherServletRegistrationConfiguration
    @Configuration
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {
        
        private final ServerProperties serverProperties;

        private final WebMvcProperties webMvcProperties;

        private final MultipartConfigElement multipartConfig;
        
        //注册一个dispatcherServlet
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public ServletRegistrationBean dispatcherServletRegistration(
                DispatcherServlet dispatcherServlet) {
            //dispatcherServlet默认拦截this.serverProperties.getServletMapping()所有请求
            //默认拦截: /  所有请求;包静态资源,但是不拦截jsp请求;   /*会拦截jsp
            //所以可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径
            ServletRegistrationBean registration = new ServletRegistrationBean(
                    dispatcherServlet, this.serverProperties.getServletMapping());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(
                    this.webMvcProperties.getServlet().getLoadOnStartup());
            if (this.multipartConfig != null) {
                registration.setMultipartConfig(this.multipartConfig);
            }
            return registration;
        }
    }
}

三.切换其他嵌入式Servlet容器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <!-- 去除依赖内嵌tomcat容器 -- >
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <!-- 加入新的undertow容器 -- >
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

四.嵌入式Servlet容器自动配置原理(源码分析)
    1.SpringBoot嵌入式Servlet容器自动配置类:EmbeddedServletContainerAutoConfiguration.java

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class) //导入bean后置处理器,在bean初始化后,未赋值前做工作
public class EmbeddedServletContainerAutoConfiguration {

    /**
     * 以tomcat为例,当使用tomcat作为容器时,就启用以下“嵌套”配置
     * Nested configuration if Tomcat is being used.
     */
    @Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class }) //判断当前是否引入了Tomcat依赖
    //当没有自定义的嵌入式Servlet工厂时,嵌入式容器生效
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedTomcat {
        //给容器中添加Tomcat嵌入式容器工厂
        @Bean
        public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
            return new TomcatEmbeddedServletContainerFactory();
        }

    }
    /**
     * Nested configuration if Undertow is being used.
     */
    @Configuration
    @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedUndertow {
        @Bean
        public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
            return new UndertowEmbeddedServletContainerFactory();
        }
    }
}

    2.创建的Tomcat嵌入式容器工厂实现了EmbeddedServletContainerFactory接口:

public class TomcatEmbeddedServletContainerFactory
        extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
    //其中TomcatEmbeddedServletContainerFactory重写了获取Servlet容器的方法
    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer(
            ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null ? this.baseDirectory
                : createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        //返回一个嵌入式Servlet容器,并启动Tomcat
        return getTomcatEmbeddedServletContainer(tomcat);
    }

    3.前面分析了,BeanPostProcessorsRegistrar.class //导入bean后置处理器,在bean初始化后,未赋值前做工作

public class EmbeddedServletContainerCustomizerBeanPostProcessor
        implements BeanPostProcessor, BeanFactoryAware {
    private List<EmbeddedServletContainerCustomizer> customizers;
    //获取定义的定制器,这些容器定制器有两种方式创建:ServerProperties读取配置文件
    //与在容器中加入嵌入式容器定制器@Bean public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
    //读取预设的容器创建属性
    private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
        if (this.customizers == null) {
            // Look up does not include the parent context
            this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
                    this.beanFactory
                            .getBeansOfType(EmbeddedServletContainerCustomizer.class,
                                    false, false)
                            .values());
            Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
        }
        return this.customizers;
    }
}

    综上:SpringBoot嵌入式Servlet容器自动配置原理(步骤)如下:
        1)、SpringBoot根据导入的依赖情况,给容器中添加相应的
        EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
        2)、容器中某个组件要创建对象就会通知后置处理器;
        EmbeddedServletContainerCustomizerBeanPostProcessor;
        只要是嵌入式的Servlet容器工厂,后置处理器就工作;
        3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法,让配置的容器属性生效

    
五.嵌入式Servlet容器启动原理(什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat)
    SpringIoc容器 ==> Servlet容器 ==> Mvc容器组件
    详细源码分析:(嵌入式Servlet容器启动原理)
    ☆ 打断点位置:
    1.获取容器工厂处:
    
    2.获取容器位置:
    
    步骤:
        1)、SpringBoot应用启动运行 run方法
        

        2)、refreshContext(context);SpringBoot刷新 IOC容器【创建 IOC容器对象,并初始化容器,创建容器中的每一个组件】;如果是 web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext
        

        3)、内嵌容器启动原理 3-SpringIoc容器刷新时 onrefresh方法调用了创建内嵌 Servlet容器工厂
       

        4)、获取嵌入式的Servlet容器工厂:
            EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();从ioc容器中获取EmbeddedServletContainerFactory 组件;
            TomcatEmbeddedServletContainerFactory创建,后置处理器监控到这个对象,就获取所有的定制器来先定制 Servlet容器的相关配置;
        5)、使用容器工厂获取嵌入式的 Servlet容器:this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
        6)、嵌入式的 Servlet容器创建对象并启动 Servlet容器;先启动嵌入式的 Servlet容器,再将 ioc容器中剩下没有创建出的对象获取出来;
        7)、IOC容器启动创建嵌入式的 Servlet容器

六.使用外部Servlet容器&JSP支持
    1.嵌入式Servlet容器不支持JSP,优化定制复杂
    2.先建一个SpringBoot应用(war),再生成web应用的文件结构目录:

        
    3.整合外置Tomcat到IDEA,并部署web应用的步骤如下(注:这里的SpringBoot的版本号为:1.5.10.RELEASE):
        
        
        
        
        
        
        

七.外部Servlet容器启动SpringBoot应用原理
    1.SpringBoot的内嵌Servlet容器的启动原理(jar包),执行SpringBoot的主程序类,启动ioc容器,创建嵌入式的Servlet容器
    2.外置Servlet的启动(启动服务器->服务器启动SpringBoot应用,再启动Ioc容器)原理:
        1)、servlet3.0规则:
            服务器启动会创建当前web应用中,每一个jar包的ServletContainerInitializer实例;ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名;同时,还可以支持@HandlerTypes,在应用启动的时候加入一些类组件
        2)、流程:
            |- 启动Tomcat
            |- 根据Servlet3.0规范,创建org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer实例
            |- SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法,并为这些WebApplicationInitializer类型的类创建实例;
            |- 每一个WebApplicationInitializer都调用自己的onStartup方法;
                《WebApplicationInitializer实现类SpringBootServletInitializer》
        
            |- SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext,创建容器
            |- Spring的应用就启动并且创建IOC容器,源码分析:

protected WebApplicationContext createRootApplicationContext(
      ServletContext servletContext) {
    //1、创建SpringApplicationBuilder
   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(AnnotationConfigEmbeddedWebApplicationContext.class);
   
    //调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
   builder = configure(builder);
   
    //使用builder创建一个Spring应用
   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);
   }
    //启动Spring应用
   return run(application);
}

            |- Spring的应用就启动并且创建IOC容器

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();
      analyzers = new FailureAnalyzers(context);
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
      
       //刷新IOC容器
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      listeners.finished(context, null);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      return context;
   }
   catch (Throwable ex) {
      handleRunFailure(context, listeners, analyzers, ex);
      throw new IllegalStateException(ex);
   }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值