手写SpringMVC 关键功能实现

2 篇文章 0 订阅
1 篇文章 0 订阅

根据SpringMVC手写自己的MVC框架


此文章的GITEE地址

分析SpringMVC 执行原理

【tomcat加载->web.xml】

【前端控制器DispatcherServlet->加载指定配置文件Springmvc.xml】

【包扫描,扫描注解->@controller/@Service/@RequestMapping/AutoWired】

【Ioc容器进行相应Bean初始化以及依赖注入维护】

【SpringMVC 相关组件初始化…、建立Url与Method之间的映射关系->HandlerMapping(处理器映射器)】

【等待请求,处理请求】
加载大致流程文字描述

手写MVC框架之注解开发

自定义注解

MyController
package com.my.edu.mvcframework.annotations;

import java.lang.annotation.*;

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
    String value() default "";
}

MyService
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
    String value() default "";
}

MyAutowired
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
    String value() default "";
}

MyRequestMapping
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
    String value() default "";
}
自定义DispatcherServlet
继承
public class MyDispatcherServlet extends HttpServlet {...}

参数
private Properties properties = new Properties();
//扫描类的权限定类名集合
private List<Stirng> classNames = new ArrayList<>();
//ioc容器
private Map<String,Object> ioc = new HashMap<>();
//存储Url和Method之间的映射关系
private List<Handler> handlerMapping = new ArraryList<>();
方法合计
@Override
    public void init(ServletConfig config) throws ServletException {
        // 1 加载配置文件 springmvc.properties
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);
        // 2 扫描相关的类,扫描注解
        doScan(properties.getProperty("scanPackage"));
        // 3 初始化bean对象(实现ioc容器,基于注解)
        doInstance();
        // 4 实现依赖注入
        doAutoWired();
        // 5 构造一个HandlerMapping处理器映射器,将配置好的url和Method建立映射关系
        initHandlerMapping();
        System.out.println("my mvc 初始化完成....");
        // 等待请求进入,处理请求
    }
加载配置文件doLoadConfig

    // 加载配置文件
    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
扫描相关的类 & 注解 doScan()
  • scanPackage: com.my.demo package----> 磁盘上的文件夹(File) com/my/demo
	指定文件的绝对路径
Thread.currentThread().getContextClassLoader().getResource("springmvc.properties").getPath();
->/Users/xxx/data/@Bruce/learn/mvc/target/classes/springmvc.properties

  classes的绝对路径
Thread.currentThread().getContextClassLoader().getResource("").getPath();  
->/Users/xxx/data/@Bruce/learn/mvc/target/classes/
private void doScan(String scanPackage){
  String scanPackagePath = Thread.currentThread()
    .getContextClassLoader()
    .getResource("").getPath() 
    + scanPackage.replaceAll("\\.","/");
  File[] files = pack.listFiles();
  //子package
  for(File file : files){
    if(File.isDirectory()){
    //递归
    doScan(scanPackage+"."+file.getName());
    } else if(file.getName().endWith(".class")){
      String className = scanPackage + "." 
        + fileName.getName.replaceAll(".class","");
      classNames.add(className);
    }
  }
}
ioc 容器

基于classNames缓存的类的全限定类名,以及反射技术 ,完成对象创建和管理

private void doInstance(){
  if(className.size() == 0 ) return;
  try{
    for(int i = 0 ; i<classNames.size();i++){
      //com.my.demo.controller.DemoController
      String className = classNames.get(i);
      //反射
      Class<?> aClass = Class.forName(className);
      //区分controller , service
      if(aClass.isAnnotationPresent(MyController.calss)){
          //controller 的id此处不做额外处理,未取用value,就拿类首字母小写作为小写保存到ioc中
          String simpleName = aClass.getSimpleName();//DemoController
          String lowerFirstSimpleName = LowerFirst(simpleName);//demoController
          Object o  = aClass.newInstance();
          ioc.put(lowerFirstSimpleName,o);
      } else if (aClass.isAnnotationPresent(MyService.class)){
          MyService annotation = aClass.getAnnotation(MyService.class);
          //获取注解value值
	     String beanName = annotation.value();
          //如果指定了id,就以指定的为准
          if(StringUtils.isE(beanName.trim())) {
              ioc.put(beanName,aClass.newInstance());
          }else{
              //如果没有指定则以首字母小写为名
              beanName = lowerFirst(aClass.getSimpleName());
              ioc.put(beanName, aClass.newInstance());
          }
        
          //service一般都有接口,所以再放置一份以接口名为beanName的ioc对象
          Class<?> interfaces = aClass.getInterfaces();
          interfaces.forEach(e -> {
              ioc.put(e.getName(),aClass.newInstance());
          });
      }catch(Exception e){
          e.printStackTrace();
      }
    }
  }
}

首字母小写方法

// 首字母小写方法
    public String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }
实现依赖注入 doAutoWired()
private void doAutoWired(){
    if(ioc.isEmpty()){
        retrun;
    }
	//遍历ioc中所有对象,如果有AutoWried注解则维护依赖
    for(Map.Entry<String,Object> entry: ioc.entrySet()){
        //获取bean中字段信息
        Field[] declaredFields = entry.getValue().getClass.getDeclaredFields();
        //遍历判断处理
	   for (int i = 0; i < declaredFields.length; i++) {
           Field declaredField = declaredFields[i];   //  @MyAutowired  private IDemoService demoService;
           if (!declaredField.isAnnotationPresent(MyAutowired.class)) {
               continue;
           }
           //有注解
           MyAutowired annotation = declareField.getAnnotation(MyAutowired.calss);
           String beanName = annotation.value();//需要注入bean的id
           //如果没有配置value
           if(StringUtil.isE(banName.trim())){
               // 没有配置具体的bean id ,就需要根据当前字段类型注入(接口注入)
               beanName = declaredField.getType().getName();
           }
           
           //开始赋值
           declaredField.setAccessible(true);
           try{
               declaredField.set(entry.getValue(),ioc.get(beanName));
           }catch(IllegalAccessException e){
               e.pringStackTrace();
           }
       }
    }
}
**绑定Url与Handler的关系:initHandlerMapping **
  • 构造一个HandlerMapping处理器映射器
  • 将url与method建立关联
