代码:
toWriteMVC.zip

4.1 SpringMVC的大致原理

4. 手写MVC框架 - 图1

4.2 步骤

  1. 先创建一个maven的web工程,引入webapp。
  2. 引入servlet的依赖,因为mvc就是对原生servlet的封装
  3. 创建DispatcherServlet类,继承HttpServlet
  4. 编写springmvc.properties配置文件,用来写mvc的一些配置
  5. 在web.xml中配置servlet(配置的就是刚刚自己创建的DispatcherServlet类)和servlet-mapping(配置拦截什么url)
  6. 进行注解的开发,主要是@Controller,@Service,@RequestMapping,@Autowired
  7. 编写controller类,service类,来用上@Controller,@Service,@RequestMapping,@Autowired等注解。
  8. 编写DispatcherServlet类的初始化流程:
  • 1.加载配置文件 springmvc.properties,把配置文件加载程Propeties 类
  • 2.扫描相关的类,扫描注解,把所有的java类的全限定类名存储起来
  • 3.初始化bean对象(实现ioc容器,基于注解),通过上一步存的全限定类名,用反射实例化对象并缓存起来
  • 4.实现依赖注入,通过遍历ioc容器里存储的类,获取到属性,再判断属性上有没有注解@Autowired,有则利用反射注入该属性的类对象
  • 5.构造一个HandlerMapping,将配置好的url和method建立映射关系
  1. 因为前端请求,然后DispatcherServlet执行doPost方法,通过反射执行method的时候需要传入对象、参数,所以编写一个Handler类来封装这些数据
  2. 总结:前端请求经过DispatcherServlet拦截,对请求的url通过HandlerMapping组件去匹配到相应的方法,然后再匹配方法的参数,进行方法执行。

    4.3 关键代码:主要是自定义Servlet的初始化流程

  3. 加载配置文件,因为web.xml配置了springmvc.properties的路径,获取该路径读取成字节流 ```java private Properties properties = new Properties();

// 加载配置文件 private void doLoadConfig(String configLocation) { // 把配置文件加载到properties当中 InputStream stream = this.getClass().getClassLoader().getResourceAsStream(configLocation); try { properties.load(stream); } catch (IOException e) { e.printStackTrace(); }

  1. }
  1. 2. 扫描配置的包所有类,把标有相关注解(controllerservice等)的类的全限定类名存储起来。
  2. ```java
  3. // 缓存扫描到的类的全限定类名
  4. private List<String> classNames = new ArrayList<>();
  5. // 扫描相关的类,扫描注解 scanPackage:com.atm.demo ->磁盘上的文件夹
  6. private void doScan(String scanPackage) {
  7. // 获取类路径+scanPackage路径=磁盘上的绝对路径
  8. String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath()
  9. + scanPackage.replace(".", "/");
  10. File pack = new File(scanPackagePath);
  11. // 拿到scanPackage目录的子文件
  12. File[] files = pack.listFiles();
  13. assert files != null;
  14. for (File file : files) {
  15. // 说明是子package
  16. if (file.isDirectory()) {
  17. // 递归
  18. doScan(scanPackage + "." + file.getName());
  19. } else if (file.getName().endsWith(".class")) {
  20. // 说明是个java类,获取全限定类名
  21. String className = scanPackage + "." + file.getName().replaceAll(".class","");
  22. classNames.add(className);
  23. }
  24. }
  25. }
  1. 初始化Bean对象,加入IOC容器 ```java // ioc容器 private Map ioc = new HashMap<>();

// 初始化bean对象(实现ioc容器,基于注解) private void doInstance() throws ClassNotFoundException, InstantiationException, IllegalAccessException { if (classNames.size() == 0) { return; } for (String className : classNames) { // 反射创建对象 Class<?> aClass = Class.forName(className); // 先不实例化,区分controller、service,如果是service得判断注解有没有值,有则取该值作 // 为bean的id if (aClass.isAnnotationPresent(AtmController.class)) { // controller的id不做处理,不取value,直接拿类的首字母小写作为id String id = lowerFirst(aClass.getSimpleName()); Object o = aClass.newInstance(); ioc.put(id,o); } else if (aClass.isAnnotationPresent(AtmService.class)) { AtmService annotation = aClass.getAnnotation(AtmService.class); // 获取注解的值 String beanName = annotation.value(); if (!””.equals(beanName.trim())) { ioc.put(beanName,aClass.newInstance()); } else { beanName = lowerFirst(aClass.getSimpleName()); ioc.put(beanName,aClass.newInstance()); } // service层往往是有接口的,此时再以接口为id,放入一份对象到ioc中 Class<?>[] interfaces = aClass.getInterfaces(); for (Class<?> anInterface : interfaces) { ioc.put(anInterface.getName(),aClass.newInstance()); } } } }

// 首字母小写方法 public String lowerFirst(String str) { char[] chars = str.toCharArray(); if (‘A’ <= chars[0] && ‘Z’ >= chars[0]) { chars[0] += 32; } return String.valueOf(chars); }

  1. 4. 实现依赖注入,判断每个bean里的属性,如果有Autowire注解则是需要注入的属性
  2. ```java
  3. // 实现依赖注入
  4. private void doAutowired() {
  5. if (ioc.isEmpty()) {
  6. return;
  7. }
  8. // 有对象,进行依赖处理
  9. for (Map.Entry<String, Object> entry : ioc.entrySet()) {
  10. // 获取bean对象中的所有字段
  11. Object value = entry.getValue();
  12. Field[] declaredFields = value.getClass().getDeclaredFields();
  13. for (Field field : declaredFields) {
  14. // 查看对象是否有Autowired的注解
  15. if (field.isAnnotationPresent(AtmAutowired.class)) {
  16. AtmAutowired annotation = field.getAnnotation(AtmAutowired.class);
  17. // 需要注入的bean的id
  18. String beanName = annotation.value();
  19. if ("".equals(beanName.trim())) {
  20. beanName = field.getType().getName();
  21. }
  22. // 开启赋值 开启强制访问
  23. field.setAccessible(true);
  24. try {
  25. // 把该类上的field属性设置为ioc.get(beanName)的值
  26. field.set(value,ioc.get(beanName));
  27. } catch (IllegalAccessException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32. }
  33. }
  1. 构造一个HandlerMapping,将配置好的url和method建立映射关系 ```java /**
  • 构造一个HandlerMapping,将配置好的url和method建立映射关系
  • 最关键的环节 */ private void initHandlerMapping() { if (ioc.isEmpty()) {
    1. return;
    } for (Map.Entry entry : ioc.entrySet()) {
    1. // 遍历ioc,获取当前遍历对象的class对象
    2. Class<?> aClass = entry.getValue().getClass();
    3. // 如果不是controller类就没必要处理
    4. if (!aClass.isAnnotationPresent(AtmController.class)) {
    5. continue;
    6. }
    7. String baseUrl = "";
    8. if (aClass.isAnnotationPresent(AtmRequestMapping.class)) {
    9. // 如果类上标了注解@RequestMapping,那么url的前缀就是该注解填写的值
    10. AtmRequestMapping annotation = aClass.getAnnotation(AtmRequestMapping.class);
    11. baseUrl = annotation.value();
    12. }
    13. Method[] methods = aClass.getMethods();
    14. for (Method method : methods) {
    15. // 如果类中的方法标识注解@RequestMapping就进行处理
    16. if (method.isAnnotationPresent(AtmRequestMapping.class)) {
    17. AtmRequestMapping annotation = method.getAnnotation(AtmRequestMapping.class);
    18. String methodUrl = annotation.value();
    19. String url = baseUrl + methodUrl;
    20. // 把method的所有信息以及url封装成Handler
    21. Handler handler = new Handler(entry.getValue(),method, Pattern.compile(url));
    22. // 处理计算参数位置信息
    23. Parameter[] parameters = method.getParameters();
    24. for (int i = 0; i < parameters.length; i++) {
    25. Parameter parameter = parameters[i];
    26. if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
    27. // 如果参数是request或者response对象,则参数名称写HttpServletRequest和HttpServletResponse
    28. handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),i);
    29. } else {
    30. handler.getParamIndexMapping().put(parameter.getName(),i);
    31. }
    32. }
    33. handlerMapping.add(handler);
    34. }
    35. }
    } } ```
  1. 方法执行的时候,都会经过自定义DisapatcherServlet的doGet或者doPost方法。 ```java @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 真正处理请求:根据url找到对应的method方法,进行调用 // 获取url // String requestUrl = req.getRequestURI(); // 反射调用 需要传入对象、参数 // method.invoke() // 根据url获取到当前请求的handler Handler handler = getHandler(req); if (handler == null) {

    1. resp.getWriter().write("404 not found");
    2. return;

    } // 参数绑定 Class<?>[] parameterTypes = handler.getMethod().getParameterTypes(); Object[] paramValues = new Object[parameterTypes.length]; // 往参数数组塞值,并且保证参数顺序与方法中一致 Map parameterMap = req.getParameterMap(); for (Map.Entry param : parameterMap.entrySet()) {

    1. String value = StringUtils.join(param.getValue(), ",");
    2. if (handler.getParamIndexMapping().containsKey(param.getKey())) {
    3. // 方法形参确实有该参数
    4. Integer index = handler.getParamIndexMapping().get(param.getKey());
    5. paramValues[index] = value;
    6. }

    } Integer requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); paramValues[requestIndex] = req;

    Integer responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName()); paramValues[responseIndex] = resp;

    // 执行handler中的method方法 try {

    1. handler.getMethod().invoke(handler.getController(), paramValues);

    } catch (IllegalAccessException | InvocationTargetException e) {

    1. e.printStackTrace();

    } }

private Handler getHandler(HttpServletRequest req) { if (handlerMapping.isEmpty()) { return null; } String url = req.getRequestURI(); for (Handler handler : handlerMapping) { Matcher matcher = handler.getPattern().matcher(url); if (matcher.matches()) { return handler; } } return null; } ```

4.4 发现的问题

  1. 在java中接口类是不能被实例化的,因为实例化的对象放在堆内存中,而接口只允许静态属性和方法声明,分别存在代码块和静态数据块,所以开辟一块堆内存来存放接口对象没有意义。