GVKun编程网logo

了解servlet容器(servlet容器的概念和作用)

1

在本文中,我们将给您介绍关于了解servlet容器的详细内容,并且为您解答servlet容器的概念和作用的相关问题,此外,我们还将为您提供关于18、配置嵌入式servlet容器(2)、19、配置嵌入式

在本文中,我们将给您介绍关于了解servlet容器的详细内容,并且为您解答servlet容器的概念和作用的相关问题,此外,我们还将为您提供关于18、配置嵌入式servlet容器(2)、19、配置嵌入式servlet容器(下)、8、Servlet容器启动解析、cxf webservice部署servlet容器报错的知识。

本文目录一览:

了解servlet容器(servlet容器的概念和作用)

了解servlet容器(servlet容器的概念和作用)

作为一个UI开发人员以及对Java知识非常了解的人,我如何简单地理解servlet容器到底是什么?

我听说过Weblogic,JBoss等是servlet容器,但是不确定这到底意味着什么。这是否意味着任何中间件技术?

请你帮助我好吗。

答案1

小编典典

Servlet容器是实现Java Servlet规范的某些版本的应用程序服务器。

简而言之,servlet规范定义了一种编程模型,该模型允许开发人员编写处理请求(几乎总是HTTP请求)的组件,例如servlet。然后可以将这些组件声明到容器中,并处理与连接和管理这些组件相关的许多繁琐任务,以便它们可以处理这些请求。

18、配置嵌入式servlet容器(2)

18、配置嵌入式servlet容器(2)

使用其他Servlet容器

 

-Jetty(长连接)
-Undertow(不支持jsp)

 

 

 替换为其他嵌入式Servlet容器

 

 
默认支持:
Tomcat(默认使用)

 

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>

<!--引入其他的Servlet -->
<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>

<!--引入其他的Servlet -->
<dependency>
     <artifactId>spring‐boot‐starter‐undertow<</artifactId>
     <groupId>org.springframework.boot</groupId>
</dependency>

 

右键排除依赖

 

嵌入式Servlet配置原理:

 

Servelt容器的自动配置类

 

@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties({ServerProperties.class})
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
    public EmbeddedWebServerFactoryCustomizerAutoConfiguration() {
    }

    @Configuration
    @ConditionalOnClass({HttpServer.class})
    public static class NettyWebServerFactoryCustomizerConfiguration {
        public NettyWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new NettyWebServerFactoryCustomizer(environment, serverProperties);
        }
    }

    @Configuration
    @ConditionalOnClass({Undertow.class, SslClientAuthMode.class})
    public static class UndertowWebServerFactoryCustomizerConfiguration {
        public UndertowWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
        }
    }

    @Configuration
    @ConditionalOnClass({Server.class, Loader.class, WebAppContext.class})
    public static class JettyWebServerFactoryCustomizerConfiguration {
        public JettyWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new JettyWebServerFactoryCustomizer(environment, serverProperties);
        }
    }

    @Configuration
    @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
    public static class TomcatWebServerFactoryCustomizerConfiguration {
        public TomcatWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
        }
    }
}

 

 

Tomcat 的Server的定制 (Jetty、Netty、Undertow 类似)

TomcatWebServerFactoryCustomizer.java

public class TomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered {
    private final Environment environment;
    private final ServerProperties serverProperties;

