源码分析-手写springMVC框架@RequestMapping和@Controller注解

Posted 漫漫求索修身

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析-手写springMVC框架@RequestMapping和@Controller注解相关的知识,希望对你有一定的参考价值。

源码分析-手写springMVC框架@RequestMapping和@Controller注解

一、springMVC运行流程

1)、用户发送请求至前端控制器DipatcherServlet

2)、DipatcherServlet收到请求调用HandlerMapping处理器映射器

3)、处理器映射器根据请求url找到对应的处理器,生成处理器对象以及处理器拦截器(如果有则生成),一并返回给DipatcherServlet

4)、DipatcherServlet通过HandlerAdapter处理器适配器调用处理器

5)、执行处理器(Controller,也叫后端控制器)

6)、Controller执行完成后,返回ModelAndView

7)、处理器适配器将ModelAndView返回给DipatcherServlet

8)、DipatcherServlet将ModelAndView返回给视图解析器

9)、ViewReslover解析后返回给具体的view

10)、DipatcherServlet对view进行视图渲染,然后返回给用户

二、Servlet相关知识回顾

servlet是单例的,线程不安全的。

servlet的生命周期:加载---实例化----服务-----销毁

init():在servlet的生命周期中仅执行一次,在服务器装入servlet时执行,负责初始化servlet对象。

service():servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,都要调用该service方法

destroy():仅执行一次,在服务器停止且卸载Servlet时执行该方法。

三、手写springMVC思路

1、web.xml加载

为了读取web.xml中的配置,我们用到ServletConfig这个类,通过web.xml中加载我们自己写的ExtDispatcherServlet和读取配置信息

2、初始阶段

在前面我们提到DispatcherServlet的initStrategies方法会初始化9大组件,但是这里将实现一些SpringMVC的最基本的组件而不是全部,按顺序包括:

  • 加载配置文件

  • 扫描用户配置包下面所有的类

  • 拿到扫描到的类,通过反射机制,实例化。并且放到ioc容器中(Map的键值对 beanName-bean) beanName默认是首字母小写

  • 初始化HandlerMapping,这里其实就是把url和method对应起来放在一个k-v的Map中,在运行阶段取出

3、运行阶段

每一次请求将会调用doGet或doPost方法,所以统一运行阶段都放在doDispatch方法里处理,它会根据url请求去HandlerMapping中匹配到对应的Method,然后利用反射机制调用Controller中的url对应的方法,并得到结果返回。按顺序包括以下功能:

  • 异常的拦截

  • 获取请求传入的参数并处理参数

  • 通过初始化好的handlerMapping中拿出url对应的方法名,反射调用

4、手写springMVC基本实现

自定义注解

package com.cy.ext.springmvc.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface ExtController {

}

package com.cy.ext.springmvc.annotation;

import static java.lang.annotation.ElementType.METHOD;

