笔记4:手写实现简单的MVC

1. 首先创建注解类

	@Documented
	@Target(ElementType.TYPE)
	@Retention(RetentionPolicy.RUNTIME)
	public @interface MyController {
    
    
	    String value() default "";
	}
	@Documented
	@Target(ElementType.TYPE)
	@Retention(RetentionPolicy.RUNTIME)
	public @interface MyService {
    
    
	    String value() default "";
	}
	@Documented
	@Target(ElementType.FIELD)
	@Retention(RetentionPolicy.RUNTIME)
	public @interface MyAutowired {
    
    
	    String value() default "";
	}
	@Documented
	@Target({
    
    ElementType.TYPE,ElementType.METHOD})
	@Retention(RetentionPolicy.RUNTIME)
	public @interface MyRequestMapping {
    
    
	    String value() default "";
	}

2. 创建MyDispatcherServlet.java类继承HttpServlet

	private static final Properties PROPERTIES = new Properties();

    /**
     * 缓存扫描到的类的全限定类名
     */
    private static final List<String> CLASS_NAME_LIST = new ArrayList<>();

    /**
     * IOC 容器
     */
    private static final Map<String, Object> IOC = new HashMap<>(16);

    private static final List<Handler> HANDLER_MAPPING = new ArrayList<>();

    @Override
    public void init(ServletConfig config) {
    
    
        // 1 加载配置文件
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        // 2 扫描相关的类,扫描注解
        doScan(PROPERTIES.getProperty("scanPackage"));

        // 3 初始化bean对象(实现ioc容器,基于注解)
        doInstance();

        // 4 实现依赖注入
        doAutoWired();

        // 5 构造一个HandleMapping处理器映射器,建立映射关系
        initHandlerMapping();

        System.out.println("mvc 初始化完成");

        // 等待请求进入,处理请求

    }

	/**
     * 首字母小写
     */
    private String lowerFirst(String simpleName) {
    
    
        if(isNotEmpty(simpleName)) {
    
    
            char[] chars = simpleName.toCharArray();
            if('A' <= chars[0] && 'Z' >= chars[0]) {
    
    
                chars[0] += 32;
            }

            simpleName = String.valueOf(chars);
        }

        return simpleName;
    }

    private boolean isNotEmpty(String s) {
    
    
        boolean flag = false;
        if(s != null && !"".equals(s.trim())) {
    
    
            flag = true;
        }

        return flag;
    }

这里Handler实体用于储存解析出来的信息,在最后requestMapping里会用到
实体如下:

	/**
	 * 封装handler方法相关的信息
	 */
	public class Handler {
    
    
	
	    private Object controller; // method.invoke(obj,)
	
	    private Method method;
	
	    private Pattern pattern; // spring中url是支持正则的
	
	    private Map<String,Integer> paramIndexMapping; // 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2>
	
	
	    public Handler(Object controller, Method method, Pattern pattern) {
    
    
	        this.controller = controller;
	        this.method = method;
	        this.pattern = pattern;
	        this.paramIndexMapping = new HashMap<>();
	    }
	
	    public Object getController() {
    
    
	        return controller;
	    }
	
	    public void setController(Object controller) {
    
    
	        this.controller = controller;
	    }
	
	    public Method getMethod() {
    
    
	        return method;
	    }
	
	    public void setMethod(Method method) {
    
    
	        this.method = method;
	    }
	
	    public Pattern getPattern() {
    
    
	        return pattern;
	    }
	
	    public void setPattern(Pattern pattern) {
    
    
	        this.pattern = pattern;
	    }
	
	    public Map<String, Integer> getParamIndexMapping() {
    
    
	        return paramIndexMapping;
	    }
	
	    public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
    
    
	        this.paramIndexMapping = paramIndexMapping;
	    }
	}

一共分为5步

  1. 加载配置文件
  2. 扫描相关的类,扫描注解
  3. 初始化bean对象(实现ioc容器,基于注解)
  4. 实现依赖注入
  5. 构造一个HandleMapping处理器映射器,建立映射关系