    public TomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
        this.environment = environment;
        this.serverProperties = serverProperties;
    }

    public int getOrder() {
        return 0;
    }

    public void customize(ConfigurableTomcatWebServerFactory factory) {
        ServerProperties properties = this.serverProperties;
        Tomcat tomcatProperties = properties.getTomcat();
        PropertyMapper propertyMapper = PropertyMapper.get();
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory);
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull().as(Duration::getSeconds).as(Long::intValue).to(factory::setBackgroundProcessorDelay);
        this.customizeRemoteIpValve(factory);
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive).to((maxThreads) -> {
            this.customizeMaxThreads(factory, tomcatProperties.getMaxThreads());
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive).to((minSpareThreads) -> {
            this.customizeMinThreads(factory, minSpareThreads);
        });
        propertyMapper.from(this::determineMaxHttpHeaderSize).whenNonNull().asInt(DataSize::toBytes).when(this::isPositive).to((maxHttpHeaderSize) -> {
            this.customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize);
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull().asInt(DataSize::toBytes).to((maxSwallowSize) -> {
            this.customizeMaxSwallowSize(factory, maxSwallowSize);
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getMaxHttpPostSize).asInt(DataSize::toBytes).when((maxHttpPostSize) -> {
            return maxHttpPostSize != 0;
        }).to((maxHttpPostSize) -> {
            this.customizeMaxHttpPostSize(factory, maxHttpPostSize);
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getAccesslog).when(Accesslog::isEnabled).to((enabled) -> {
            this.customizeAccessLog(factory);
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding);
        properties.getClass();
        propertyMapper.from(properties::getConnectionTimeout).whenNonNull().to((connectionTimeout) -> {
            this.customizeConnectionTimeout(factory, connectionTimeout);
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive).to((maxConnections) -> {
            this.customizeMaxConnections(factory, maxConnections);
        });
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive).to((acceptCount) -> {
            this.customizeAcceptCount(factory, acceptCount);
        });
        this.customizeStaticResources(factory);
        this.customizeErrorReportValve(properties.getError(), factory);
    }

.......
}

 

 

步骤:
1)、SpringBoot根据导入的依赖情况,给容器中添加相应的
        EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
2)、容器中某个组件要创建对象就会惊动后置处理器;
        EmbeddedServletContainerCustomizerBeanPostPro
        只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3)、后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调
        用定制器的定制方法

 

嵌入式Servlet容器启动原理;

什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat;
获取嵌入式的Servlet容器工厂:

 

断点

1)、SpringBoot应用启动运行run
2)、refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器
      创建容器中的每一个组件】
3)、refresh(context);刷新刚才创建好的ioc容器
private void refreshContext(ConfigurableApplicationContext context) {
    this.refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        } catch (AccessControlException var3) {
            ;
        }
    }
}
4)、 onRefresh(); web的ioc容器重写了onRefresh方法
5)、webioc容器会创建嵌入式的Servlet容器;ServletWebServerApplicationContext(
6)、获取嵌入式的Servlet容器工厂
7)、使用容器工厂获取嵌入式的Servlet容器
8)、嵌入式的Servlet容器创建对象并启动Servlet容
先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;
IOC容器启动创建嵌入式的Servlet容器

 

19、配置嵌入式servlet容器(下)

19、配置嵌入式servlet容器(下)

使用外置的Servlet

 
嵌入式Servlet容器:应用打成可执行的j ar
优点:简单、便携;
缺点:默认不支持JSP、优化定制比较复杂
        使用定制器【ServerProperties、自定义
        EmbeddedServletContainerCustomizer】,
        自己编写嵌入式Servlet容器的创建工厂
        EmbeddedServletContainerFactory】
 
外置的Servlet容器:外面安装Tomcat---应用war包的方式打包
 
创建工程的时候使用war包

 

 添加xml配置文件

 

 

 
 添加外部的服务器:

 

 

 完成hello.jsp页面的跳转

 

hello.jsp

 

test.jsp

 

 

 test.java

 

 

 测试:

 

 

 

总结:
1)、必须创建一个war项目;( 利用idea创建好目录结构
2)、将嵌入式的Tomcat指定为provided
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

 3)、必须编写一个SpringBootServletInitializer的子类,并调用configure方法

4)、启动服务器就可以使用

 

 

原理:

jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器
 
war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器
 
 
servlet3.0(Spring注解版)
章节:8.2.4 Shared libraries / runtimes pluggability
规则:
1)、服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例:
2)、ServletContainerInitializer的实现放在jar包的 META-INF/services文件夹下,有一个名为
        javax.servlet.ServletContainerInitializer的全类名
3)、还可以使用 @HandlesTypes,在应用启动的时候加载我们感兴趣

 

 

 

流程:

1)、启动Tomcat
2)、org\springframework\spring-web\4.3.14.RELEASE\spring-web-         5.1.3.RELEASE.jar!\METAINF\services\javax.servlet.ServletContainerInitializer:
  Spring的web模块里面有这个文件:

 

