一.嵌入式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容器的定制器):
1 @Bean 2 public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){ 3 return new EmbeddedServletContainerCustomizer() { 4 //定制嵌入式的Servlet容器相关的规则 5 @Override 6 public void customize(ConfigurableEmbeddedServletContainer container) { 7 container.setPort(8083); //设置端口为8083 8 } 9 }; 10 }
二.注册Servlet Filter Listener
我们可以分别使用ServletRegisterationBean FilterRegisterationBean ServletListenerRegisterationBean完成这三大组件的注册
--Servlet
1 package com.zhiyun.springboot.web_restfulcrud.servlet; 2 3 import javax.servlet.ServletException; 4 import javax.servlet.http.HttpServlet; 5 import javax.servlet.http.HttpServletRequest; 6 import javax.servlet.http.HttpServletResponse; 7 import java.io.IOException; 8 9 /** 10 * @author : S K Y 11 * @version :0.0.1 12 */ 13 public class MyServlet extends HttpServlet { 14 //处理get()请求 15 @Override 16 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 17 doPost(req, resp); 18 } 19 20 @Override 21 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 22 resp.getWriter().write("Hello MyServlet"); 23 } 24 }
1 package com.zhiyun.springboot.web_restfulcrud.config; 2 3 import com.zhiyun.springboot.web_restfulcrud.servlet.MyServlet; 4 import org.springframework.boot.web.servlet.ServletRegistrationBean; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 8 /** 9 * @author : S K Y 10 * @version :0.0.1 11 */ 12 @Configuration 13 public class MyServerConfig { 14 //注册三大组件 15 @Bean 16 public ServletRegistrationBean servletRegistrationBean() { 17 return new ServletRegistrationBean(new MyServlet(), "/myServlet"); 18 } 19 }
--Filter
1 package com.zhiyun.springboot.web_restfulcrud.filter; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 6 import javax.servlet.*; 7 import java.io.IOException; 8 9 /** 10 * @author : S K Y 11 * @version :0.0.1 12 */ 13 public class MyFilter implements Filter { 14 private Logger logger = LoggerFactory.getLogger(this.getClass()); 15 16 @Override 17 public void init(FilterConfig filterConfig) throws ServletException { 18 19 } 20 21 @Override 22 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 23 logger.debug("自定义的Filter启用了!"); 24 chain.doFilter(request, response); 25 } 26 27 @Override 28 public void destroy() { 29 30 } 31 }
1 @Bean 2 public FilterRegistrationBean filterRegistrationBean() { 3 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); 4 filterRegistrationBean.setFilter(new MyFilter()); 5 filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello", "/myServlet")); 6 return filterRegistrationBean; 7 }
--Listener
1 package com.zhiyun.springboot.web_restfulcrud.listener; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 6 import javax.servlet.ServletContextEvent; 7 import javax.servlet.ServletContextListener; 8 9 /** 10 * @author : S K Y 11 * @version :0.0.1 12 */ 13 public class MyListener implements ServletContextListener { 14 private Logger logger = LoggerFactory.getLogger(this.getClass()); 15 16 @Override 17 public void contextInitialized(ServletContextEvent sce) { 18 logger.debug("contextInitialized...当前web应用启动了"); 19 } 20 21 @Override 22 public void contextDestroyed(ServletContextEvent sce) { 23 logger.debug("contextDestroyed...当前web项目销毁"); 24 } 25 }
1 @Bean 2 public ServletListenerRegistrationBean servletListenerRegistrationBean() { 3 return new ServletListenerRegistrationBean<>(new MyListener()); 4 }
--由于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容器
1 <!--引入Web模块--> 2 <dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-web</artifactId> 5 <exclusions> 6 <exclusion> 7 <artifactId>spring-boot-starter-tomcat</artifactId> 8 <groupId>org.springframework.boot</groupId> 9 </exclusion> 10 </exclusions> 11 </dependency> 12 13 <!--引入其他的Servlet容器--> 14 <dependency> 15 <artifactId>spring-boot-starter-jetty</artifactId> 16 <groupId>org.springframework.boot</groupId> 17 </dependency>
四.嵌入式Servlet容器的自动配置原理
在SpringBoot中拥有如下自动配置类EmbeddedServletContainerAutoConfiguration:
1 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 2 @Configuration 3 @ConditionalOnWebApplication 4 @Import(BeanPostProcessorsRegistrar.class) 5 public class EmbeddedServletContainerAutoConfiguration {
--该类就是嵌入式的Servlet容器自动配置类
1 /** 2 * Nested configuration if Tomcat is being used. 3 */ 4 @Configuration 5 @ConditionalOnClass({ Servlet.class, Tomcat.class }) 6 @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) 7 public static class EmbeddedTomcat { 8 9 @Bean 10 public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { 11 return new TomcatEmbeddedServletContainerFactory(); 12 } 13 14 }
--如果我们导入了Servlet的相关袭来,那么我们就会存在Servlet.class类以及Tomcat.class类,并且容器中不存在EmbeddedServletContainerFactory嵌入式容器工厂(用户自定义的Servlet容器工厂,那么该配置就会生效),在嵌入式容器工厂中定义了如下类:
1 public interface EmbeddedServletContainerFactory { 2 3 /** 4 * Gets a new fully configured but paused {@link EmbeddedServletContainer} instance. 5 * Clients should not be able to connect to the returned server until 6 * {@link EmbeddedServletContainer#start()} is called (which happens when the 7 * {@link ApplicationContext} has been fully refreshed). 8 * @param initializers {@link ServletContextInitializer}s that should be applied as 9 * the container starts 10 * @return a fully configured and started {@link EmbeddedServletContainer} 11 * @see EmbeddedServletContainer#stop() 12 */ 13 EmbeddedServletContainer getEmbeddedServletContainer( 14 ServletContextInitializer... initializers); 15 16 }
--获取嵌入式的Servlet容器,在SpringBoot的默认实现中存在如下的实现:
--以嵌入式Tomcat容器工程为例:
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 return getTomcatEmbeddedServletContainer(tomcat); 19 }
--可以发现其内部使用Java代码的方式创建了一个Tomcat,并配置了Tomcat工作的基本环境,最终返回一个嵌入式的Tomcat容器
1 /** 2 * Create a new {@link TomcatEmbeddedServletContainer} instance. 3 * @param tomcat the underlying Tomcat server 4 * @param autoStart if the server should be started 5 */ 6 public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) { 7 Assert.notNull(tomcat, "Tomcat Server must not be null"); 8 this.tomcat = tomcat; 9 this.autoStart = autoStart; 10 initialize(); 11 } 12 13 private void initialize() throws EmbeddedServletContainerException { 14 TomcatEmbeddedServletContainer.logger 15 .info("Tomcat initialized with port(s): " + getPortsDescription(false)); 16 synchronized (this.monitor) { 17 try { 18 addInstanceIdToEngineName(); 19 try { 20 // Remove service connectors to that protocol binding doesn‘t happen 21 // yet 22 removeServiceConnectors(); 23 24 // Start the server to trigger initialization listeners 25 this.tomcat.start(); 26 27 // We can re-throw failure exception directly in the main thread 28 rethrowDeferredStartupExceptions(); 29 30 Context context = findContext(); 31 try { 32 ContextBindings.bindClassLoader(context, getNamingToken(context), 33 getClass().getClassLoader()); 34 } 35 catch (NamingException ex) { 36 // Naming is not enabled. Continue 37 } 38 39 // Unlike Jetty, all Tomcat threads are daemon threads. We create a 40 // blocking non-daemon to stop immediate shutdown 41 startDaemonAwaitThread(); 42 } 43 catch (Exception ex) { 44 containerCounter.decrementAndGet(); 45 throw ex; 46 } 47 } 48 catch (Exception ex) { 49 throw new EmbeddedServletContainerException( 50 "Unable to start embedded Tomcat", ex); 51 } 52 } 53 }
--我们对嵌入式容器的配置修改是如何生效的:
1.修改ServerProperties中的属性
2.嵌入式Servlet容器定制器:EmbeddedServletContainerCustomizer,帮助我们修改了Sevlet容器的一些默认配置,例如端口号;在EmbeddedServletContainerAutoConfiguration中导入了一个名为BeanPostProcessorsRegistrar,给容器中导入一些组件即嵌入式Servlet容器的后置处理器.后置处理器表示的是在bean初始化前后(创建完对象,还没有赋予初值)执行初始化工作.
3.EmbeddedServletContainerAutoConfiguration为嵌入式Servlet容器的后置处理器的自动配置类,其存在如下类:
1 @Override 2 public Object postProcessBeforeInitialization(Object bean, String beanName) 3 throws BeansException { 4 if (bean instanceof ConfigurableEmbeddedServletContainer) { 5 postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean); 6 } 7 return bean; 8 }
1 private void postProcessBeforeInitialization( 2 ConfigurableEmbeddedServletContainer bean) { 3 for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) { 4 customizer.customize(bean); 5 } 6 }
1 private Collection<EmbeddedServletContainerCustomizer> getCustomizers() { 2 if (this.customizers == null) { 3 // Look up does not include the parent context 4 this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>( 5 this.beanFactory 6 .getBeansOfType(EmbeddedServletContainerCustomizer.class, 7 false, false) 8 .values()); 9 Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE); 10 this.customizers = Collections.unmodifiableList(this.customizers); 11 } 12 return this.customizers; 13 }
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
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-web</artifactId> 4 </dependency> 5 <dependency> 6 <groupId>org.springframework.boot</groupId> 7 <artifactId>spring-boot-starter-tomcat</artifactId> 8 <scope>provided</scope> 9 </dependency>
3.必须编写一个SpringBootServletInitializer的子类,目的就是调用config方法
1 package com.skykuqi.springboot.exteralservlet; 2 3 import org.springframework.boot.builder.SpringApplicationBuilder; 4 import org.springframework.boot.web.support.SpringBootServletInitializer; 5 6 public class ServletInitializer extends SpringBootServletInitializer { 7 8 @Override 9 protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 10 //传入SpringBoot应用的主程序 11 return application.sources(ExteralservletApplication.class); 12 } 13 14 }
七.外置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的时候会创建容器
1 protected WebApplicationContext createRootApplicationContext( 2 ServletContext servletContext) { 3 SpringApplicationBuilder builder = createSpringApplicationBuilder(); 4 StandardServletEnvironment environment = new StandardServletEnvironment(); 5 environment.initPropertySources(servletContext, null); 6 builder.environment(environment); 7 builder.main(getClass()); 8 ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); 9 if (parent != null) { 10 this.logger.info("Root context already created (using as parent)."); 11 servletContext.setAttribute( 12 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); 13 builder.initializers(new ParentContextApplicationContextInitializer(parent)); 14 } 15 builder.initializers( 16 new ServletContextApplicationContextInitializer(servletContext)); 17 builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); 18 builder = configure(builder); 19 SpringApplication application = builder.build(); 20 if (application.getSources().isEmpty() && AnnotationUtils 21 .findAnnotation(getClass(), Configuration.class) != null) { 22 application.getSources().add(getClass()); 23 } 24 Assert.state(!application.getSources().isEmpty(), 25 "No SpringApplication sources have been defined. Either override the " 26 + "configure method or add an @Configuration annotation"); 27 // Ensure error pages are registered 28 if (this.registerErrorPageFilter) { 29 application.getSources().add(ErrorPageFilterConfiguration.class); 30 } 31 return run(application); 32 }
--将创建RootApplicationContext容器,在创建容器时会进行如下操作:
a.创建SpringApplicationBuilder
b.在18行调用了configer(),将SpringBoot的主程序类传入了进来
c.使用builder创建一个Spring应用
原文地址:https://www.cnblogs.com/skykuqi/p/12000060.html