SpringBoot(七) -- 嵌入式Servlet容器
一.嵌入式Servlet容器
在传统的开发中,我们在完成开发后需要将项目打成war包,在外部配置好TomCat容器,而这个TomCat就是Servlet容器.在使用SpringBoot开发时,我们无需再外部配置Servlet容器,使用的是嵌入式的Servlet容器(TomCat).如果我们使用嵌入式的Servlet容器,存在以下问题:
1.如果我们是在外部安装了TomCat,如果我们想要进行自定义的配置优化,可以在其conf文件夹下修改配置文件来实现.在使用内置Servlet容器时,我们可以使用如下方法来修改Servlet容器的相关配置:
(1)例如我们可以使用server.port=80来修改我们的启用端口号为80;及我们可以通过修改和Server有关的配置来实现(ServerProperties)
(2)修改通用的设置server.XXX;
(3)修改和Tomcat相关的设置:server.tomcat.xxx
2.我们可以编写一个EmbeddedServletContainerCustomizer(嵌入式Servlet容器的定制器):
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer() {
//定制嵌入式的Servlet容器相关的规则
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8083); //设置端口为8083
}
};
}
二.注册Servlet Filter Listener
我们可以分别使用ServletRegisterationBean FilterRegisterationBean ServletListenerRegisterationBean完成这三大组件的注册
--Servlet
package com.zhiyun.springboot.web_restfulcrud.servlet; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* @author : S K Y
* @version :0.0.1
*/
public class MyServlet extends HttpServlet {
//处理get()请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
} @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello MyServlet");
}
}
package com.zhiyun.springboot.web_restfulcrud.config; import com.zhiyun.springboot.web_restfulcrud.servlet.MyServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* @author : S K Y
* @version :0.0.1
*/
@Configuration
public class MyServerConfig {
//注册三大组件
@Bean
public ServletRegistrationBean servletRegistrationBean() {
return new ServletRegistrationBean(new MyServlet(), "/myServlet");
}
}
--Filter
package com.zhiyun.springboot.web_restfulcrud.filter; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import javax.servlet.*;
import java.io.IOException; /**
* @author : S K Y
* @version :0.0.1
*/
public class MyFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override
public void init(FilterConfig filterConfig) throws ServletException { } @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
logger.debug("自定义的Filter启用了!");
chain.doFilter(request, response);
} @Override
public void destroy() { }
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new MyFilter());
filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello", "/myServlet"));
return filterRegistrationBean;
}
--Listener
package com.zhiyun.springboot.web_restfulcrud.listener; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; /**
* @author : S K Y
* @version :0.0.1
*/
public class MyListener implements ServletContextListener {
private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override
public void contextInitialized(ServletContextEvent sce) {
logger.debug("contextInitialized...当前web应用启动了");
} @Override
public void contextDestroyed(ServletContextEvent sce) {
logger.debug("contextDestroyed...当前web项目销毁");
}
}
@Bean
public ServletListenerRegistrationBean servletListenerRegistrationBean() {
return new ServletListenerRegistrationBean<>(new MyListener());
}
--由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件.注册三大组件可以采取这样的方法.
--SpringBoot帮我们自动配置SpringMVC的是惠普,自动的注册SpringMVC的前端控制器:DispatcherServlet.默认拦截"/"所有资源包括静态资源,但是不拦截JSP请求,"/*"会拦截JSP.我们可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径.
三.使用其他的嵌入式容器
SpringBoot还支持Jetty(适合开发长连接的应用,例如聊天室),Undertow(不支持JSP):
1.默认使用了TomCat
2.切换使用其他Servlet容器,首先需要排除其中的spring-boot-starter-web -->spring-boot-starter-tomcat依赖,而后则可以引入其他的Servlet容器
<!--引入Web模块-->
<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>
四.嵌入式Servlet容器的自动配置原理
在SpringBoot中拥有如下自动配置类EmbeddedServletContainerAutoConfiguration:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
--该类就是嵌入式的Servlet容器自动配置类
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat { @Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
} }
--如果我们导入了Servlet的相关袭来,那么我们就会存在Servlet.class类以及Tomcat.class类,并且容器中不存在EmbeddedServletContainerFactory嵌入式容器工厂(用户自定义的Servlet容器工厂,那么该配置就会生效),在嵌入式容器工厂中定义了如下类:
public interface EmbeddedServletContainerFactory { /**
* Gets a new fully configured but paused {@link EmbeddedServletContainer} instance.
* Clients should not be able to connect to the returned server until
* {@link EmbeddedServletContainer#start()} is called (which happens when the
* {@link ApplicationContext} has been fully refreshed).
* @param initializers {@link ServletContextInitializer}s that should be applied as
* the container starts
* @return a fully configured and started {@link EmbeddedServletContainer}
* @see EmbeddedServletContainer#stop()
*/
EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers); }
--获取嵌入式的Servlet容器,在SpringBoot的默认实现中存在如下的实现:
--以嵌入式Tomcat容器工程为例:
@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);
return getTomcatEmbeddedServletContainer(tomcat);
}
--可以发现其内部使用Java代码的方式创建了一个Tomcat,并配置了Tomcat工作的基本环境,最终返回一个嵌入式的Tomcat容器
/**
* Create a new {@link TomcatEmbeddedServletContainer} instance.
* @param tomcat the underlying Tomcat server
* @param autoStart if the server should be started
*/
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
} private void initialize() throws EmbeddedServletContainerException {
TomcatEmbeddedServletContainer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
try {
// Remove service connectors to that protocol binding doesn't happen
// yet
removeServiceConnectors(); // Start the server to trigger initialization listeners
this.tomcat.start(); // We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions(); Context context = findContext();
try {
ContextBindings.bindClassLoader(context, getNamingToken(context),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
} // Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
containerCounter.decrementAndGet();
throw ex;
}
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat", ex);
}
}
}
--我们对嵌入式容器的配置修改是如何生效的:
1.修改ServerProperties中的属性
2.嵌入式Servlet容器定制器:EmbeddedServletContainerCustomizer,帮助我们修改了Sevlet容器的一些默认配置,例如端口号;在EmbeddedServletContainerAutoConfiguration中导入了一个名为BeanPostProcessorsRegistrar,给容器中导入一些组件即嵌入式Servlet容器的后置处理器.后置处理器表示的是在bean初始化前后(创建完对象,还没有赋予初值)执行初始化工作.
3.EmbeddedServletContainerAutoConfiguration为嵌入式Servlet容器的后置处理器的自动配置类,其存在如下类:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ConfigurableEmbeddedServletContainer) {
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
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;
}
4.获取到了所有的定制器,调用了每一个定制器的customer方法来给Servlet容器进行属性赋值.
5.ServerProperties也是配置器,因此其配置流程如下:
(1)SpringBoot根据导入的依赖情况,添加响应的配置容器工厂EmbeddedServletCustomerFactory
(2)容器中某个组件要创建对象就会惊动后置处理器;
(3)只要是嵌入式的Servlet容器工厂 后置处理器就工作,从容器中获取所有的EmbeddedServletContainerCustomizer调用定制器的定制方法
五.嵌入式Servlet容器启动原理
获取嵌入式的Servlet容器工厂:
1.SpringBoot引用启动运行run方法;
2.refreshContext(context);SpringBoot刷新容器并初始化容器,创建容器中的每一个组件:如果是Web应用,创建web的IOC容器AnnotationConfigEmbeddedWebApplicationContext,如果不是则创建AnnotationConfigApplicationContext;
3.refreshContext(context)刷新刚才创建好的容器
4.onRefresh():web的IOC容器重写了onRefresh方法;
5.webIOC容器会创建嵌入式的servlet容器:createEmbeddedServletContainer();
6.获取嵌入式的servlet容器工厂:EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
7.使用容器工厂获取嵌入式的Servlet容器;
8.嵌入式的Servlet容器创建对象,并启动servlet容器.
六.使用外置的Servlet容器
嵌入式Servlet容器:
优点: 简单,快捷
缺点:默认不支持JSP,优化定制复杂(使用定制器,自定义配置servlet容器的创建工厂);
--外部的Servlet容器,:外面安装Tomcat-应用war包的方式打包.
--我们使用war包的形式创建SpringBoot工程可以发现其目录结构如下:
--创建项目webapp路径及web-XML文件:
--部署Tomcat服务器:
--创建步骤:
1.必须创建一个war项目;
2.将嵌入式的Tomcat指定为provided
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
3.必须编写一个SpringBootServletInitializer的子类,目的就是调用config方法
package com.skykuqi.springboot.exteralservlet; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer; public class ServletInitializer extends SpringBootServletInitializer { @Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用的主程序
return application.sources(ExteralservletApplication.class);
} }
七.外置Servlet容器的启动原理
1.jar包:当我们的应用是使用SpringBoot的jar包形式的话,我们可以直接通过执行SpringBoot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器;
2.war包:启动服务器,服务器启动SpringBoot应用,启用IOC容器:
3.在Servlet3.0中有一项规范:
(1)服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer的实例;
(2)ServletContainerInitializer的实现必须放在META-INF/services文件夹下,该文件夹下还必须有一个文件名为javax.servlet.ServletContainerInitializer的文件,文件的内容就是ServletContainerInitializer实现的全类名.
(3)可以使用@HandlesTypes注解来实现,容器在应用启动的时候,加载我们所感兴趣的类.
4.启动流程:
(1)启动Tomcat服务器,Spring的Web模块中存在该文件:
(2)SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)所标注的所有这个类型的类都传入到onStartup方法的集合中为这些不是接口不是抽象类类型的类创建实例;
(3)每一个WebApplicationInitializer的实现类都调用自己的onStartup方法.
(4)相当于我们的SpringServletContainerInitializer的类会被创建对象,并执行onStartup方法;
(6)SpringServletContainerInitializer执行onStartup的时候会创建容器
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
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);
builder = configure(builder);
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);
}
return run(application);
}
--将创建RootApplicationContext容器,在创建容器时会进行如下操作:
a.创建SpringApplicationBuilder
b.在18行调用了configer(),将SpringBoot的主程序类传入了进来
c.使用builder创建一个Spring应用
SpringBoot(七) -- 嵌入式Servlet容器的更多相关文章
- java框架之SpringBoot(8)-嵌入式Servlet容器
前言 SpringBoot 默认使用的嵌入式 Servlet 容器为 Tomcat,通过依赖关系就可以看到: 问题: 如何定制和修改 Servlet 容器相关配置? SpringBoot 能否支持其它 ...
- springboot(八) 嵌入式Servlet容器自动配置原理和容器启动原理
1.嵌入式Servlet容器自动配置原理 1.1 在spring-boot-autoconfigure-1.5.9.RELEASE.jar => springboot自动配置依赖 jar包下,E ...
- SpringBoot配置嵌入式Servlet容器
1).如何定制和修改Servlet容器的相关配置: 1.修改和server有关的配置(ServerProperties[也是EmbeddedServletContainerCustomizer]): ...
- SpringBoot源码学习系列之嵌入式Servlet容器
目录 1.博客前言简单介绍 2.定制servlet容器 3.变换servlet容器 4.servlet容器启动原理 SpringBoot源码学习系列之嵌入式Servlet容器启动原理 @ 1.博客前言 ...
- SpringBoot嵌入式Servlet容器
SpringBoot默认是将Tomcat作为嵌入式的servlet容器. 问题: 如何修改嵌入式的servlet容器? 1)在配置文件中设置对应的属性值 server.port=8081 # Tomc ...
- 嵌入式Servlet容器自动配置和启动原理
EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置? @AutoConfigureOrder(Ordered.HIGHEST_PRE ...
- springboot外置的Servlet容器
嵌入式Servlet容器:应用打成可执行的jar 优点:简单.便携: 缺点:默认不支持JSP.优化定制比较复杂(使用定制器[ServerProperties.自定义EmbeddedServle ...
- 配置嵌入式Servlet容器
SpringBoot默认是用的是Tomcat作为嵌入式的Servlet容器:问题?1).如何定制和修改Servlet容器的相关配置:1.修改和server有关的配置(ServerProperties) ...
- 19、配置嵌入式servlet容器(下)
使用外置的Servlet 嵌入式Servlet容器:应用打成可执行的j ar 优点:简单.便携: 缺点:默认不支持JSP.优化定制比较复杂 使用定制器[ServerProperti ...
随机推荐
- 错误:非法字符:“\ufeff”
导入开源的项目的时候,你可以碰到以上的编码问题,这一般这个项目是用eclipse开发的.主要原因是: Eclipse可以自动把UTF-8+BOM文件转为普通的UTF-8文件,但Android ...
- 借助tcpdump统计http请求
借助tcpdump统计http请求 这里所说的统计http请求,是指统计QPS(每秒请求数),统计前十条被访问最多的url.一般做这样的统计时,我们经常会使用网站访问日志来统计.当我们来到一个 ...
- Java动手动脑02
一.平方数静方法: public class SquareInt { public static void main(String[] args) { int result; for (int x = ...
- SpringCloud创建Eureka
Eureka是什么 Eureka是Netflix的一 个子模块,也是核心模块之一- .Eureka是一 个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移.服务注册与发现对于微服务 ...
- day2 for,not,while,range
>>> def str_len(s): ... l = len(s) ... if l > 3: ... print("3") ... elif l < ...
- div 垂直居中的方法
方法一 .table-body>ul>li{ font-size: 0 } .table-body>ul>li:after { content: ''; width: 0; h ...
- Java——容器(Comparable)
[Comparable]
- codevs 1026 逃跑的拉尔夫 x
1026 逃跑的拉尔夫 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 年轻的拉尔夫开玩笑地从一个小镇上偷走了一辆车,但他 ...
- 洛谷 P4570 BZOJ 2460 [BJWC2011]元素
Time limit 20000 ms Memory limit 131072 kB OS Linux 解题思路 看题解可知 我们将矿石按照魔法值降序排序,然后依次将矿石编号放入线性基(突然想起线代里 ...
- 【转】解决ajax跨域问题的5种解决方案
转自: https://blog.csdn.net/itcats_cn/article/details/82318092 什么是跨域问题?跨域问题来源于JavaScript的"同源策略& ...