servlet容器(tomcat),springmvc,filter,interceptor等关系

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了servlet容器(tomcat),springmvc,filter,interceptor等关系相关的知识,希望对你有一定的参考价值。

参考技术A https://blog.csdn.net/leonardc/article/details/80281477
讲的很透彻

servlet一般会处理servlet request,结束后返回一个servlet response
这俩都是接口

java里面 servlet是负责处理一个请求的具体逻辑类对象,但是不负责rpc
servlet容器,是对外接受url请求,根据映射,转发给一个servlet处理,并返回响应
比如tomcat,在开发的时候,重点是web.xml配置文件
filter是数据servlet容器的东西,和springmvc没关系
是在容器启动的时候,初始化的,不是由spring来管理的
执行顺序,和web.xml里面配置顺序有关系

web.xml中会配置很多的servlet,filter(它是一类特殊的servlet), listener监听器,然后会映射到不同的url上
如果使用springmvc框架的话,
会在最开始,加载spring的配置文件,毕竟spring也是需要有人来加载的
这里面加载的是除controller之外其他的bean
到一个叫contextConfigration的对象中

这个dispatcherServlet 的初始化,是需要具体的springmvc容器的配置文件来初始化的
这里面配置一些interceptor的bean
以及扫描所有controller
dispatcherServlet 的初始化也会需要一开始初始化的contextConfigration对象

springmvc是个对象容器,是spring的一个子集,是一个用来管理controller对象的
而service,dao等对象,是由spring容器来管理
所以springmvc里的bean可以调用spring里的bean,反之不行
interceptor是springmvc管理的,通过aop来实现!

tomcat servlet容器,不仅可以和springmvc配合,也可以和struts配合

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容器的定制器):

 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以上是关于servlet容器(tomcat),springmvc,filter,interceptor等关系的主要内容,如果未能解决你的问题,请参考以下文章

Servlet容器:Jetty和tomcat的比较

Servlet容器:Jetty和tomcat的比较

都知道Tomcat是个Servlet容器,可是Servlet又是怎么回事?

SpringBoot -- 嵌入式Servlet容器

JSP学习 —— 开篇:JSP,servlet容器,Tomcat,servlet容器之间的关系

[Java]Servlet工作原理之一:Servlet的容器