从servlet到spring-mvc源码

52次阅读

共计 10330 个字符,预计需要花费 26 分钟才能阅读完成。

一:什么是 servlet

Java Servlet 简称 servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。


二:WEB 三大组件及 Servlet 实现

1.servlet;2.filter;3.listener;


原始版 Servlet 例子:

1. 创建工程:servlet-xml
2. 创建 servlet:

package com.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class ServletDemo
 */
public class ServletDemo extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public ServletDemo() {super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {response.setCharacterEncoding("GBK");
        response.getWriter().write("菜鸟教程:https://www.runoob.com/servlet/servlet-intro.html");
    }
    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {doGet(request, response);
    }

}

3.web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <servlet>
    <servlet-name>ServletDemo</servlet-name>
    <servlet-class>com.servlet.ServletDemo</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>ServletDemo</servlet-name>
    <url-pattern>/ServletDemo</url-pattern>
  </servlet-mapping>
</web-app>

4. 启动 tomcat, 访问 http://localhost:8080/servlet-xml/ServletDemo


注解的 Servlet 例子

不用 web.xml 配置,用注解也可以使用 servlet,查看 servlet 官方文档:

大概意思是说框架提供了 ServletContainerInitializer 的实现,必须是绑定在 META/INF/services 目录下,文件为:javax.servlet.ServletContainerInitializer, 文件里包含 ServletContainerInitializer 的实现类的目的地,类路径。

1. 新建工程 servlet-anno, 也是 Dynamic web project。
2. 新建 javax.servlet.ServletContainerInitializer 文件。

3. 新建 servlet:

package com.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class OrderServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        resp.getWriter().write("testServlet...order");
    }

}

4. 监听器的实现:

package com.servlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 * 监听应用的启动和停止
 */
public class OrderListener implements ServletContextListener {

    
    // 监听 ServletContext 销毁
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        // TODO Auto-generated method stub
        System.out.println("OrderListener...contextDestroyed...");
    }

    // 监听 ServletContext 启动初始化
    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        // TODO Auto-generated method stub
        ServletContext servletContext = arg0.getServletContext();
        System.out.println("OrderListener...contextInitialized...");
    }

}

5. 过滤器:

package com.servlet;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class OrderFilter implements Filter {

    @Override
    public void destroy() {// TODO Auto-generated method stub}

    @Override
    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
            throws IOException, ServletException {
        // 过滤请求
        System.out.println("UserFilter...doFilter...");
        // 放行
        arg2.doFilter(arg0, arg1);
        
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {// TODO Auto-generated method stub}

}

6. 实现 ServletContainerInitializer,即 servlet 容器初始化,主要是做三大组件的注册。

package com.servlet;

import java.util.EnumSet;
import java.util.Set;

import javax.servlet.DispatcherType;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;

import com.service.TestService;

// 容器启动的时候会将 @HandlesTypes 指定的这个类型下面的子类(实现类,子接口等)传递过来;// 传入感兴趣的类型;@HandlesTypes(value={TestService.class})
public class TestServletContainerInitializer implements ServletContainerInitializer{

    @Override
    public void onStartup(Set<Class<?>> arg0, ServletContext arg1) throws ServletException {System.out.println("感兴趣的类型:");
        for (Class<?> claz : arg0) {System.out.println(claz);// 当传进来后,可以根据自己需要利用反射来创建对象等操作
        }
        // 注册 servlet 组件
        javax.servlet.ServletRegistration.Dynamic servlet = arg1.addServlet("orderServlet", new OrderServlet());
        // 配置 servlet 的映射信息(路径请求)servlet.addMapping("/orderTest");
        
        // 注册监听器 Listener
        arg1.addListener(OrderListener.class);
        
        // 注册 Filter
        javax.servlet.FilterRegistration.Dynamic filter = arg1.addFilter("orderFilter", OrderFilter.class);
        // 添加 Filter 的映射信息,可以指定专门来拦截哪个 servlet
        filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
        
    }

}

tomcat 启动加载应用的时候,会运行 onStartup 方法;
Set<Class<?>> arg0:感兴趣的类型的所有子类型 (对实现了
TestService 接口相关的 );
ServletContext arg1: 代表当前 Web 应用的 ServletContext;一个 Web 应用一个 ServletContext;
1)、使用 ServletContext 注册 Web 组件(Servlet、Filter、Listener)
2)、使用编码的方式,在项目启动的时候给 ServletContext 里面添加组件;

启动 tomcat:

如图启动的时候打印出了 3 个感兴趣的类即实现了 TestService 接口的;另外打印出了监听器的日志,即 ServletContext 启动创建的时候。


访问 http://localhost:8080/servlet-anno/orderTest 即可进入对应的 servlet: 即进入了过滤器


除了在 ServletContainerInitializer 注册 servlet 还可以用注解 @WebServle 来加入组件:新增如下代码并重启 tomcat

@WebServlet("/anno")
public class TestServlet  extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("servlet-anno....success");
    }
}

