springboot使用的是嵌入式的servlet容器

image.png
因为引入了spring-boot-starter-web 启动器 改启动器会自动引入tomcat相关的jar包,所以springboot默认的使用的是tomcat作为servlet容器。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>

问题:如何修改和定制servlet容器的相关配置?
如何切换servlet容器呢,如何支持其他容器的?

如何修改和定制servlet容器的相关配置?

springboot1和springboot2实现方式的差别还是蛮大的,但是一些基本的思路和使用方式没有改变,接下来我先采用springboot1系列的版本,以及会谈及一下中间遇到的问题和感悟。
基于springboot的版本:1.5.10.RELEASE

  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>1.5.10.RELEASE</version>
  5. <relativePath/> <!-- lookup parent from repository -->
  6. </parent>
  7. <groupId>com.springboot.servletContainer.version1</groupId>
  8. <artifactId>embedded-container</artifactId>
  9. <version>0.0.1-SNAPSHOT</version>
  10. <name>embedded-container</name>
  11. <description>Demo project for Spring Boot</description>
  12. <properties>
  13. <java.version>1.8</java.version>
  14. </properties>
  15. <dependencies>
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-web</artifactId>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-test</artifactId>
  23. <scope>test</scope>
  24. </dependency>
  25. </dependencies>

在1.5.10.RELEASE版本中修改servlet容器的一些配置可以在应用的主配置文件中application.properties直接修改

  1. server.port=8086
  2. server.context-path=/portal

通过server.xxx 这个server前缀 可以将配置的属性配置到容器上
通过启动日志也可以发现 配置生效
image.png

那么为什么通过在配置文件中 配置server.xxxx 前缀的内容可以修改容器的配置呢?

image.png

了解下注解@ConfigurationProperties

注入配置文件属性方式一

加在类上,需要和@Component注解,结合使用

  1. @ConfigurationProperties(prefix = "person")
  2. @Component
  3. public class Person {
  4. private String name;
  5. private Integer age;
  6. public String getName() {
  7. return name;
  8. }
  9. public void setName(String name) {
  10. this.name = name;
  11. }
  12. public Integer getAge() {
  13. return age;
  14. }
  15. public void setAge(Integer age) {
  16. this.age = age;
  17. }
  18. }

注入配置文件属性方式二

通过@Bean的方式进行声明

  1. @Configuration
  2. public class PersonConfig {
  3. @Bean
  4. @ConfigurationProperties(prefix = "person")
  5. public Person person(){
  6. return new Person();
  7. }
  8. }

注入配置文件属性方式三

结合这个注解@EnableConfigurationProperties(Person.class)

  1. @Configuration
  2. @EnableConfigurationProperties(Person.class)
  3. public class PersonConfig {
  4. }
  1. @ConfigurationProperties(prefix = "person")
  2. public class Person {
  3. private String name;
  4. private Integer age;
  5. private Car car;
  6. public String getName() {
  7. return name;
  8. }
  9. public void setName(String name) {
  10. this.name = name;
  11. }
  12. public Integer getAge() {
  13. return age;
  14. }
  15. public void setAge(Integer age) {
  16. this.age = age;
  17. }
  18. public Car getCar() {
  19. return car;
  20. }
  21. public void setCar(Car car) {
  22. this.car = car;
  23. }
  24. }

注意:加入下面的依赖 配置属性的时候会生成提示

you can easily generate your own configuration meta-data file from items annotated with @ConfigurationProperties by using the spring-boot-configuration-processor jar. The jar includes a Java annotation processor which is invoked as your project is compiled. To use the processor, simply include spring-boot-configuration-processor as an optional dependency, for example with Maven you would add:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-configuration-processor</artifactId>
  4. <optional>true</optional>
  5. </dependency>