3)、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所
  有这个类型的类都传入到onStartup方法的Set>;为这些WebApplicationInitializer类型的类创建实例;

 

@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);
}}}}

 

 

4)、每一个WebApplicationInitializer都调用自己的onStartup;
WebApplicationInitializer的实现

 

是我们自己的Servletinitializer类
5)、相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
6)、SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
public class ServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(WebJspApplication.class);
    }
}

 

 SpringBootServletInitializer.java

public void onStartup(ServletContext servletContext) throws ServletException {
    this.logger = LogFactory.getLog(this.getClass());
    WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
    if (rootAppContext != null) {
        servletContext.addListener(new ContextLoaderListener(rootAppContext) {
            public void contextInitialized(ServletContextEvent event) {
            }
        });
    } else {
        this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
    }
}

.....

 

 

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);
}
7)、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);
    }
}

 

8、Servlet容器启动解析

8、Servlet容器启动解析

1.1、简介

SpringBoot1.x只区分web环境和非web环境,而在2.x版本中引入了Reactive环境,即响应式环境.那么现在SpringBoot支持三种环境:
Servlet的web环境、Reactive的web环境以及非web环境.90%以上的公司使用的是Servlet的web环境,而该环境默认使用的是tomcat容器,
本章内容主要是介绍Servlet容器启动流程.

1.2、全局流程解析

// 容器刷新的核心流程,之前已经分析过了
public void refresh() throws BeansException, IllegalStateException {  
    synchronized (this.startupShutdownMonitor) {  
       // Prepare this context for refreshing.  
       prepareRefresh();  
  
       // Tell the subclass to refresh the internal bean factory.  
       ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();  
  
       // Prepare the bean factory for use in this context.  
       prepareBeanFactory(beanFactory);  
  
       try {  
           // Allows post-processing of the bean factory in context subclasses.  
           postProcessBeanFactory(beanFactory);  
  
           // Invoke factory processors registered as beans in the context.  
           invokeBeanFactoryPostProcessors(beanFactory);  
  
           // Register bean processors that intercept bean creation.  
           registerBeanPostProcessors(beanFactory);  
  
           // Initialize message source for this context.  
           initMessageSource();  
  
           // Initialize event multicaster for this context.  
           initApplicationEventMulticaster();  
  
           // Initialize other special beans in specific context subclasses.  
	   // 走到这里,这里会启动web服务器
           onRefresh();  
  
           // Check for listener beans and register them.  
           registerListeners();  
  
           // Instantiate all remaining (non-lazy-init) singletons.  
           finishBeanFactoryInitialization(beanFactory);  
  
           // Last step: publish corresponding event.  
           finishRefresh();  
      } 
   }  
}
// 走到这里,这里会启动web服务器
onRefresh(); 

protected void onRefresh() {  
    super.onRefresh();  
    try {  
        createWebServer();  
    }  
}
private void createWebServer() {  
    // 刚开始这两个属性都为null
    WebServer webServer = this.webServer;  
    ServletContext servletContext = getServletContext();  
    if (webServer == null && servletContext == null) {  
	// 走到这步获取factory
        ServletWebServerFactory factory = getWebServerFactory();  
        this.webServer = factory.getWebServer(getSelfInitializer());  
    }  
    else if (servletContext != null) {  
        try {  
            getSelfInitializer().onStartup(servletContext);  
        }  
    }  
    initPropertySources();  
}

protected ServletWebServerFactory getWebServerFactory() {  
    // Use bean names so that we don''t consider the hierarchy  
    // 默认返回只有一个,tomcatServletWebServerFactory,下面第三节会分析这个bean定义什么时候被加载到容器中
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);  
    if (beanNames.length == 0) {   
	// 抛异常
    }  
    if (beanNames.length > 1) {   
	// 抛异常
    }  
    // 调用getBean创建工厂类实例
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);  
}
// this.webServer = factory.getWebServer(getSelfInitializer())
// 调用这个创建webServer
public WebServer getWebServer(ServletContextInitializer... initializers) {  
    // 创建一个tomcat
    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);  
    return getTomcatWebServer(tomcat);  
}

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {  
    // 这步就创建一个TomcatWebServer,里面还会做一些事情,以后分析
    return new TomcatWebServer(tomcat, getPort() >= 0);  
}
 initPropertySources();  
 
 protected void initPropertySources() {  
    ConfigurableEnvironment env = getEnvironment();  
    if (env instanceof ConfigurableWebEnvironment) {  
        ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, null);  
    }  
}