import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(value={METHOD, TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface ExtRequestMapping {

     String value() default "";

}

controller类

package com.cy.ext.springmvc.controller;

import com.cy.ext.springmvc.annotation.ExtController;

import com.cy.ext.springmvc.annotation.ExtRequestMapping;

@ExtController

@ExtRequestMapping("/index")

public class IndexController {

     @ExtRequestMapping("/test")

     public String test(){

          System.out.println("The Program is to here=====");

          return "index";

     }

}

自定义前端控制器类

package com.cy.ext.springmvc.servlet;

import java.io.IOException;

import java.lang.reflect.Method;

import java.util.List;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.ConcurrentMap;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import com.cy.ext.springmvc.annotation.ExtController;

import com.cy.ext.springmvc.annotation.ExtRequestMapping;

import com.cy.ext.utils.ClassUtils;

/**

 * 手写springMVC 原理分析</br>

 * 1、创建一个前端控制器ExtDispatcherServlet 拦截所有请求,springMVC基于servlet实现<br>

 * 2、初始化操作,重写servletinit方法</br>

 * ####2.1、扫包 将包下所有的类,注入到springmvc容器里面,存放在map集合中,key默认为类名小写 value为对象<br>

 * ####2.2、将url映射和方法进行关联</br>

 * ########2.2.1、判断类上是否有注解,使用反射机制循环遍历方法,判断方法上是否存在注解,进行封装

 * 3、处理请求,重写get或者post方法(获取请求url,从urlBeans集合中获取实例对象,获取成功实例对象后,

 * 从urlMethodBeans集合中获取方法名称,然后使用反射机制执行)

 * @author ymk

 *

 */

public class ExtDispatcherServlet extends HttpServlet{

     /**

      * com.cy.ext.springmvc.servlet.ExtDispatcherServlet

      */

     private static final long serialVersionUID = 1L;

     //springMVC 容器对象key:类名id  value:对象

     private ConcurrentMap<String, Object> springmvcBeans = new ConcurrentHashMap<String, Object>();

     

     private ConcurrentMap<String, Object> urlBeans = new ConcurrentHashMap<String, Object>();

     

     private ConcurrentMap<String, Object> urlMethods = new ConcurrentHashMap<String, Object>();

     @Override

     public void init() throws ServletException {

          // 1、获取当前包下所有类

          List<Class<?>> classes = ClassUtils.getClasses("com.cy.ext");

          try {

              // 2、包下所有的类,注入到springmvc容器里面,存放在map集合中,key默认为类名小写 value为对象

              findClassMvcAnnotation(classes);

          } catch (Exception e) {

              

          }

          // 3、将url和方法进行映射

          handlerMapping();

     }

     

     

     @Override

     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

          // TODO Auto-generated method stub

          doPost(req, resp);

     }

     

     @Override

     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

          //获取请求的url

          String requestURI = req.getRequestURI();

          System.out.println("requestURI:"+requestURI);

          if (requestURI == null || requestURI == "") {

              return;

          }

          // 2.从Map集合中获取控制对象

          Object object = urlBeans.get(requestURI);

          if (object == null) {

              resp.getWriter().print("404----------------not found url");

              return;

          }

          Object methodNameObj = urlMethods.get(requestURI);

          if (methodNameObj == null) {

              resp.getWriter().println("not found method");

          }

          // 4.使用java的反射机制调用方法

          String methodName = String.valueOf(methodNameObj);

          String resultPage = (String) methodInvoke(object, methodName);

          // 5.调用视图转换器渲染给页面展示

          extResourceViewResolver(resultPage, req, resp);

          

     }

     

     // 调用视图转换器渲染给页面展示

     private void extResourceViewResolver(String pageName, HttpServletRequest request

              ,HttpServletResponse response) throws IOException,ServletException{

          String preffix = "/";

          String suffix = ".jsp";

          request.getRequestDispatcher(preffix+pageName+suffix).forward(request, response);

     }

     

     //使用java的反射机制调用方法

     private Object methodInvoke(Object object,String methodName){

          Class<? extends Object> classInfo = object.getClass();

          try {

              Method method = classInfo.getMethod(methodName);

              Object result = method.invoke(object);

              return result;

          } catch (Exception e) {

              return null;

          }

     }

     

     //包下所有的类,注入到springmvc容器里面,存放在map集合中,key默认为类名小写 value为对象

     private void findClassMvcAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {

          for (Class<?> classInfo : classes) {

              if (classInfo != null) {

                   ExtController extController = classInfo.getAnnotation(ExtController.class);

                   if (extController != null) {

                        //将类首字母转为小写

                        String beanId = ClassUtils.toLowerCaseFirstOne(classInfo.getSimpleName());

                        //初始化对象

                        Object object = ClassUtils.newInstance(classInfo);

                        springmvcBeans.put(beanId, object);

                   }

              }

          }

     }

     

     //将url和方法进行映射

     public void handlerMapping(){

          //// 1.遍历springmvc bean容器 判断类上属否有url映射注解

          for (Map.Entry<String, Object> mvcBean : springmvcBeans.entrySet()) {

              // 2.遍历所有的方法上是否有url映射注解

              // 获取bean的对象

              Object object = mvcBean.getValue();

              // 3.判断类上是否有加url映射注解

              Class<? extends Object> classInfo = object.getClass();

              ExtRequestMapping extRequestMapping = classInfo.getAnnotation(ExtRequestMapping.class);

//            ExtRequestMapping extRequestMapping = classInfo.getDeclaredAnnotation(ExtRequestMapping.class);

              String baseUrl = "";

              if (extRequestMapping != null) {

                   baseUrl = extRequestMapping.value();

              }

              Method[] methods = classInfo.getDeclaredMethods();

              for (Method method : methods) {

                   // 判断方法上是否有加url映射注解

                   ExtRequestMapping extMethodAnnotation = method.getAnnotation(ExtRequestMapping.class);

                   if (extMethodAnnotation != null) {

                        String methodUrl = baseUrl + extMethodAnnotation.value();

                        urlBeans.put(methodUrl, object);

                        urlMethods.put(methodUrl, method.getName());

                   }

              }

          }

     }

}