1到4步和手写Spring自定义注解类的思路一样 代码入下:

  1. 加载配置文件
	/**
     * 加载配置文件
     * @param contextConfigLocation 路径
     */
    private void doLoadConfig(String contextConfigLocation) {
    
    
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
    
    
            PROPERTIES.load(resourceAsStream);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }
  1. 扫描相关的类,扫描注解
	/**
     * 扫描类
     * scanPackage: com.lossdate.demo  package---->  磁盘上的文件夹(File)  com/lossdate/demo
     */
    private void doScan(String scanPackage) {
    
    
        String path = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).getPath() + scanPackage.replaceAll("\\.", "/");
        File packages = new File(path);
        File[] files = packages.listFiles();
        for (File file : Objects.requireNonNull(files)) {
    
    
            if(file.isDirectory()) {
    
    
                // com.lossdate.demo.controller
                doScan(scanPackage + "." + file.getName());
            } else if(file.getName().endsWith(".class")) {
    
    
                CLASS_NAME_LIST.add(scanPackage + "." + file.getName().replaceAll(".class", ""));
            }
        }
    }
  1. 初始化bean对象(实现ioc容器,基于注解)
	/**
     * IOC容器
     */
    private void doInstance() {
    
    
        if(CLASS_NAME_LIST.size() > 0) {
    
    
            CLASS_NAME_LIST.forEach(className -> {
    
    
                //com.lossdate.demo.controller.DemoController
                try {
    
    
                    Class<?> aClass = Class.forName(className);
                    // DemoController
                    String simpleName = aClass.getSimpleName();
                    // 首字母转小写 demoController
                    String lowerSimpleName = lowerFirst(simpleName);
                    // 区分controller,区分service'
                    if(aClass.isAnnotationPresent(MyController.class)) {
    
    
                        // controller的id此处不做过多处理,就拿类的首字母小写作为id,保存到ioc中
                        Object o = aClass.newInstance();
                        IOC.put(lowerSimpleName, o);
                    } else if(aClass.isAnnotationPresent(MyService.class)) {
    
    
                        Object o = aClass.newInstance();
                        MyService annotation = aClass.getAnnotation(MyService.class);
                        //获取注解value值 如果指定了id,就以指定的为准
                        if(isNotEmpty(annotation.value())) {
    
    
                            lowerSimpleName = annotation.value();
                        }
                        IOC.put(lowerSimpleName, o);

                        // service层往往是有接口的,面向接口开发,此时再以接口名为id,放入一份对象到ioc中,便于后期根据接口类型注入
                        Class<?>[] interfaces = aClass.getInterfaces();
                        for (Class<?> anInterface : interfaces) {
    
    
                            // 以接口的全限定类名作为id放入
                            IOC.put(anInterface.getName(), o);
                        }
                    }
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }

            });
        }
    }
  1. 实现依赖注入
	/**
     * 实现依赖注入
     */
    private void doAutoWired() {
    
    
        // 有对象,再进行依赖注入处理
        if(!IOC.isEmpty()) {
    
    
            // 遍历ioc中所有对象,查看对象中的字段,是否有@MyAutowired注解,如果有需要维护依赖注入关系
            IOC.forEach((key, value) -> {
    
    
                // 获取bean对象中的字段信息
                Field[] declaredFields = value.getClass().getDeclaredFields();
                // 遍历判断处理
                for (Field declaredField : declaredFields) {
    
    
                    //  @Autowired
                    MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class);
                    String beanName = declaredField.getType().getName();
                    // 有该注解
                    if(isNotEmpty(annotation.value())) {
    
    
                        beanName = annotation.value();
                    }

                    // 开启赋值
                    declaredField.setAccessible(true);
                    try {
    
    
                        declaredField.set(value, IOC.get(beanName));
                    } catch (IllegalAccessException e) {
    
    
                        e.printStackTrace();
                    }
                }
            });
        }
    }