public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {  
    WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);  
}

public static void initServletPropertySources(MutablePropertySources sources,  
                              @Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {  
  
    // servletContextInitParams
    String name = StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME;  
    // 判断servletContext不为null且包含这个servletContextInitParams属性集
    if (servletContext != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {
	// 封装一个ServletContextPropertySource属性集替换原有的
        sources.replace(name, new ServletContextPropertySource(name, servletContext));  
    }  
    name = StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME;  
    // servletConfig默认为null
    if (servletConfig != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {  
        sources.replace(name, new ServletConfigPropertySource(name, servletConfig));  
    }  
}
// onRefresh方法结束后,我们看finishRefresh方法
// Last step: publish corresponding event.  
finishRefresh();  

protected void finishRefresh() {  
    super.finishRefresh();  
    WebServer webServer = startWebServer();  
    if (webServer != null) {  
	// 发布Server初始完毕事件
        publishEvent(new ServletWebServerInitializedEvent(webServer, this));  
    }  
}

private WebServer startWebServer() {  
    WebServer webServer = this.webServer;  
    if (webServer != null) {  
        webServer.start();  
    }  
    return webServer;  
}

1.3、web容器工厂类加载解析

// 默认返回只有一个,tomcatServletWebServerFactory,那么这个bean定义什么时候被加载到容器中
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);  

// 它是通过@Import注解导入到容器中的
public void parse(Set<beandefinitionholder> configCandidates) {  
    for (BeanDefinitionHolder holder : configCandidates) {  
        BeanDefinition bd = holder.getBeanDefinition();  
        try {  
            if (bd instanceof AnnotatedBeanDefinition) {  
               parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());  
            }  
            else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {  
               parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());  
            }  
            else {  
               parse(bd.getBeanClassName(), holder.getBeanName());  
            }  
       }  
    }  
    // 这边会处理@Import注解导入的配置类
    this.deferredImportSelectorHandler.process();  
}
public void process() {  
    List<deferredimportselectorholder> deferredImports = this.deferredImportSelectors;  
    this.deferredImportSelectors = null;  
    try {  
        if (deferredImports != null) {  
           DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();  
           deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);  
	   // 排序并依此遍历调用register方法
           deferredImports.forEach(handler::register);  
           handler.processGroupImports();  
        }  
    }  
    finally {  
        this.deferredImportSelectors = new ArrayList<>();  
    }  
}
public void register(DeferredImportSelectorHolder deferredImport) {  
     // 这个返回的Selector就是AutoConfigurationImportSelector,这也是@SpringBootApplication上通过@Import注解
     // 导入的Selector. getImportGroup返回的是AutoConfigurationGroup.class
     Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
     // computeIfAbsent就是不存在就创建,这边也是创建一个grouping
     DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(  
                                                   (group != null ? group : deferredImport),  
                                                   key -> new DeferredImportSelectorGrouping(createGroup(group)));  
     // this.deferredImports.add(deferredImport);
     // 加入到了deferredImports中,List<deferredimportselectorholder> deferredImports = new ArrayList<>()
     grouping.add(deferredImport);  
     // 放入到configurationClasses中
     this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),  
                                                                         deferredImport.getConfigurationClass());  
}
// 这边主要是构造了一个grouping,在下一步进行处理
deferredImports.forEach(handler::register);  
handler.processGroupImports(); 

public void processGroupImports() {  
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {  
	 // 下面先分析这个getImports方法
         grouping.getImports().forEach(entry -> {  
             ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());  
             try {  
                 processImports(configurationClass, asSourceClass(configurationClass),  
                 asSourceClasses(entry.getImportClassName()), false);  
             }  
         });  
    }  
}