访问 http://localhost:8080/servlet-anno/anno 也是 OK 的


三:Spring mvc

spring-mvc 整合也是基于 servlet 的,基于注解版的 servlet。

1. 新建 maven 工程:加入依赖

      <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>3.0-alpha-1</version>
            <scope>provided</scope>
        </dependency>

2. 查看 spring 依赖包:

①可找到 javax.servlet.ServletContainerInitializer 文件为:org.springframework.web.SpringServletContainerInitializer.


②即 SpringServletContainerInitializer 为 servlet 容器的初始化类, 容器启动调用 onStartup 方法: 查看代码发现主要是做了把感兴趣的类做一下判断然后调用各自的 onStartup 方法。

这个感兴趣的类是重点:@HandlesTypes(WebApplicationInitializer.class)


③WebApplicationInitializer 在 spring-web 中有三个实现类:
a:AbstractContextLoaderInitializer– 主要是注册监听器.


b:AbstractDispatcherServletInitializer– 主要是 DispatcherServlet 容器初始化,如图注册 servlet,filter 步骤都和注解版 servlet 步骤类似。

注意:创建 servlet 容器, 即 WebApplicationContext servletAppContext = createServletApplicationContext();
此方法是抽象归根到底是 AbstractAnnotationConfigDispatcherServletInitializer 的抽象方法 getServletConfigClasses


c:AbstractAnnotationConfigDispatcherServletInitializer– 提供了注解方式配置的 dispatcherServlet 初始化器,即如下 2 个方法:
1.getRootConfigClasses() 根容器 ( 存放 service 业务层,repositories 数据库 dao 层及事务控制相关处理);
2.getServletConfigClasses() 即 servlet 容器 ( 存放 controller, 视图解析, 映射相关)。

如官网图 https://docs.spring.io/spring…:


3:基于注解整合 spring-mvc

新建一个初始化器:

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import com.enjoy.config.TestAppConfig;
import com.enjoy.config.TestRootConfig;


//web 容器启动的时候创建对象;调用方法来初始化容器以前前端控制器
public class TestWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    // 获取根容器的配置类;(Spring 的配置文件)父容器;@Override
    protected Class<?>[] getRootConfigClasses() {
        // 指定配置类(配置文件)位置
        return new Class<?>[]{TestRootConfig.class} ;
    }

    // 获取 web 容器的配置类(SpringMVC 配置文件)子容器 (servlet 容器);@Override
    protected Class<?>[] getServletConfigClasses() {return new Class<?>[]{TestAppConfig.class} ;
    }

    // 获取 DispatcherServlet 的映射信息
    //  /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括 *.jsp;//  /*:拦截所有请求;连 *.jsp 页面都拦截;jsp 页面是 tomcat 的 jsp 引擎解析的;@Override
    protected String[] getServletMappings() {
        // TODO Auto-generated method stub
        return new String[]{"/"};
    }

}

再来看看父容器 TestRootConfig 和 servlet 容器 TestAppConfig 配置类:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

//Spring 的容器不扫描 controller; 父容器
@ComponentScan(value="com.test",excludeFilters={@Filter(type=FilterType.ANNOTATION,classes={Controller.class})
})
public class TestRootConfig {}
import java.util.List;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;


//SpringMVC 只扫描 Controller;子容器
//useDefaultFilters=false 禁用默认的过滤规则;@ComponentScan(value="com.test",includeFilters={@Filter(type=FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters=false)
public class TestAppConfig   {}

启动 tomcat,2 个配置文件分别会扫描对应的类加载到 spring 的对应的根容器和 servlet 容器中, 注解整合 spring-mvc 就完成啦!!!


四:注解定制和接管 spring-mvc

xml 配置的时候如下配置拦截器,视图解析等。


若使用注解配置定制我们的 springmvc, 可看官网描述,加入 @EnableWebMvc, 来定制配置功能, 直接在 servlet 容器对应的配置里面加入 @EnableWebMvc 并实现 WebMvcConfigurer 接口即可对对应需要配置的用代码做配置, 或者继承 WebMvcConfigurer 的具体实现类。

正文完
 0