第五步:构造一个HandleMapping处理器映射器,建立映射关系
这里要将类,方法,url, 参数在方法中的顺序解析存储到Handler中存储,用于doPost时的匹配。
Handler参数有:

  1. Object controller
  2. Method method
  3. Pattern pattern
  4. Map<String,Integer> paramIndexMapping
    其中类的存储时用于方法的invoke,url用了Pattern方便后面的正则匹配。
	/**
     * 构造一个HandleMapping处理器映射器
     * 将url和method建立关联
     */
    private void initHandlerMapping() {
    
    
        if(!IOC.isEmpty()) {
    
    
            IOC.forEach((key, value) -> {
    
    
                // 获取ioc中当前遍历的对象的class类型
                Class<?> aClass = value.getClass();
                if(aClass.isAnnotationPresent(MyController.class)) {
    
    
                    String baseUrl = "";
                    if(aClass.isAnnotationPresent(MyRequestMapping.class)) {
    
    
                        MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class);
                        // 等同于/demo
                        baseUrl = annotation.value();
                    }

                    // 获取方法
                    Method[] methods = aClass.getMethods();
                    for (Method method : methods) {
    
    
                        //  方法没有标识MyRequestMapping,就不处理
                        if(method.isAnnotationPresent(MyRequestMapping.class)) {
    
    
                            MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                            // 计算出来的url /demo/query
                            String url = baseUrl + annotation.value();

                            //生成handler
                            Handler handler = new Handler(value, method, Pattern.compile(url));
                            //获取方法需要的参数
                            Parameter[] parameters = method.getParameters();
                            //记录参数位置信息
                            for (int i = 0; i < parameters.length; i++) {
    
    
                                Parameter parameter = parameters[i];
                                if(parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
    
    
                                    //参数为request或response
                                    handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                                } else {
    
    
                                    //普通参数
                                    handler.getParamIndexMapping().put(parameter.getName(), i);
                                }
                            }

                            HANDLER_MAPPING.add(handler);
                        }
                    }
                }
            });
        }
    }

3. 初始化完成mvc后,进行get和post的请求处理

1. get
这里直接调post

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

2. post
分为三步
1. 获取handler
2. 处理方法的参数及位置
3. handler的method属性调用方法
代码如下:

	@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        //1. 获取handler
        Handler handler = getHandler(req);

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

        //2. 处理方法的参数及位置
        //获取方法参数长度,创建参数数组
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        Object[] params = new Object[parameterTypes.length];

        //获取普通传参
        Map<String, String[]> parameterMap = req.getParameterMap();
        //匹配参数
        parameterMap.forEach((key, arr) -> {
    
    
            // name=1&name=2   name [1,2] -> 如同 1,2
            String paramValue = StringUtils.join(arr, ",");
            //获取位置
            Integer index = handler.getParamIndexMapping().get(key);
            if(index != null) {
    
    
                params[index] = paramValue;
            }
        });

        //HttpServletRequest
        Integer reqIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        if(reqIndex != null) {
    
    
            params[reqIndex] = req;
        }
        //HttpServletRequest
        Integer respIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        if(respIndex != null) {
    
    
            params[respIndex] = resp;
        }

        //3. handler的method属性调用方法
        try {
    
    
            handler.getMethod().invoke(handler.getController(), params);
        } catch (IllegalAccessException | InvocationTargetException e) {
    
    
            e.printStackTrace();
        }
    }

	/**
     * 获取handler
     */
    private Handler getHandler(HttpServletRequest req) {
    
    
        if(!HANDLER_MAPPING.isEmpty()) {
    
    
            for (Handler handler : HANDLER_MAPPING) {
    
    
                Matcher matcher = handler.getPattern().matcher(req.getRequestURI());
                if(matcher.matches()) {
    
    
                    return handler;
                }
            }
        }

        return null;
    }

4. 配置文件

  1. springmvc.properties
scanPackage=com.lossdate.demo
  1. web.xml
  <servlet>
    <servlet-name>mymvc</servlet-name>
    <servlet-class>com.lossdate.edu.mvcframework.servlet.MyDispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>springmvc.properties</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>mymvc</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

猜你喜欢

转载自blog.csdn.net/Lossdate/article/details/111876710
今日推荐