public Iterable<group.entry> getImports() {  
    // 这边的deferredImports是通过grouping.add(deferredImport)添加进去的
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {  
        this.group.process(deferredImport.getConfigurationClass().getMetadata(),  
                                                                       deferredImport.getImportSelector());  
    }  
    return this.group.selectImports();  
}
// 两个参数如下图所示,annotationMetadata是主配置类上面的注解
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { 
     // 下面先分析下这个getAutoConfigurationEntry方法
     AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)  
                     .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);  
     // 加入到这个autoConfigurationEntries中
     this.autoConfigurationEntries.add(autoConfigurationEntry);  
     for (String importClassName : autoConfigurationEntry.getConfigurations()) {  
	 // 接着依次遍历放入到这个map中,Map<string, annotationmetadata> entries = new LinkedHashMap<>()
         this.entries.putIfAbsent(importClassName, annotationMetadata);  
     }  
}

// 这边就到了AutoConfigurationImportSelector类的方法中
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,  
                                                           AnnotationMetadata annotationMetadata) {  
    // 判断是否支持自动配置
    if (!isEnabled(annotationMetadata)) {  
        return EMPTY_ENTRY;  
    }  
    // 这个attributes属性就是上图中显示的,用来过滤自动配置类的
    AnnotationAttributes attributes = getAttributes(annotationMetadata);  
    // 下面分析这个方法,这个就是加载容器中的自动配置类
    List<string> configurations = getCandidateConfigurations(annotationMetadata, attributes);  
    // 去除重复的,方法就是放入set再放入list中
    configurations = removeDuplicates(configurations);  
    // 去除掉应该被排除的
    Set<string> exclusions = getExclusions(annotationMetadata, attributes);  
    checkExcludedClasses(configurations, exclusions);  
    configurations.removeAll(exclusions); 
	
    // 通过filter过滤,下面分析,过滤完发现只有22个了
    configurations = filter(configurations, autoConfigurationMetadata);  
    // 发布一个事件,好像没有做啥关键的
    fireAutoConfigurationImportEvents(configurations, exclusions);  
    // 将configurations封装成AutoConfigurationEntry返回
    return new AutoConfigurationEntry(configurations, exclusions);  
}

protected boolean isEnabled(AnnotationMetadata metadata) {  
    if (getClass() == AutoConfigurationImportSelector.class) {  
	// 判断有没有配置这个属性,没有的话默认为true,
	// String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
        return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);  
    }  
    return true;  
}
protected List<string> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { 
    // getSpringFactoriesLoaderFactoryClass()返回EnableAutoConfiguration.class,那么这边就是获取容器中所有的字段配置类
    // 见下图,默认总共有118个
    List<string> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),  
                                                                                getBeanClassLoader());  
    return configurations;  
}

private List<string> filter(List<string> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {  
    long startTime = System.nanoTime();  
    String[] candidates = StringUtils.toStringArray(configurations);  
    boolean[] skip = new boolean[candidates.length];  
    boolean skipped = false;  
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {  
        invokeAwareMethods(filter);  
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);  
        for (int i = 0; i < match.length; i++) {  
            if (!match[i]) {  
               skip[i] = true;  
               candidates[i] = null;  
               skipped = true;  
            }  
       }  
    }  
    if (!skipped) {  
        return configurations;  
    }  
    List<string> result = new ArrayList<>(candidates.length);  
    for (int i = 0; i < candidates.length; i++) {  
        if (!skip[i]) {  
           result.add(candidates[i]);  
        }  
    }  
    return new ArrayList<>(result);  
}

protected List<autoconfigurationimportfilter> getAutoConfigurationImportFilters() {  
    // 看一下这个过滤的逻辑,看下图它的实现类大概就知道它是怎样过滤了
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);  
}

// 下面解释下1.3节提出的问题,tomcatServletWebServerFactory,这个bean定义什么时候被加载到容器中
// filter之后的中有一个是org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
@Configuration  
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)  
@ConditionalOnClass(ServletRequest.class)  
@ConditionalOnWebApplication(type = Type.SERVLET)  
@EnableConfigurationProperties(ServerProperties.class)  
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,  
          // 会Import一个这个
          ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,  
          ServletWebServerFactoryConfiguration.EmbeddedJetty.class,  
          ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })  