private void initHandlerMapping(){
    if(ioc.isEEmpty()){
        return;
    }
    for(Map.Entry<String,Object> entry : ioc.entrySet()){
        //获取ioc中当前遍历的对象class类型
        Class<?> aClass = entry.getValue().getClass();
        
        if(!aClass.isAnnotationPresent(MyController.class)){
            continue;
        }
		String baseUrl = "";
        if(aClass.isAnnotationPresent(MyRequestMapping.class)){
            MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class);
            baseUrl = annotation.value();// 等同于/demo
        }
        
        //获取方法
        Method[] methods = aClass.getMethod();
        for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];

                //  方法没有标识myRequestMapping,就不处理
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }

                // 如果标识,就处理
                MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                String methodUrl = annotation.value();  // /query
                String url = baseUrl + methodUrl;    // 拼接的url /demo/query

                // 把method所有信息及url封装为一个Handler
                Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url));

			    // 计算方法的参数位置信息 
            	// query(HttpServletRequest request, HttpServletResponse response,String name)
                Parameter[] parameters = method.getParameters();
                for (int j = 0; j < parameters.length; j++) {
                    Parameter parameter = parameters[j];
                    if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                        // 如果是request和response对象,那么参数名称写HttpServletRequest和HttpServletResponse
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), j);
                    } else {
                        handler.getParamIndexMapping().put(parameter.getName(), j);  // <name,2>
                    }

                }

                // 建立url和method之间的映射关系(map缓存起来)
                handlerMapping.add(handler);

            }
    }
    
}
处理请求


    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()) {
                continue;
            }
            return handler;
        }
        return null;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    /*       处理请求:根据url,找到对应的Method方法,进行调用
           获取uri
          String requestURI = req.getRequestURI();
          Method method = handlerMapping.get(requestURI);// 获取到一个反射的方法
           反射调用,需要传入对象,需要传入参数,此处无法完成调用,没有把对象缓存起来,也没有参数!!!!改造initHandlerMapping();
          method.invoke()
         根据uri获取到能够处理当前请求的hanlder(从handlermapping中(list))*/
        Handler handler = getHandler(req);

        if (handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }

        // 参数绑定
        // 获取所有参数类型数组,这个数组的长度就是我们最后要传入的args数组的长度
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();


        // 根据上述数组长度创建一个新的数组(参数数组,是要传入反射调用的)
        Object[] paraValues = new Object[parameterTypes.length];

        // 以下就是为了向参数数组中塞值,而且还得保证参数的顺序和方法中形参顺序一致

        Map<String, String[]> parameterMap = req.getParameterMap();

        // 遍历request中所有参数  (填充除了request,response之外的参数)
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            // name=1&name=2   name [1,2]
            String value = StringUtils.join(param.getValue(), ",");  // 如同 1,2

            // 如果参数和方法中的参数匹配上了,填充数据
            if (!handler.getParamIndexMapping().containsKey(param.getKey())) {
                continue;
            }
            // 方法形参确实有该参数,找到它的索引位置,对应的把参数值放入paraValues
            Integer index = handler.getParamIndexMapping().get(param.getKey());//name在第 2 个位置
            paraValues[index] = value;  // 把前台传递过来的参数值填充到对应的位置去
        }

        int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); // 0
        paraValues[requestIndex] = req;

        int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName()); // 1
        paraValues[responseIndex] = resp;

        // 最终调用handler的method属性
        try {
            handler.getMethod().invoke(handler.getController(), paraValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


    }

Pojo包装类
/**
 * 封装handler方法相关的信息
 */
@Data
public class Handler {
		// method.invoke(obj,)
    private Object controller; 
    private Method method;
	  // spring中url是支持正则的
    private Pattern pattern; 
  	// 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2>
    private Map<String,Integer> paramIndexMapping; 
  
    public Handler(Object controller, Method method, Pattern pattern) {
        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
        this.paramIndexMapping = new HashMap<>();
    }
}
DemoController
@MyController
@MyRequestMapping("/demo")
public class DemoController {

    @MyAutowired
    private IDemoService demoService;


    /**
     * URL: /demo/query?name=lisi
     * @param request
     * @param response
     * @param name
     * @return
     */
    @MyRequestMapping("/query")
    public String query(HttpServletRequest request, HttpServletResponse response,String name) {
        return demoService.get(name);
    }
}

IDemoService & IdemoServiceImpl
package com.my.demo.service;

public interface IDemoService {

    String get(String name);
}


-----------------------------------------------------------------------
    

package com.my.demo.service.impl;

import com.my.demo.service.IDemoService;
import com.my.edu.mvcframework.annotations.MyService;

@MyService("demoService")
public class DemoServiceImpl implements IDemoService {
    @Override
    public String get(String name) {
        System.out.println("service 实现类中的name参数:" + name) ;
        return name;
    }
}



附录


乱码问题解决

Post请求乱码

web.xml 中加入过滤器

  <!--springmvc提供的针对post请求的编码过滤器-->
  <filter>
    <filter-name>encoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值