zoukankan      html  css  js  c++  java
  • springmvc 源码分析(一)-- DisparcherServlet的创建和注册到tomcat

    一. 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的拦截过程

      

     
     

     

        

        

        


       

         


          

     

  • 相关阅读:
    WinForm换行
    aspx获取页面间传送的值
    Response.BinaryWrite()方法输出二进制图像
    Jquery删除table的行和列
    WinForm DataGridView控件隔行变色
    IE中table的innerHTML无法赋值
    C#为IE添加可信任站点
    静态代码检查
    第三方开源小工具
    查看sql server 服务器内存使用情况
  • 原文地址:https://www.cnblogs.com/yangxiaohui227/p/13187719.html
Copyright © 2011-2022 走看看