public class ServletWebServerFactoryAutoConfiguration {
@Configuration  
class ServletWebServerFactoryConfiguration {  

   @Configuration  
   @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })  
   @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)  
   public static class EmbeddedTomcat {  
  
      @Bean  
      public TomcatServletWebServerFactory tomcatServletWebServerFactory() {  
	  // 这边就会创建一个TomcatServletWebServerFactory
          return new TomcatServletWebServerFactory();  
      }  
  
  }}
// 到此为止grouping.getImports方法分析完了,它是返回之前加载的22个自动配置类
public void processGroupImports() {  
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {  
	 // 下面先分析这个getImports方法
         grouping.getImports().forEach(entry -> {  
	     // 这边这个configurationClass获取的还是主配置类SpringbootApplication
             ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());  
             try {  
	         // 调用processImports方法处理,这个方法之前已经分析过了
                 processImports(configurationClass, asSourceClass(configurationClass),  
                                                    asSourceClasses(entry.getImportClassName()), false);  
             }  
         });  
    }  
}

1.4、Web容器个性化配置解析

// 入口,onRefresh方法中会创建webServer,走到这里
protected ServletWebServerFactory getWebServerFactory() {  
    // Use bean names so that we don''t consider the hierarchy  
    // 默认返回只有一个,tomcatServletWebServerFactory,下面第三节会分析这个bean定义什么时候被加载到容器中
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);  
    if (beanNames.length == 0) {   
	// 抛异常
    }  
    if (beanNames.length > 1) {   
	// 抛异常
    }  
    // 调用getBean创建工厂类实例,这边会调用getBean开始创建bean实例对象,这个步骤之前已经分析过了
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);  
}
@Override  
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)  
                                                                              throws BeansException {  
    Object result = existingBean;  
    for (BeanPostProcessor processor : getBeanPostProcessors()) {  
	// 当中有一个WebServerFactoryCustomizerBeanPostProcessor
        Object current = processor.postProcessBeforeInitialization(result, beanName);  
        if (current == null) {  
            return result;  
        }  
        result = current;  
    }  
    return result;  
}

// 走到这里,WebServerFactoryCustomizerBeanPostProcessor
@Override  
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  
    if (bean instanceof WebServerFactory) {  
       postProcessBeforeInitialization((WebServerFactory) bean);  
    }  
    return bean;  
}

private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {  
    // 先看下getCustomizers方法
    // 这边lamda表达式的意思就是遍历getCustomizers方法,通过invoke调用每个customizer.cutomize方法定制修改web容器
    LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)  
             .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)  
             .invoke((customizer) -> customizer.customize(webServerFactory));  
}

private Collection<webserverfactorycustomizer<?>> getCustomizers() {  
    if (this.customizers == null) {  
       // Look up does not include the parent context  
       // 获取容器中的所有WebServerFactoryCustomizer,待会我们分析下第二个Customizer
       this.customizers = new ArrayList&lt;&gt;(getWebServerFactoryCustomizerBeans());  
       this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);  
       this.customizers = Collections.unmodifiableList(this.customizers);  
    }  
    return this.customizers;  
}

private Collection<webserverfactorycustomizer<?>> getWebServerFactoryCustomizerBeans() {  
    return (Collection) this.beanFactory.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();  
}

// 我们先来看下上图中第二个定制器,ServletWebServerFactoryCustomizer
@Configuration  
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)  
@ConditionalOnClass(ServletRequest.class)  
@ConditionalOnWebApplication(type = Type.SERVLET)  
@EnableConfigurationProperties(ServerProperties.class)  
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,  
          ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,  
          ServletWebServerFactoryConfiguration.EmbeddedJetty.class,  
          ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })  
public class ServletWebServerFactoryAutoConfiguration {  
  
