学到微笑之 - 自定义 MVC 框架

    科技2024-05-26  75

    没啥想说的,就放个表情包吧

    1 Spring MVC 执行流程

    2 MVC 框架注解开发

    自定义注解

    (1)MyController

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

    (2)MyService

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

    (3)MyAutowired

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

    (4)MyRequestMapping

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

    3 MVC 框架流程结构开发

    前端控制器 MyDispatcherServlet

    private Properties properties = new Properties(); // 缓存扫描到的类的全限定类名 private List<String> classNames = new ArrayList<>(); // ioc容器 private Map<String, Object> ioc = new HashMap<String, Object>(); // 存储url和Method之间的映射关系 private List<Handler> handlerMapping = new ArrayList<>(); public void init(ServletConfig config) { // 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 初始化完成...."); // 6 等待请求进入,处理请求 }

    web.xml

    <web-app> <servlet> <servlet-name>lgoumvc</servlet-name> <servlet-class>com.lagou.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> </web-app>

    springmvc.properties

    scanPackage=com.demo

    (1)加载配置文件

    private void doLoadConfig(String contextConfigLocation) { InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { properties.load(resourceAsStream); } catch (IOException e) { e.printStackTrace(); } }

    (2)扫描相关类,扫描注解

    // 扫描磁盘路径 private void doScan(String scanPackage) { // 将 com.demo 转为 com/demo String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/"); File pack = new File(scanPackagePath); File[] files = pack.listFiles(); for (File file : files) { // 子package,是目录则继续递归 if (file.isDirectory()) { // 递归 com.demo.controller doScan(scanPackage + "." + file.getName()); } else if (file.getName().endsWith(".class")) { // 获取全限定类名 String className = scanPackage + "." + file.getName().replaceAll(".class", ""); classNames.add(className); } } }

    (3)初始化 Bean 对象

    /** * 初始化 bean * 基于classNames缓存的类的全限定类名,以及反射技术,完成对象创建和管理 */ private void doInstance() { if (classNames.size() == 0) return; try { for (int i = 0; i < classNames.size(); i++) { // com.controller.DemoController String className = classNames.get(i); // 反射 Class<?> aClass = Class.forName(className); // 区分controller,区分service' if (aClass.isAnnotationPresent(MyController.class)) { // controller的id此处不做过多处理,不取value了,就拿类的首字母小写作为id,保存到ioc中 // DemoController String simpleName = aClass.getSimpleName(); // demoController String lowerFirstSimpleName = lowerFirst(simpleName); Object o = aClass.newInstance(); ioc.put(lowerFirstSimpleName, o); } else if (aClass.isAnnotationPresent(MyService.class)) { LagouService annotation = aClass.getAnnotation(LagouService.class); //获取注解value值 String beanName = annotation.value(); // 如果指定了id,就以指定的为准 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 (int j = 0; j < interfaces.length; j++) { Class<?> anInterface = interfaces[j]; // 以接口的全限定类名作为id放入 ioc.put(anInterface.getName(), aClass.newInstance()); } } else { continue; } } } catch (Exception e) { e.printStackTrace(); } }

    (4)实现依赖注入

    private void doAutoWired() { if (ioc.isEmpty()) { return; } // 有对象,再进行依赖注入处理 // 遍历ioc中所有对象,查看对象中的字段,是否有@MyAutowired注解,如果有需要维护依赖注入关系 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]; if (!declaredField.isAnnotationPresent(MyAutowired.class)) { continue; } // 有该注解 // @MyAutowired("") private IDemoService demoService; MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class); String beanName = annotation.value(); // 需要注入的bean的id if ("".equals(beanName.trim())) { // 没有配置具体的bean id,那就需要根据当前字段类型注入(接口注入) IDemoService beanName = declaredField.getType().getName(); } // 开启赋值 declaredField.setAccessible(true); try { declaredField.set(entry.getValue(), ioc.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }

    (5)构造一个HandlerMapping处理器映射器,将配置好的url和Method建立映射关系

    /* 构造一个HandlerMapping处理器映射器 最关键的环节 目的:将url和method建立关联 */ private void initHandlerMapping() { if (ioc.isEmpty()) { 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(); } // 获取方法 Method[] methods = aClass.getMethods(); 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(); // 计算出来的url /demo/query String url = baseUrl + methodUrl; // 建立url和method之间的映射关系(map缓存起来) // private Map<String,Method> handlerMapping = now HashMap<>(); handlerMapping.put(url,method); } } }

    存在问题:只保存了方法,没有保存参数,没有保存具体的对象(不知道是哪个对象的方法),无法完成调用

    (6)优化 - Handle封装引入

    将 controller,要调用的 method,方法的参数,url 封装在一个类里

    public class Handler { // method.invoke(对象,) private Object controller; private Method method; // spring中url是支持正则的 private Pattern pattern; // 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2> private Map<String,Integer> paramIndexMapping; } // 获取方法 Method[] methods = aClass.getMethods(); 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)); // 计算方法的参数位置信息 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); } } // 建立url和method之间的映射关系(map缓存起来) handlerMapping.add(handler); }

    (7)等待请求进入,处理请求

    根据 url 获取 handle

    // 根据uri获取到能够处理当前请求的hanlder(从handlermapping中(list)) private Handler getHandler(HttpServletRequest req) { if (handlerMapping.isEmpty()) { return null; } // 从request中获取url String url = req.getRequestURI(); for (Handler handler : handlerMapping) { Matcher matcher = handler.getPattern().matcher(url); if (!matcher.matches()) { continue; } return handler; } return null; }

    从request获取参数值,根据相应位置保存到数组

    @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 根据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 // 如果参数和方法中的参数匹配上了,填充数据 Map<String,Integer> paramIndexMapping; // 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2> if (!handler.getParamIndexMapping().containsKey(param.getKey())) { continue; } // 方法形参确实有该参数,找到它的索引位置,对应的把参数值放入paraValues Integer index = handler.getParamIndexMapping().get(param.getKey());//name在第 2 个位置 paraValues[index] = value; // 把前台传递过来的参数值填充到对应的位置去 } // 获取 HttpServletRequest int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); // 0 paraValues[requestIndex] = req; // 获取 HttpServletResponse 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(); } }

     

     

     

    Processed: 0.009, SQL: 8