也就是说 加入这个依赖会为你生成自己的元数据文件spring-configuration-metadata.json

  1. {
  2. "hints": [],
  3. "groups": [
  4. {
  5. "sourceType": "com.atguigu.entity.Person",
  6. "name": "person",
  7. "type": "com.atguigu.entity.Person"
  8. },
  9. {
  10. "sourceType": "com.atguigu.entity.Person",
  11. "name": "person.auth-info",
  12. "sourceMethod": "getAuthInfo()",
  13. "type": "com.atguigu.entity.Person$AuthInfo"
  14. },
  15. {
  16. "sourceType": "com.atguigu.entity.Person",
  17. "name": "person.car",
  18. "sourceMethod": "getCar()",
  19. "type": "com.atguigu.entity.Car"
  20. }
  21. ],
  22. "properties": [
  23. {
  24. "sourceType": "com.atguigu.entity.Person",
  25. "name": "person.age",
  26. "type": "java.lang.Integer"
  27. },
  28. {
  29. "sourceType": "com.atguigu.entity.Person$AuthInfo",
  30. "name": "person.auth-info.password",
  31. "type": "java.lang.String"
  32. },
  33. {
  34. "sourceType": "com.atguigu.entity.Person$AuthInfo",
  35. "name": "person.auth-info.username",
  36. "type": "java.lang.String"
  37. },
  38. {
  39. "sourceType": "com.atguigu.entity.Car",
  40. "name": "person.car.car-color",
  41. "type": "java.lang.String"
  42. },
  43. {
  44. "sourceType": "com.atguigu.entity.Car",
  45. "name": "person.car.car-name",
  46. "type": "java.lang.String"
  47. },
  48. {
  49. "sourceType": "com.atguigu.entity.Person",
  50. "name": "person.name",
  51. "type": "java.lang.String"
  52. }
  53. ]
  54. }

生成的路径
Configuration meta-data files are located inside jars under META-INF/spring-configuration-metadata.json
image.png

Nested properties:嵌套属性

  1. //The annotation processor will automatically consider inner classes as nested properties.
  2. For example, the following class:
  3. @ConfigurationProperties(prefix="server")
  4. public class ServerProperties {
  5. private String name;
  6. private Host host;
  7. // ... getter and setters
  8. private static class Host {
  9. private String ip;
  10. private int port;
  11. // ... getter and setters
  12. }
  13. }

Will produce meta-data information for server.name, server.host.ip and server.host.port properties. You can use the @NestedConfigurationPropertyannotation on a field to indicate that a regular (non-inner) class should be treated as if it were nested.

  1. @ConfigurationProperties(prefix = "person")
  2. public class Person {
  3. private String name;
  4. private Integer age;
  5. @NestedConfigurationProperty
  6. private Car car;
  7. private AuthInfo authInfo;
  8. public String getName() {
  9. return name;
  10. }
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. public Integer getAge() {
  15. return age;
  16. }
  17. public void setAge(Integer age) {
  18. this.age = age;
  19. }
  20. public Car getCar() {
  21. return car;
  22. }
  23. public void setCar(Car car) {
  24. this.car = car;
  25. }
  26. public void setAuthInfo(AuthInfo authInfo) {
  27. this.authInfo = authInfo;
  28. }
  29. public AuthInfo getAuthInfo() {
  30. return authInfo;
  31. }
  32. public static class AuthInfo {
  33. private String username;
  34. private String password;
  35. public String getUsername() {
  36. return username;
  37. }
  38. public void setUsername(String username) {
  39. this.username = username;
  40. }
  41. public String getPassword() {
  42. return password;
  43. }
  44. public void setPassword(String password) {
  45. this.password = password;
  46. }
  47. }
  48. }

向上面这样也会把Car这个普通常规类当成嵌套属性一样



上面插入了一段题外话,说明的是springboot配置元数据的一些相关知识,接下来步入整体,如何修改内置的servlet容器相关的配置呢?**

通过配置文件的方式修改servlet容器属性

和容器相关的配置均映射到ServerProperties类上,通过@ConfigurationProperties注解

  1. @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
  2. public class ServerProperties
  3. implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
  4. /**
  5. * Server HTTP port.
  6. */
  7. private Integer port;
  8. /**
  9. * Network address to which the server should bind to.
  10. */
  11. private InetAddress address;
  12. /**
  13. * Context path of the application.
  14. */
  15. private String contextPath;
  16. /**
  17. * Display name of the application.
  18. */
  19. private String displayName = "application";
  20. ....
  21. }

通过一个定制类来修改servlet容器的相关配置

  1. public interface EmbeddedServletContainerCustomizer {
  2. /**
  3. * Customize the specified {@link ConfigurableEmbeddedServletContainer}.
  4. * @param container the container to customize
  5. */
  6. void customize(ConfigurableEmbeddedServletContainer container);
  7. }

在容器中配置一个servlet容器定制器EmbeddedServletContainerCustomizer

  1. @Configuration
  2. public class MvcConfig {
  3. @Bean
  4. public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
  5. return new EmbeddedServletContainerCustomizer() {
  6. //定制嵌入式servelt容器相关的规则
  7. @Override
  8. public void customize(ConfigurableEmbeddedServletContainer container) {
  9. container.setPort(8090);
  10. container.setDisplayName("baidu");
  11. }
  12. };
  13. }
  14. }

总结:

通过配置文件和通过定制器修改servlet容器的表现方式是不一样的,一个是通过修改配置文件,一个是根据java代码方式,但是殊途同归,最终都采用的是定制器的方式,因为修改配置文件的server.xxx属性来修改容器的配置,会自动把server前缀的配置自动绑定到serverProperties类上,而这个类也是定制器的实现类,重写了customize方法,将配置文件的属性,又绑定到ConfigurableEmbeddedServletContainer上

  1. @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
  2. public class ServerProperties
  3. implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
  4. /**
  5. * Server HTTP port.
  6. */
  7. private Integer port;
  8. /**
  9. * Network address to which the server should bind to.
  10. */
  11. private InetAddress address;
  12. @Override
  13. public void customize(ConfigurableEmbeddedServletContainer container) {
  14. if (getPort() != null) {
  15. container.setPort(getPort());
  16. }
  17. if (getAddress() != null) {
  18. container.setAddress(getAddress());
  19. }
  20. if (getContextPath() != null) {
  21. container.setContextPath(getContextPath());
  22. }
  23. if (getDisplayName() != null) {
  24. container.setDisplayName(getDisplayName());
  25. }
  26. if (getSession().getTimeout() != null) {
  27. container.setSessionTimeout(getSession().getTimeout());
  28. }
  29. container.setPersistSession(getSession().isPersistent());
  30. container.setSessionStoreDir(getSession().getStoreDir());
  31. if (getSsl() != null) {
  32. container.setSsl(getSsl());
  33. }
  34. if (getJspServlet() != null) {
  35. container.setJspServlet(getJspServlet());
  36. }
  37. if (getCompression() != null) {
  38. container.setCompression(getCompression());
  39. }
  40. container.setServerHeader(getServerHeader());
  41. if (container instanceof TomcatEmbeddedServletContainerFactory) {
  42. getTomcat().customizeTomcat(this,
  43. (TomcatEmbeddedServletContainerFactory) container);
  44. }
  45. if (container instanceof JettyEmbeddedServletContainerFactory) {
  46. getJetty().customizeJetty(this,
  47. (JettyEmbeddedServletContainerFactory) container);
  48. }
  49. if (container instanceof UndertowEmbeddedServletContainerFactory) {
  50. getUndertow().customizeUndertow(this,
  51. (UndertowEmbeddedServletContainerFactory) container);
  52. }
  53. container.addInitializers(new SessionConfiguringInitializer(this.session));
  54. container.addInitializers(new InitParameterConfiguringServletContextInitializer(
  55. getContextParameters()));
  56. }
  57. }

注册servlet filter listener 三大组件

在没有web.xml的情况下如何注册三大组件
image.png

image.png

注册servlet

如何将如下三个组件注册到这个容器中呢?

  1. public class MyServlet extends HttpServlet {
  2. //处理get请求
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. doPost(req,resp);
  6. }
  7. //处理post请求
  8. @Override
  9. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  10. resp.getWriter().write("hello myServlet");
  11. }
  12. }
  1. public class MyFilter implements Filter {
  2. @Override
  3. public void init(FilterConfig filterConfig) throws ServletException {
  4. }
  5. @Override
  6. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  7. System.out.println("myfilter process....");
  8. filterChain.doFilter(servletRequest,servletResponse);
  9. }
  10. @Override
  11. public void destroy() {
  12. }
  13. }
  1. public class MyListener implements ServletContextListener {
  2. @Override
  3. public void contextInitialized(ServletContextEvent servletContextEvent) {
  4. System.out.println("servletContext initialize");
  5. }
  6. @Override
  7. public void contextDestroyed(ServletContextEvent servletContextEvent) {
  8. System.out.println("servletContext destroyed");
  9. }
  10. }

使用如下的方式注入三大组件

  1. @Configuration
  2. public class MvcConfig {
  3. //注册servlet
  4. @Bean
  5. public ServletRegistrationBean myservlet(){
  6. ServletRegistrationBean servletRegistrationBean=new ServletRegistrationBean(new MyServlet(),"/myServlet");
  7. return servletRegistrationBean;
  8. }
  9. //注册filter
  10. @Bean
  11. public FilterRegistrationBean myfilter(){
  12. FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean();
  13. filterRegistrationBean.setFilter(new MyFilter());
  14. filterRegistrationBean.setUrlPatterns(Arrays.asList("/myServlet"));
  15. return filterRegistrationBean;
  16. }
  17. //注册listener
  18. @Bean
  19. public ServletListenerRegistrationBean myListener(){
  20. ServletListenerRegistrationBean<MyListener> servletListenerRegistrationBean=new ServletListenerRegistrationBean<>(new MyListener());
  21. return servletListenerRegistrationBean;
  22. }
  23. }