   // 它是怎么引入到容器中的呢?它是通过ServletWebServerFactoryAutoConfiguration的bean方法引入到的,
   // 并且赋值serverProperties
   @Bean  
   public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {  
       return new ServletWebServerFactoryCustomizer(serverProperties);  
   }
}
// 这就是我们在application.properties中修改port,会被赋值到这里,然后定制器就可以取到
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)  
public class ServerProperties {  
    private Integer port;  
    private InetAddress address;
    // 省略一些
}
// 然后我们再来看invoke((customizer) -> customizer.customize(webServerFactory))这个步骤,这会调用每个定制器的定制方法
// 具体我们看下ServletWebServerFactoryCustomizer的cutomize方法
public class ServletWebServerFactoryCustomizer  
             implements WebServerFactoryCustomizer<configurableservletwebserverfactory>, Ordered {  
  
   private final ServerProperties serverProperties;  
  
   public ServletWebServerFactoryCustomizer(ServerProperties serverProperties) {  
       this.serverProperties = serverProperties;  
   }  
  
   @Override  
   public int getOrder() {  
       return 0;  
   }  
  
   @Override  
   public void customize(ConfigurableServletWebServerFactory factory) {  
       PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();  
	   // 这边是lamda表达式,调用factory.setPort进行赋值
       map.from(this.serverProperties::getPort).to(factory::setPort);  
       map.from(this.serverProperties::getAddress).to(factory::setAddress);  
       map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);  
       map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);  
       map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);  
       map.from(this.serverProperties::getSsl).to(factory::setSsl);  
       map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);  
       map.from(this.serverProperties::getCompression).to(factory::setCompression);  
       map.from(this.serverProperties::getHttp2).to(factory::setHttp2);  
       map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);  
       map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);  
  }  
}

cxf webservice部署servlet容器报错

cxf webservice部署servlet容器报错

异常信息如下:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ''helloWorld'': Invocation of init method Failed; nested exception is java.lang.NoSuchMethodError: javax.wsdl.xml.WSDLReader.readWSDL(Ljavax/wsdl/xml/WSDLLocator;Lorg/w3c/dom/Element;)Ljavax/wsdl/DeFinition;
 at org.springframework.beans.factory.support.AbstractAutowireCapablebeanfactory.initializeBean(AbstractAutowireCapablebeanfactory.java:1422)
 at org.springframework.beans.factory.support.AbstractAutowireCapablebeanfactory.doCreateBean(AbstractAutowireCapablebeanfactory.java:518)
 at org.springframework.beans.factory.support.AbstractAutowireCapablebeanfactory.createBean(AbstractAutowireCapablebeanfactory.java:455)
 at org.springframework.beans.factory.support.Abstractbeanfactory$1.getobject(Abstractbeanfactory.java:293)
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
 at org.springframework.beans.factory.support.Abstractbeanfactory.doGetBean(Abstractbeanfactory.java:290)
 at org.springframework.beans.factory.support.Abstractbeanfactory.getBean(Abstractbeanfactory.java:192)
 at org.springframework.beans.factory.support.DefaultListablebeanfactory.preInstantiateSingletons(DefaultListablebeanfactory.java:585)
 at org.springframework.context.support.AbstractApplicationContext.finishbeanfactoryInitialization(AbstractApplicationContext.java:895)
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:425)
 at org.apache.cxf.transport.servlet.CXFServlet.createSpringContext(CXFServlet.java:160)
 at org.apache.cxf.transport.servlet.CXFServlet.loadBus(CXFServlet.java:74)
 at org.apache.cxf.transport.servlet.CXFNonspringServlet.init(CXFNonspringServlet.java:71)
 at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1173)
 at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:993)
 at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4420)
 at org.apache.catalina.core.StandardContext.start(StandardContext.java:4733)
 at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:799)
 at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:779)
 at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:601)
 at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:675)
 at org.apache.catalina.startup.HostConfig.deployDescriptors(HostConfig.java:601)
 at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:502)
 at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1315)
 at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:324)
 at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:142)
 at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1061)
 at org.apache.catalina.core.StandardHost.start(StandardHost.java:840)
 at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1053)
 at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:463)
 at org.apache.catalina.core.StandardService.start(StandardService.java:525)
 at org.apache.catalina.core.StandardServer.start(StandardServer.java:754)
 at org.apache.catalina.startup.Catalina.start(Catalina.java:595)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289)
 at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414)