class扫包工具类

package com.cy.ext.utils;

import java.io.File;

import java.io.FileFilter;

import java.io.IOException;

import java.net.JarURLConnection;

import java.net.URL;

import java.net.URLDecoder;

import java.util.ArrayList;

import java.util.Enumeration;

import java.util.List;

import java.util.jar.JarEntry;

import java.util.jar.JarFile;

import org.apache.commons.lang.StringUtils;

/**

 * 扫包工具

 * @author ymk

 *

 */

public class ClassUtils {

     /**

      * 取得某个接口下所有实现这个接口的类

      */

     public static List<Class> getAllClassByInterface(Class c) {

          List<Class> returnClassList = null;

          if (c.isInterface()) {

              // 获取当前的包名

              String packageName = c.getPackage().getName();

              // 获取当前包下以及子包下所以的类

              List<Class<?>> allClass = getClasses(packageName);

              if (allClass != null) {

                   returnClassList = new ArrayList<Class>();

                   for (Class classes : allClass) {

                        // 判断是否是同一个接口

                        if (c.isAssignableFrom(classes)) {

                             // 本身不加入进去

                             if (!c.equals(classes)) {

                                  returnClassList.add(classes);

                             }

                        }

                   }

              }

          }

          return returnClassList;

     }

     /*

      * 取得某一类所在包的所有类名 不含迭代

      */

     public static String[] getPackageAllClassName(String classLocation, String packageName) {

          // 将packageName分解

          String[] packagePathSplit = packageName.split("[.]");

          String realClassLocation = classLocation;

          int packageLength = packagePathSplit.length;

          for (int i = 0; i < packageLength; i++) {

              realClassLocation = realClassLocation + File.separator + packagePathSplit[i];

          }

          File packeageDir = new File(realClassLocation);

          if (packeageDir.isDirectory()) {

              String[] allClassName = packeageDir.list();

              return allClassName;

          }

          return null;

     }

     /**

      * 从包package中获取所有的Class

      *

      * @param pack

      * @return

      */

     public static List<Class<?>> getClasses(String packageName) {

          // 第一个class类的集合

          List<Class<?>> classes = new ArrayList<Class<?>>();

          // 是否循环迭代

          boolean recursive = true;

          // 获取包的名字 并进行替换

          String packageDirName = packageName.replace('.', '/');

          // 定义一个枚举的集合 并进行循环来处理这个目录下的things

          Enumeration<URL> dirs;

          try {

              dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);

              // 循环迭代下去

              while (dirs.hasMoreElements()) {

                   // 获取下一个元素

                   URL url = dirs.nextElement();

                   // 得到协议的名称

                   String protocol = url.getProtocol();

                   // 如果是以文件的形式保存在服务器上

                   if ("file".equals(protocol)) {

                        // 获取包的物理路径

                        String filePath = URLDecoder.decode(url.getFile(), "UTF-8");

                        // 以文件的方式扫描整个包下的文件 并添加到集合中

                        findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);

                   } else if ("jar".equals(protocol)) {

                        // 如果是jar包文件

                        // 定义一个JarFile

                        JarFile jar;

                        try {

                             // 获取jar

                             jar = ((JarURLConnection) url.openConnection()).getJarFile();

                             // 从此jar包 得到一个枚举类

                             Enumeration<JarEntry> entries = jar.entries();

                             // 同样的进行循环迭代

                             while (entries.hasMoreElements()) {

                                  // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件

                                  JarEntry entry = entries.nextElement();

                                  String name = entry.getName();

                                  // 如果是以/开头的

                                  if (name.charAt(0) == '/') {

                                      // 获取后面的字符串

                                      name = name.substring(1);

                                  }

                                  // 如果前半部分和定义的包名相同

                                  if (name.startsWith(packageDirName)) {

                                      int idx = name.lastIndexOf('/');

                                      // 如果以"/"结尾 是一个包

                                      if (idx != -1) {

                                           // 获取包名 把"/"替换成"."

                                           packageName = name.substring(0, idx).replace('/', '.');

                                      }

                                      // 如果可以迭代下去 并且是一个包

                                      if ((idx != -1) || recursive) {

                                           // 如果是一个.class文件 而且不是目录

                                           if (name.endsWith(".class") && !entry.isDirectory()) {

                                                // 去掉后面的".class" 获取真正的类名

                                                String className = name.substring(packageName.length() + 1, name.length() - 6);

                                                try {

                                                     // 添加到classes

                                                     classes.add(Class.forName(packageName + '.' + className));

                                                } catch (ClassNotFoundException e) {

                                                     e.printStackTrace();

                                                }

                                           }

                                      }

                                  }

                             }

                        } catch (IOException e) {

                             e.printStackTrace();

                        }

                   }

              }

          } catch (IOException e) {

              e.printStackTrace();

          }

          return classes;

     }

     /**

      * 以文件的形式来获取包下的所有Class

      *

      * @param packageName

      * @param packagePath

      * @param recursive

      * @param classes

      */

     public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,

              List<Class<?>> classes) {

          // 获取此包的目录 建立一个File

          File dir = new File(packagePath);

          // 如果不存在或者 也不是目录就直接返回

          if (!dir.exists() || !dir.isDirectory()) {

              return;

          }

          // 如果存在 就获取包下的所有文件 包括目录

          File[] dirfiles = dir.listFiles(new FileFilter() {

              // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)

              public boolean accept(File file) {

                   return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));

              }

          });

          // 循环所有文件

          for (File file : dirfiles) {

              // 如果是目录 则继续扫描

              if (file.isDirectory()) {

                   findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,

                             classes);

              } else {

                   // 如果是java类文件 去掉后面的.class 只留下类名

                   String className = file.getName().substring(0, file.getName().length() - 6);

                   try {

                        // 添加到集合中去

                        classes.add(Class.forName(packageName + '.' + className));

                   } catch (ClassNotFoundException e) {

                        e.printStackTrace();

                   }

              }

          }

     }

     

     //将字符串首字母转为小写

     public static String getFirstLetterToLower(String str){

          if (StringUtils.isEmpty(str)) {

              return str;

          }

          return str.substring(0, 1).toLowerCase() + str.substring(1);

     }

     

     // 首字母转小写

     public static String toLowerCaseFirstOne(String s) {

          if (Character.isLowerCase(s.charAt(0)))

              return s;

          else

              return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();

     }

     

     //初始化对象

     public static Object newInstance(Class<?> classInfo) throws InstantiationException, IllegalAccessException{

          Object newInstance = classInfo.newInstance();

          return newInstance;

     }

}

pom文件:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.cy</groupId>

  <artifactId>springframework_day20</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>war</packaging>

 

  <dependencies>

          <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->

          <dependency>

              <groupId>javax.servlet</groupId>

              <artifactId>javax.servlet-api</artifactId>

              <version>3.1.0</version>

              <scope>provided</scope>

          </dependency>

          <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->

          <dependency>

              <groupId>commons-lang</groupId>

              <artifactId>commons-lang</artifactId>

              <version>2.6</version>

          </dependency>

  </dependencies>

<!--   <build>

        <plugins>

            <plugin>

                <groupId>org.apache.maven.plugins</groupId>

                <artifactId>maven-compiler-plugin</artifactId>

                <configuration>

                    <source>1.8</source>

                    <target>1.8</target>

                </configuration>

            </plugin>

        </plugins>

    </build> -->

</project>

web.xml文件

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

     version="2.4">

     <!-- Spring MVC 核心控制器 DispatcherServlet 配置 -->

     <servlet>

          <servlet-name>dispatcher</servlet-name>

          <servlet-class>com.cy.ext.springmvc.servlet.ExtDispatcherServlet

          </servlet-class>

          <load-on-startup>1</load-on-startup>

     </servlet>

     <servlet-mapping>

          <servlet-name>dispatcher</servlet-name>

          <!-- 拦截所有/* 的请求,交给DispatcherServlet处理,性能最好 -->

          <url-pattern>/</url-pattern>

     </servlet-mapping>

</web-app>


以上是关于源码分析-手写springMVC框架@RequestMapping和@Controller注解的主要内容,如果未能解决你的问题,请参考以下文章

手写模拟SpringMvc源码

自己手写一个 SpringMVC 框架

带你手写一个SpringMVC框架(有助于理解springMVC)

荧客技荐自己手写一个 SpringMVC 框架

手写spring+springmvc+mybatis框架篇springIOC容器

手写Spring MVC框架 实现简易版mvc框架