具体为什么用这三个registrationBean就可以实现注册 ,我在下一个专栏为大家讲解。。。

其实我们会发现springboot底层大量注册的方式 使用了如上的方式:DispatcherServletAutoConfiguration

  1. @Configuration
  2. @Conditional(DispatcherServletRegistrationCondition.class)
  3. @ConditionalOnClass(ServletRegistration.class)
  4. @EnableConfigurationProperties(WebMvcProperties.class)
  5. @Import(DispatcherServletConfiguration.class)
  6. protected static class DispatcherServletRegistrationConfiguration {
  7. private final ServerProperties serverProperties;
  8. private final WebMvcProperties webMvcProperties;
  9. private final MultipartConfigElement multipartConfig;
  10. public DispatcherServletRegistrationConfiguration(
  11. ServerProperties serverProperties, WebMvcProperties webMvcProperties,
  12. ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
  13. this.serverProperties = serverProperties;
  14. this.webMvcProperties = webMvcProperties;
  15. this.multipartConfig = multipartConfigProvider.getIfAvailable();
  16. }
  17. @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
  18. @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
  19. public ServletRegistrationBean dispatcherServletRegistration(
  20. DispatcherServlet dispatcherServlet) {
  21. ServletRegistrationBean registration = new ServletRegistrationBean(
  22. dispatcherServlet, this.serverProperties.getServletMapping());
  23. registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
  24. registration.setLoadOnStartup(
  25. this.webMvcProperties.getServlet().getLoadOnStartup());
  26. if (this.multipartConfig != null) {
  27. registration.setMultipartConfig(this.multipartConfig);
  28. }
  29. return registration;
  30. }
  31. }

上面涉及到一点知识 就是注解方式注入bean,采用构造器的形式注入,这里稍微的进行一下讲解

@bean注入的参数问题

  1. @Configuration
  2. public class ProjectTaskConfig {
  3. @Bean
  4. public ProjectTask projectTask(Car car){
  5. return new ProjectTask();
  6. }
  7. }
  8. //注意:如果容器中没有Car类型的bean 那么是无法启动项目的,会报错
  9. //Parameter 0 of method projectTask in com.atguigu.config.ProjectTaskConfig required a bean of type 'com.atguigu.entity.Car' that could not be found.
  10. //Consider defining a bean of type 'com.atguigu.entity.Car' in your configuration.
  11. //也就是说会有强依赖性

###objectFactory

同理下面的方法也是:

  1. @Configuration
  2. public class ProjectTaskConfig {
  3. @Bean
  4. public ProjectTask projectTask(ObjectFactory<Materail> objectFactory){
  5. Materail materail = objectFactory.getObject();
  6. Assert.notNull(materail,"car is not exists");
  7. return new ProjectTask();
  8. }
  9. }
  1. public class BeanAnnotationApplicationTests {
  2. @Autowired
  3. Person p;
  4. @Autowired
  5. Materail materail;
  6. @Autowired
  7. private ObjectFactory<Materail> objectFactory;

以上两种方式均可以注入容器中已经存在的Materail,但是在调用它的getObject方法的时候一定要确保
1.容器中含有该类型的bean
No qualifying bean of type ‘com.atguigu.entity.Materail’ available: expected at least 1 bean which qualifies as autowire candidate
2.容器中只能还有一个该类型的bean可以匹配 ,否则报错

  1. public interface ObjectFactory<T> {
  2. /**
  3. * Return an instance (possibly shared or independent)
  4. * of the object managed by this factory.
  5. * @return an instance of the bean (should never be {@code null})
  6. * @throws BeansException in case of creation errors
  7. */
  8. T getObject() throws BeansException;
  9. }

###ObjectProvider

从spring4.3后开始引进的新的方式

  1. /**
  2. * A variant of {@link ObjectFactory} designed specifically for injection points,
  3. * allowing for programmatic optionality and lenient not-unique handling.
  4. *
  5. * @author Juergen Hoeller
  6. * @since 4.3
  7. */
  8. public interface ObjectProvider<T> extends ObjectFactory<T> {
  9. /**
  10. * Return an instance (possibly shared or independent) of the object
  11. * managed by this factory.
  12. * <p>Allows for specifying explicit construction arguments, along the
  13. * lines of {@link BeanFactory#getBean(String, Object...)}.
  14. * @param args arguments to use when creating a corresponding instance
  15. * @return an instance of the bean
  16. * @throws BeansException in case of creation errors
  17. * @see #getObject()
  18. */
  19. T getObject(Object... args) throws BeansException;
  20. /**
  21. * Return an instance (possibly shared or independent) of the object
  22. * managed by this factory.
  23. * @return an instance of the bean, or {@code null} if not available
  24. * @throws BeansException in case of creation errors
  25. * @see #getObject()
  26. */
  27. T getIfAvailable() throws BeansException;
  28. /**
  29. * Return an instance (possibly shared or independent) of the object
  30. * managed by this factory.
  31. * @return an instance of the bean, or {@code null} if not available or
  32. * not unique (i.e. multiple candidates found with none marked as primary)
  33. * @throws BeansException in case of creation errors
  34. * @see #getObject()
  35. */
  36. T getIfUnique() throws BeansException;
  37. }

因为ObjectProvider继承至ObjectFactory,所以会有getObject()方法,调用这个方法的时候,要确保容器中只有一个工厂方法在创建这个对象 什么叫工厂方法 实现factoryBean的类 和@bean标注方法的类 都可以叫做工厂类,调用getObject(),所以也就是在调用对应工厂类中的方法,

  1. public class BeanAnnotationApplicationTests {
  2. @Autowired
  3. Person p;
  4. @Autowired
  5. Materail materail;
  6. @Autowired
  7. private ObjectFactory<Materail> objectFactory;
  8. @Autowired
  9. private ObjectProvider<House> objectProvider;
  10. //用autowired注入 说明容器中只能有一个该类型的bean
  11. House hh = objectProvider.getObject();
  12. //org.springframework.beans.factory.NoUniqueBeanDefinitionException:
  13. // No qualifying bean of type 'com.atguigu.entity.House' available:
  14. // expected single matching bean but found 2: h1,h2
  15. House sy = objectProvider.getObject("沈阳");
  16. House ifAvailable = objectProvider.getIfAvailable();
  17. House ifUnique = objectProvider.getIfUnique();
  18. System.out.println(sy);
  19. System.out.println(ifAvailable);
  20. System.out.println(ifUnique);

我配置House类时

  1. @Bean
  2. @Lazy
  3. public House h1(String msg){
  4. this.msg=msg;
  5. return new House();
  6. }
  7. @Bean
  8. @Lazy
  9. public House h2(){
  10. return new House();
  11. }

所以调用
House hh = objectProvider.getObject();时报错
错误如下:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.atguigu.entity.House’ available: expected single matching bean but found 2: h1,h2

可以将容器中所有该类型的bean注入

  1. @Bean
  2. public House h1(Materail materail){
  3. this.materail=materail;
  4. return new House();
  5. }
  6. @Bean
  7. public House h2(){
  8. return new House();
  9. }
  10. @Autowired
  11. private ObjectProvider<List<House>> objectProvider;
  12. List<House> ifAvailable = objectProvider.getIfAvailable();//全部注入 上面两个
  13. System.out.println(ifAvailable);

同样如果注入的方式是这样
@Autowired
private ObjectProvider> objectProvider;
然后调用
List lists = objectProvider.getObject(); 这样需要确保容器中至少有一个bean,否则报错

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘java.util.List‘ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

使用的demo

  1. @Bean
  2. @ConditionalOnMissingBean(MeterRegistry.class)
  3. public CompositeMeterRegistry compositeMeterRegistry(
  4. MetricsProperties config,
  5. ObjectProvider<List<MeterRegistryConfigurer>> configurers,
  6. ObjectProvider<Collection<MetricsExporter>> exportersProvider) {
  7. CompositeMeterRegistry composite =
  8. config.isUseGlobalRegistry() ? Metrics.globalRegistry : new CompositeMeterRegistry();
  9. if (exportersProvider.getIfAvailable() != null) {
  10. exportersProvider.getIfAvailable().forEach(exporter -> {
  11. final MeterRegistry childRegistry = exporter.registry();
  12. if (composite == childRegistry) {
  13. throw new IllegalStateException("cannot add a CompositeMeterRegistry to itself");
  14. }
  15. composite.add(childRegistry);
  16. });
  17. }
  18. if (configurers.getIfAvailable() != null) {
  19. configurers.getIfAvailable().forEach(conf -> conf.configureRegistry(composite));
  20. }
  21. return composite;
  22. }

如何切换servlet容器呢

springboot内嵌的servlet容器 - 图7 从前面的讲解 我们知道可以通过servlet容器定制器的方式进行容器的属性设置

  1. public interface EmbeddedServletContainerCustomizer {
  2. /**
  3. * Customize the specified {@link ConfigurableEmbeddedServletContainer}.
  4. * @param container the container to customize
  5. */
  6. void customize(ConfigurableEmbeddedServletContainer container);
  7. }

查看继承树
image.png

切换容器的方式

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. <exclusions>
  5. <exclusion>
  6. <artifactId>spring-boot-starter-tomcat</artifactId>
  7. <groupId>org.springframework.boot</groupId>
  8. </exclusion>
  9. </exclusions>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-jetty</artifactId>
  14. </dependency>

排除tomcat依赖 并且加入jetty依赖

底层servlet容器自动配置的原理是如何的?为什么会自动配置的tomcat

  1. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
  2. @Configuration
  3. @ConditionalOnWebApplication
  4. @Import(BeanPostProcessorsRegistrar.class)//这个注解的作用我们细讲
  5. public class EmbeddedServletContainerAutoConfiguration {
  6. /**
  7. * Nested configuration if Tomcat is being used.
  8. */
  9. @Configuration
  10. @ConditionalOnClass({ Servlet.class, Tomcat.class })
  11. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
  12. public static class EmbeddedTomcat {
  13. @Bean
  14. public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
  15. return new TomcatEmbeddedServletContainerFactory();
  16. }
  17. }
  18. }

如果这个内部配置类生效就会在当前容器中注入TomcatEmbeddedServletContainerFactory 嵌入式的tomcat容器工厂类 那么这个类要做什么呢?

  1. public interface EmbeddedServletContainerFactory {
  2. /**
  3. * Gets a new fully configured but paused {@link EmbeddedServletContainer} instance.
  4. * Clients should not be able to connect to the returned server until
  5. * {@link EmbeddedServletContainer#start()} is called (which happens when the
  6. * {@link ApplicationContext} has been fully refreshed).
  7. * @param initializers {@link ServletContextInitializer}s that should be applied as
  8. * the container starts
  9. * @return a fully configured and started {@link EmbeddedServletContainer}
  10. * @see EmbeddedServletContainer#stop()
  11. */
  12. EmbeddedServletContainer getEmbeddedServletContainer(
  13. ServletContextInitializer... initializers);
  14. }

image.png

接下来就是进入主题的时候了

  1. @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
  2. @Configuration
  3. @ConditionalOnWebApplication
  4. @Import(BeanPostProcessorsRegistrar.class)
  5. public class EmbeddedServletContainerAutoConfiguration {
  6. /**
  7. * Nested configuration if Tomcat is being used.
  8. */
  9. @Configuration
  10. @ConditionalOnClass({ Servlet.class, Tomcat.class })
  11. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
  12. public static class EmbeddedTomcat {
  13. @Bean
  14. public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
  15. return new TomcatEmbeddedServletContainerFactory();
  16. }
  17. }
  18. /**
  19. * Nested configuration if Jetty is being used.
  20. */
  21. @Configuration
  22. @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
  23. WebAppContext.class })
  24. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
  25. public static class EmbeddedJetty {
  26. @Bean
  27. public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
  28. return new JettyEmbeddedServletContainerFactory();
  29. }
  30. }
  31. /**
  32. * Nested configuration if Undertow is being used.
  33. */
  34. @Configuration
  35. @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
  36. @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
  37. public static class EmbeddedUndertow {
  38. @Bean
  39. public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
  40. return new UndertowEmbeddedServletContainerFactory();
  41. }
  42. }

spring会根据导入的jar包的情况,自动的向容器中添加对应的容器工厂类,例如tomcat的jar包引入后,就会配置
TomcatEmbeddedServletContainerFactory 容器工厂。

TomcatEmbeddedServletContainerFactory实现了EmbeddedServletContainerFactory接口,并重写了接口的getEmbeddedServletContainer()方法获取一个容器

  1. @Override
  2. public EmbeddedServletContainer getEmbeddedServletContainer(
  3. ServletContextInitializer... initializers) {
  4. Tomcat tomcat = new Tomcat();
  5. File baseDir = (this.baseDirectory != null ? this.baseDirectory
  6. : createTempDir("tomcat"));
  7. tomcat.setBaseDir(baseDir.getAbsolutePath());
  8. Connector connector = new Connector(this.protocol);
  9. tomcat.getService().addConnector(connector);
  10. customizeConnector(connector);
  11. tomcat.setConnector(connector);
  12. tomcat.getHost().setAutoDeploy(false);
  13. configureEngine(tomcat.getEngine());
  14. for (Connector additionalConnector : this.additionalTomcatConnectors) {
  15. tomcat.getService().addConnector(additionalConnector);
  16. }
  17. prepareContext(tomcat.getHost(), initializers);
  18. //将创建的内置tomcat包装一下
  19. return getTomcatEmbeddedServletContainer(tomcat);
  20. }
  21. protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
  22. Tomcat tomcat) {
  23. // 用TomcatEmbeddedServletContainer类包装创建的tomcat对象
  24. return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
  25. }
  1. public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {
  2. private final Tomcat tomcat;
  3. private final boolean autoStart;
  4. //调用这个方法包装tomcat
  5. public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
  6. Assert.notNull(tomcat, "Tomcat Server must not be null");
  7. this.tomcat = tomcat;
  8. this.autoStart = autoStart;
  9. initialize();
  10. }
  11. //在initialize()方法中启动内置的tomcat
  12. private void initialize() throws EmbeddedServletContainerException {
  13. TomcatEmbeddedServletContainer.logger
  14. .info("Tomcat initialized with port(s): " + getPortsDescription(false));
  15. synchronized (this.monitor) {
  16. try {
  17. addInstanceIdToEngineName();
  18. try {
  19. // Remove service connectors to that protocol binding doesn't happen
  20. // yet
  21. removeServiceConnectors();
  22. // Start the server to trigger initialization listeners
  23. this.tomcat.start();
  24. // We can re-throw failure exception directly in the main thread
  25. rethrowDeferredStartupExceptions();
  26. Context context = findContext();
  27. try {
  28. ContextBindings.bindClassLoader(context, getNamingToken(context),
  29. getClass().getClassLoader());
  30. }
  31. catch (NamingException ex) {
  32. // Naming is not enabled. Continue
  33. }
  34. // Unlike Jetty, all Tomcat threads are daemon threads. We create a
  35. // blocking non-daemon to stop immediate shutdown
  36. startDaemonAwaitThread();
  37. }
  38. catch (Exception ex) {
  39. containerCounter.decrementAndGet();
  40. throw ex;
  41. }
  42. }
  43. catch (Exception ex) {
  44. throw new EmbeddedServletContainerException(
  45. "Unable to start embedded Tomcat", ex);
  46. }
  47. }
  48. }
  49. }

现在剩下的问题就是
1.EmbeddedServletContainerFactory这个工厂接口会在什么时候调用它的获取容器方法来创建一个servlet容器呢?
2.@Import(BeanPostProcessorsRegistrar.class)//这个注解的作用我们细讲 这个注解的作用?

  1. public static class BeanPostProcessorsRegistrar
  2. implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
  3. private ConfigurableListableBeanFactory beanFactory;
  4. @Override
  5. public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
  6. if (beanFactory instanceof ConfigurableListableBeanFactory) {
  7. this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
  8. }
  9. }
  10. @Override
  11. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
  12. BeanDefinitionRegistry registry) {
  13. if (this.beanFactory == null) {
  14. return;
  15. }
  16. registerSyntheticBeanIfMissing(registry,
  17. "embeddedServletContainerCustomizerBeanPostProcessor",
  18. EmbeddedServletContainerCustomizerBeanPostProcessor.class);
  19. registerSyntheticBeanIfMissing(registry,
  20. "errorPageRegistrarBeanPostProcessor",
  21. ErrorPageRegistrarBeanPostProcessor.class);
  22. }
  23. private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
  24. String name, Class<?> beanClass) {
  25. if (ObjectUtils.isEmpty(
  26. this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
  27. RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
  28. beanDefinition.setSynthetic(true);
  29. registry.registerBeanDefinition(name, beanDefinition);
  30. }
  31. }
  32. }

BeanPostProcessorsRegistrar负责向容器中注入EmbeddedServletContainerCustomizerBeanPostProcessor后置处理器,在bean初始化前后做一些拦截工作
看一下这个bean后置处理器做了写什么

  1. public class EmbeddedServletContainerCustomizerBeanPostProcessor
  2. implements BeanPostProcessor, BeanFactoryAware {
  3. private ListableBeanFactory beanFactory;
  4. private List<EmbeddedServletContainerCustomizer> customizers;
  5. @Override
  6. public void setBeanFactory(BeanFactory beanFactory) {
  7. Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
  8. "EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "
  9. + "with a ListableBeanFactory");
  10. this.beanFactory = (ListableBeanFactory) beanFactory;
  11. }
  12. @Override
  13. public Object postProcessBeforeInitialization(Object bean, String beanName)
  14. throws BeansException {
  15. //只对ConfigurableEmbeddedServletContainer类型进行拦截
  16. if (bean instanceof ConfigurableEmbeddedServletContainer) {
  17. postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
  18. }
  19. return bean;
  20. }
  21. @Override
  22. public Object postProcessAfterInitialization(Object bean, String beanName)
  23. throws BeansException {
  24. return bean;
  25. }
  26. private void postProcessBeforeInitialization(
  27. ConfigurableEmbeddedServletContainer bean) {
  28. for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
  29. //获取所用的定制器 进行对该servletContainer的定制
  30. customizer.customize(bean);
  31. }
  32. }
  33. private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
  34. if (this.customizers == null) {
  35. // Look up does not include the parent context
  36. this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
  37. this.beanFactory
  38. .getBeansOfType(EmbeddedServletContainerCustomizer.class,
  39. false, false)
  40. .values());
  41. Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
  42. this.customizers = Collections.unmodifiableList(this.customizers);
  43. }
  44. return this.customizers;
  45. }
  46. }

补充:这里有一些好的编程方式 借鉴spring的 ,有机会会细细的学一下
Assert:断言的工具类 spring提供的
Collections:java提供的工具类

总结:

1,首先springboot根据我们的导包情况 会在容器中增加TomcatEmbeddedServletContainerFactory 容器工厂类
2.接下来后置处理器只会拦截ConfigurableEmbeddedServletContainer类型的bean的创建
恰好我们的TomcatEmbeddedServletContainerFactory就是这个类型的
image.png

容器启动的时候会调用refresh方法刷新容器:

  1. protected void onRefresh() throws BeansException {
  2. // For subclasses: do nothing by default.
  3. }

这个刷新 恰好是留个子类实现的,创建context的时候创建的是

  1. /**
  2. * The class name of application context that will be used by default for non-web
  3. * environments.
  4. */
  5. public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
  6. + "annotation.AnnotationConfigApplicationContext";
  7. /**
  8. * The class name of application context that will be used by default for web
  9. * environments.
  10. */
  11. public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
  12. + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
  13. protected ConfigurableApplicationContext createApplicationContext() {
  14. Class<?> contextClass = this.applicationContextClass;
  15. if (contextClass == null) {
  16. try {
  17. contextClass = Class.forName(this.webEnvironment
  18. ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
  19. }
  20. catch (ClassNotFoundException ex) {
  21. throw new IllegalStateException(
  22. "Unable create a default ApplicationContext, "
  23. + "please specify an ApplicationContextClass",
  24. ex);
  25. }
  26. }
  27. return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
  28. }

因为是web环境 所以创建的AnnotationConfigEmbeddedWebApplicationContext,它的refresh方法

  1. @Override
  2. protected void onRefresh() {
  3. super.onRefresh();
  4. try {
  5. //创建嵌入式的容器
  6. createEmbeddedServletContainer();
  7. }
  8. catch (Throwable ex) {
  9. throw new ApplicationContextException("Unable to start embedded container",
  10. ex);
  11. }
  12. }
  13. private void createEmbeddedServletContainer() {
  14. EmbeddedServletContainer localContainer = this.embeddedServletContainer;
  15. ServletContext localServletContext = getServletContext();
  16. if (localContainer == null && localServletContext == null) {
  17. //获取容器工厂
  18. EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
  19. //调用容器工厂的getEmbeddedServletContainer方法 把配置的参数设置给tomcat 并启动tomcat
  20. this.embeddedServletContainer = containerFactory
  21. .getEmbeddedServletContainer(getSelfInitializer());
  22. }
  23. else if (localServletContext != null) {
  24. try {
  25. getSelfInitializer().onStartup(localServletContext);
  26. }
  27. catch (ServletException ex) {
  28. throw new ApplicationContextException("Cannot initialize servlet context",
  29. ex);
  30. }
  31. }
  32. initPropertySources();
  33. }
  34. //只能有一个容器工厂
  35. protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
  36. // Use bean names so that we don't consider the hierarchy
  37. String[] beanNames = getBeanFactory()
  38. .getBeanNamesForType(EmbeddedServletContainerFactory.class);
  39. if (beanNames.length == 0) {
  40. throw new ApplicationContextException(
  41. "Unable to start EmbeddedWebApplicationContext due to missing "
  42. + "EmbeddedServletContainerFactory bean.");
  43. }
  44. if (beanNames.length > 1) {
  45. throw new ApplicationContextException(
  46. "Unable to start EmbeddedWebApplicationContext due to multiple "
  47. + "EmbeddedServletContainerFactory beans : "
  48. + StringUtils.arrayToCommaDelimitedString(beanNames));
  49. }
  50. return getBeanFactory().getBean(beanNames[0],
  51. EmbeddedServletContainerFactory.class);
  52. }

以上是自动配置的servlet容器的过程 是springboot1.5.10的版本 ,有时间会探究下2.0系列的版本

本文代码github地址

https://github.com/sunyjgithub/bean-annotation.git