Caused by: java.lang.NoSuchMethodError: javax.wsdl.xml.WSDLReader.readWSDL(Ljavax/wsdl/xml/WSDLLocator;Lorg/w3c/dom/Element;)Ljavax/wsdl/DeFinition;
 at org.apache.cxf.wsdl11.WSDLManagerImpl.loadDeFinition(WSDLManagerImpl.java:260)
 at org.apache.cxf.wsdl11.WSDLManagerImpl.getDeFinition(WSDLManagerImpl.java:205)
 at org.apache.cxf.service.factory.ReflectionServicefactorybean.isEmptywsdl(ReflectionServicefactorybean.java:2590)
 at org.apache.cxf.service.factory.ReflectionServicefactorybean.isFromWsdl(ReflectionServicefactorybean.java:531)
 at org.apache.cxf.service.factory.ReflectionServicefactorybean.initializeServiceModel(ReflectionServicefactorybean.java:537)
 at org.apache.cxf.service.factory.ReflectionServicefactorybean.create(ReflectionServicefactorybean.java:252)
 at org.apache.cxf.jaxws.support.JaxWsServicefactorybean.create(JaxWsServicefactorybean.java:205)
 at org.apache.cxf.frontend.AbstractWSDLBasedEndpointFactory.createEndpoint(AbstractWSDLBasedEndpointFactory.java:101)
 at org.apache.cxf.frontend.Serverfactorybean.create(Serverfactorybean.java:159)
 at org.apache.cxf.jaxws.JaxWsServerfactorybean.create(JaxWsServerfactorybean.java:211)
 at org.apache.cxf.jaxws.EndpointImpl.getServer(EndpointImpl.java:454)
 at org.apache.cxf.jaxws.EndpointImpl.doPublish(EndpointImpl.java:334)
 at org.apache.cxf.jaxws.EndpointImpl.publish(EndpointImpl.java:251)
 at org.apache.cxf.ws.discovery.internal.WSdiscoveryServiceImpl.startup(WSdiscoveryServiceImpl.java:197)
 at org.apache.cxf.ws.discovery.internal.WSdiscoveryServiceImpl.serverStarted(WSdiscoveryServiceImpl.java:122)
 at org.apache.cxf.ws.discovery.listeners.WSdiscoveryServerListener.startServer(WSdiscoveryServerListener.java:72)
 at org.apache.cxf.bus.managers.ServerLifeCycleManagerImpl.startServer(ServerLifeCycleManagerImpl.java:61)
 at org.apache.cxf.endpoint.ServerImpl.start(ServerImpl.java:146)
 at org.apache.cxf.jaxws.EndpointImpl.doPublish(EndpointImpl.java:360)
 at org.apache.cxf.jaxws.EndpointImpl.publish(EndpointImpl.java:251)
 at org.apache.cxf.jaxws.EndpointImpl.publish(EndpointImpl.java:537)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at org.springframework.beans.factory.support.AbstractAutowireCapablebeanfactory.invokeCustomInitMethod(AbstractAutowireCapablebeanfactory.java:1546)
 at org.springframework.beans.factory.support.AbstractAutowireCapablebeanfactory.invokeInitMethods(AbstractAutowireCapablebeanfactory.java:1487)
 at org.springframework.beans.factory.support.AbstractAutowireCapablebeanfactory.initializeBean(AbstractAutowireCapablebeanfactory.java:1419)
 ... 38 more

原因是你的容器中有多个wsdl4j-*.jar包。cxf 正确的包是wsdl4j-1.6.2.jar.

解决的办法就是只保留wsdl4j-1.6.2.jar 。删除掉其他的wsdl4j-*.jar.

关于了解servlet容器servlet容器的概念和作用的问题我们已经讲解完毕,感谢您的阅读,如果还想了解更多关于18、配置嵌入式servlet容器(2)、19、配置嵌入式servlet容器(下)、8、Servlet容器启动解析、cxf webservice部署servlet容器报错等相关内容,可以在本站寻找。

本文标签: