一. servlet 3.0 的使用

1.1 环境搭建:

servlet跟spring没有任何关系,我创建一个servlet可以不依赖spring,现在搭建一个纯的servlet项目,并实现简单的类似springMVC的功能:

引入依赖:

   <dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0.1</version>
<scope>provided</scope>
</dependency>

项目的结构:

1.2    web 三大组件的创建 servlet filter listener:

自己创建的servlet可以直接实现Servlet接口,也可以继承HttpServlet

public class DispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("test");
}
}

自己创建的filter要实现Filter接口:

public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest=(HttpServletRequest) request;
String userName = httpServletRequest.getParameter("userName");
if(!"xiaoMing".equals(userName)){
response.getWriter().write("用户名不正确");
return;
}
chain.doFilter(request,response);
}
}

自己创建的监听器需要实现下面接口:

public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext sc = sce.getServletContext();
//todo 可以用sc注册各种组件 } @Override
public void contextDestroyed(ServletContextEvent sce) { }
}

web组件注册到tomcat容器的三种方式:

方式1: 在web.xml中配置:

方式2: servlet3.0的方式,使用注解实现

方式3: 通过servletContex来注册:这里要用到servlet的3.0的一个SPI规范: tomcat容器在启动时,会找到类路径下:  META-INF/services/javax.servlet.ServletContainerInitializer 这个文件,文件名是固定的,为ServletContainerInitializer类的全限定类名,

文件里面的内容,为实现ServletContainerInitializer该接口的类的全限定类名,tomcat会调用该类的onStartup方法:

那么我们可以定义一个类来实现它:

然后在类路径下:

         

上面的方式一和方式三在springMVC中有使用到:
    1.3.自定义简单的springMVC功能:

思路: servlet可以配置对应的访问路径,例如路径"/product/info" 代表访问的是http://product/info,那么如果我配置了的路径为“/”,那么就会拦截所有的请求,拦截请求后,之后我再根据路径去找到对应的controller,例如我的dispartcherservlet

的拦截路径是“/”,前端请求url为/product/info,那么拦截到请求后,拿到/product/info去找到对应的controller:
  先创建一个注解:(下面的注解是我自己定义的,跟springMVC是没关系的)

创建一个controller:

修改dispatcherServlet的逻辑:

package com.yang.xiao.hui.servlet;

import com.yang.xiao.annotation.RequestMapping;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method; public class DispatcherServlet extends HttpServlet { @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext = req.getServletContext();
String controller = (String)servletContext.getAttribute("controller"); //拿到controller的权限定类名
try {
Class<?> aClass = Class.forName(controller); //反射创建对象
Method[] declaredMethods = aClass.getDeclaredMethods(); //获取所有的方法 String requestURI = req.getRequestURI(); //拿到请求路径
Object o = aClass.newInstance(); //创建controller对象
Method finalMethod=null;
for (Method method : declaredMethods) {
if(!method.isAnnotationPresent(RequestMapping.class)){ //遍历所有的方法,判断方法有没RequestMapping注解
continue;
}
RequestMapping reqMapping = method.getAnnotation(RequestMapping.class);
String url = reqMapping.url();
if(requestURI.equals(url)){ //请求的url有没跟方法RequestMapping注解的url一致
finalMethod=method;
break;
} }
if(finalMethod!=null){
Object invoke = finalMethod.invoke(o); //找到对应的处理方法,反射调用
resp.getWriter().print(invoke);
} } catch (Exception e) {
e.printStackTrace();
} } @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}

contoller的权限定类名是什么时候放到ServletContext作为属性的呢?

总结下自定义springmvc的逻辑:

二 .springMVC 源码分析
    2.1 springMVC是如何注册DispatcherServlet的?(非springboot项目)

spring注册DispatcherServlet主要有2种方式,一种是在web.xml中配置,一种是servlet 3.0以后提供的一个SPI:实现:ServletContainerInitializer 接口  并在类路径下 META-INF/services/javax.servlet.ServletContainerInitializer配置

先看第一种方式: web.xml,这种是spring最老的一种配置方式:

第二种方式: 零配置实现:
  

在srpingweb的jar包下可以看到javax.servlet.ServletContainerInitializer文件,打开查询看内容:

所以根据servlet 3.0规范,tomcat启动时会调用SpringServletContainerInitializer类的onStartup方法:

@HandlesTypes(WebApplicationInitializer.class)     //WebApplicationInitializer  该接口的实现类 将会在调用下面onStartup方法时,传入Set<Class<?>> webAppInitializerClasses集合中
public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) //根据servlet 3.0规范,当tomcat启动时,将会调用该方法
throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<>(); if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
} if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
} servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext); //遍历所有的WebApplicationInitializer实现类,并调用起onStartup(servletContext)方法
}
} }

通过上面分析: tomcat启动时会调用SpringServletContainerInitializer的onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)方法,该方法的逻辑主要是调用webAppInitializerClasses集合里面每个实例的

onStartup(servletContext) 方法,因此我只要实现WebApplicationInitializer就可以了;

看看WebApplicationInitializer接口的代码:

根据这个原理实现零配置注册dispatcherServlet的代码如下:

public class MyWebAppInitializer implements WebApplicationInitializer {

	public void onStartup(ServletContext container) {
// Create the 'root' Spring application context 创建父容器 用于service 和 dao 层
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(AppConfig.class); // Manage the lifecycle of the root application context 注册监听器
container.addListener(new ContextLoaderListener(rootContext)); // Create the dispatcher servlet's Spring application context 注册子容器 用于controller
AnnotationConfigWebApplicationContext dispatcherContext =
new AnnotationConfigWebApplicationContext();
dispatcherContext.register(DispatcherConfig.class);
dispatcherContext.setParent(rootContext);
// Register and map the dispatcher servlet 注册dispatcherServlet到tomcat
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}

2.2 springboot是如何注册DispatcherServlet?
      使用的是springboot的自动装配机制,在org.springframework.boot.autoconfigure的jar包中有个DispatcherServletAutoConfiguration配置类:

该类有个方法创建DispatcherServlet作为bean传入spring容器中:

还有一个类,负责将DispatcherServlet注入tomcat 容器:

我们可以看到,通过DispatcherServletRegistrationBean这个bean 将DispatcherServlet注入tomcat容器的,拦截路径由webMvcProperties.getServlet().getPath()获得:

可以看到拦截的路径为“/”,代表拦截所有,我们再看看DispatcherServletRegistrationBean的父类:

我在回想之前纯servlet项目时,通过代码向tomcat注册servlet的方式:

由此可见,最终也是用到ServletRegistration.Dynamic类来向容器注册对应的servlet

DispatcherServletRegistrationBean创建时传入了DispatcherServlet,在其父类中有个方法:

上图的方式跟我们之前注入的方式是一样的:

至此,我们可以知道DispatcherServlet是如何注册到tomcat的了,下一篇springMVC源码分析,将会分析DispatcherServlet的拦截过程

  

springmvc 源码分析(一)-- DisparcherServlet的创建和注册到tomcat的更多相关文章

  1. 7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解

    从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, res ...

  2. springMVC源码分析--访问请求执行ServletInvocableHandlerMethod和InvocableHandlerMethod

    在之前一篇博客中springMVC源码分析--RequestMappingHandlerAdapter(五)我们已经简单的介绍到具体请求访问的执行某个Controller中的方法是在RequestMa ...

  3. springMVC源码分析--AbstractHandlerMethodMapping注册url和HandlerMethod对应关系(十一)

    在上一篇博客springMVC源码分析--AbstractHandlerMethodMapping获取url和HandlerMethod对应关系(十)中我们简单地介绍了获取url和HandlerMet ...

  4. springMVC源码分析--AbstractHandlerMapping(二)

    上一篇博客springMVC源码分析--HandlerMapping(一)中我们简单的介绍了HandlerMapping,接下来我们介绍一下它的抽象实现类AbstractHandlerMapping

  5. springMVC源码分析--国际化实现Session和Cookie(二)

    上一篇博客springMVC源码分析--国际化LocaleResolver(一)中我们介绍了springMVC提供的国际化的解决方案,接下来我们根据springMVC提供的解决方案来简单的实现一个多语 ...

  6. SpringMVC源码分析--容器初始化(四)FrameworkServlet

    在上一篇博客SpringMVC源码分析--容器初始化(三)HttpServletBean我们介绍了HttpServletBean的init函数,其主要作用是初始化了一下SpringMVC配置文件的地址 ...

  7. springMVC源码分析--容器初始化(二)DispatcherServlet

    在上一篇博客springMVC源码分析--容器初始化(一)中我们介绍了spring web初始化IOC容器的过程,springMVC作为spring项目中的子项目,其可以和spring web容器很好 ...

  8. springMVC源码分析--FlashMap和FlashMapManager重定向数据保存

    在上一篇博客springMVC源码分析--页面跳转RedirectView(三)中我们看到了在RedirectView跳转时会将跳转之前的请求中的参数保存到fFlashMap中,然后通过FlashMa ...

  9. springMVC源码分析--页面跳转RedirectView(三)

    之前两篇博客springMVC源码分析--视图View(一)和springMVC源码分析--视图AbstractView和InternalResourceView(二)中我们已经简单的介绍了View相 ...

随机推荐

  1. promise和async await的区别

    在项目中第一次遇到async await的这种异步写法,来搞懂它 项目场景 :点击登录按钮后执行的事件,先进行表单校验 this.$refs.loginFormRef.validate(element ...

  2. PHP学习中的一些总结(持续更新)

    文件上传部分 在前台的<form>表单中 hidden隐藏域的MAX_FILE_SIZE可以起到实质性的控制作用,即在文件上传之前就可以判断文件的大小,格式为: <form acti ...

  3. Vue.js 实战教程(附demo)

    在实战之前,你需要对vuejs的基础语法有一定的了解,可以通过以下几个途径进行学习: vue.js官方文档:https://cn.vuejs.org/v2/guide/index.html vue.j ...

  4. Android开发之设置应用设置全屏的两种解决方法 兼容android5.0等两种解决方法

    在开发中我们经常需要把我们的应用设置为全屏,有两种方法,一中是在代码中设置,另一种方法是在配置文件里改! 一.在代码中设置:  代码如下: package com.android.tutor; imp ...

  5. okhttp3 示例

    1.GET请求 private fun httpGetDemo() { //1.请求参数 val url = httpHost + "/api/test?arg1=xxx" //2 ...

  6. 记录Unity的优化tip(不断更新)

    大概记录遇到的可以优化的点.1.Mesh.UploadMeshData:预先把网格送到GPU unity是这样的,它对一个网格,先把它搞到内存,然后在第一次渲染它时把它送到GPU.但送GPU经常是个瓶 ...

  7. 关于js重名方法的先后调用问题

    当js中方法重名时,最后引入的js会覆盖前面的引入的js(就是说会调用最后引入的js中的方法)详情参照(main.js与white.js 的a())但是,当最后一个js中存在语法上的错误时(也可以是本 ...

  8. Zabbix如何监控Linux防火墙服务

    今天在巡检的时候,突然想到Zabbix能否监控Linux的防火墙服务呢? 显然是可以的,但是Zabbix 5下默认的模板"Template OS Linux by Zabbix agent& ...

  9. leetcode刷题-64最小路径和

    题目 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. 说明:每次只能向下或者向右移动一步. 示例: 输入:[  [1,3,1],  [1,5, ...

  10. 3.AVPacket使用

    1.使用注意 AVPacket需要用户通过av_packet_allc()创建好空间后.才能供给fimpeg进行获取解码前帧数据,由于解码前帧数据大小是不固定的(比如I帧数据量最大)所以ffmpeg会 ...