• 手动写个类似的Spring MVC框架试试


    Spring MVC是个经得起考验的一个成熟框架,在企业内也是一个很多开发者选择的框架。

    SpringMVC是一种基于Java,实现了Web MVC设计模式,请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将Web层进行职责解耦。基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,SpringMVC也是要简化我们日常Web开发。

    MVC设计模式

    MVC设计模式的任务是将包含业务数据的模块与显示模块的视图解耦。这是怎样发生的?在模型和视图之间引入重定向层可以解决问题。此重定向层是控制器,控制器将接收请求,执行更新模型的操作,然后通知视图关于模型更改的消息。

    概念很复杂,不管他先,但是我们现在知道Spring MVC通过注解可以分发请求。我们从这里入手,大概思路就是,一个HttpServlet接受所有的请求,然后根据路由,判断具体通知那个方法执行,从而达到类似的功能。

    OK,那么我们现在首先要有一个接受所有请求的HttpServlet,我们定义一个BaseServlet,并继承HttpServlet;

    /**
     * 请求访问入口(所有请求都会来到这里进行分发)
     * @author zhanglr
     *
     */
    @WebServlet(name = "BaseServlet", urlPatterns = "/")
    public final class BaseServlet extends HttpServlet {
    
        private static final long serialVersionUID = 1L;
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            super.service(req, resp);
    
            }
        }
    }

    我们先不去管service()的具体实现,我们现在有一个总的HttpServlet了,所有请求都会被拦截在这里,那么具体通知哪些方法呢,首先,我们需要有注解,类似Spring MVC的@RequestMapping、@PostMapping等;

    那么我们现在定义两个注解,一个是定义在类上的注解,一个是定义具体方法的注解;

    /**
     * 类注解映射,映射使用的类
     * 
     * @author zhanglr
     *
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE })
    public @interface RequestType {
        String url();
    
        /**
         * 请求方式,get或post,大小写不敏感
         * @return
         */
        String type();
    }
    /**
     * 方法注解映射,映射调用的方法
     * @author zhanglr
     *
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface RequestMethod {
        String name() default "";
    }

    好了,我们现在有两个注解,一个是注解在类上的,也是路由请求地址的前一段地址,我们以模块来区分;第二个注解是注解在方法上的,也就是路由请求地址的后面一段地址,两段地址的意义我是用 【模块/功能】来组合;

    解析:@RequestType中有两个属性,type是请求方式;url是路由地址,也是上面所有我习惯用的模块地址;

    Ok,我们现在将这两个注解用起来,类似Spring MVC,我们先来创建一个UserController;

    /**
     * 用户逻辑控制器
     * @author zhanglr
     *
     */
    @RequestType(type = "POST", url = "/user")
    public class UserController {
    
        private UserService userService = new UserService();
        
        @RequestMethod(name = "/login.do")
        public IActionResult login(HttpServletRequest request, HttpServletResponse response) {
            // 登录逻辑
            return ErrorCodeResponse.Success.getSuccess("success");
        }
        
        
        /**
         * 用户注册
         * @param request
         * @param response
         * @return
         */
        @RequestMethod(name = "/register.do")
        public IActionResult registerUser(HttpServletRequest request, HttpServletResponse response) {
            UserRegisterRequest userRegisterRequest = ClassUtil.getObjectFromResquest(request, UserRegisterRequest.class);
            if (Types.isEmpty(userRegisterRequest.getAccount())) {
                return ErrorCodeResponse.AccountIsNull.getError();
            }
            if (Types.isEmpty(userRegisterRequest.getPassword())) {
                return ErrorCodeResponse.PasswordIsNull.getError();
            }
            if (Types.isEmpty(userRegisterRequest.getAgainPwd())) {
                return ErrorCodeResponse.ParamIsNull.getError("二次密码");
            }
            return userService.registerUser(userRegisterRequest);
        }
    )

    以上新建了一个用户逻辑控制器,@RequestType即模块功能地址为“/user”,请求方式为“Post”,用户类中定义了两个方法,分别是登录注册,@RequestMethod分别注解在两个方法中,类似登录功能即@RequestMethod(name = "/login")。那么我们预想中的请求登录地址就是“/user/login.do”,同样,注册地址就是“/user/register.do”;

    OK,我们现在有SpringMVC的Controller了。其中有一些工具类待会再贴出来吧。

    返回我们的BaseServlet,现在需要思考的就是接受到的请求,怎样准备的通知对应的方法,我们通过请求地址匹配注解上的内容即可;

    /**
     * 请求访问入口(所有请求都会来到这里进行分发)
     * @author zhanglr
     *
     */
    @WebServlet(name = "BaseServlet", urlPatterns = "/")
    public final class BaseServlet extends HttpServlet {
    
        private static final long serialVersionUID = 1L;
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            super.service(req, resp);
            req.setCharacterEncoding("utf-8");
            resp.setContentType("text/html");
            resp.setCharacterEncoding("utf-8");
            String contextPath = req.getContextPath();
            String requestUri = req.getRequestURI();
            // 去除contextPath为真实请求路径
            String realUri = requestUri.substring(contextPath.length(), requestUri.length());
            // 获取入口包下的所有controller类
            List<Class<?>> classes = ClassUtil.getAllClassByPackageName(this.getClass().getPackage());
            for (Class<?> currClass : classes) {
                // 获取当前类的注解
                RequestType requestType = currClass.getAnnotation(RequestType.class);
                String methodType = req.getMethod().toLowerCase().trim();
                // 当前类注解不为空,请求路径以该注解url开头且请求类型为注解type则获取该类
                if (requestType != null && requestType.type().toLowerCase().trim().equals(methodType)
                        && realUri.contains(requestType.url()) && realUri.startsWith(requestType.url())) {
                    // 获取当前类的所有方法
                    Method[] methods = currClass.getDeclaredMethods();
                    for (Method currMethod : methods) {
                        // 获取当前方法的注解
                        RequestMethod requestMethod = currMethod.getAnnotation(RequestMethod.class);
                        // 当前方法注解不为空且请求路径以该注解name结尾,则获取该方法
                        if (requestMethod != null && realUri.contains(requestMethod.name()) && realUri.endsWith(requestMethod.name())) {
                            try {
                                IActionResult result = (IActionResult) currMethod.invoke(currClass.newInstance(), req, resp);
                                if (result != null && result.getData() != null) {
                                    resp.getWriter().print(result.getData());
                                    resp.getWriter().flush();
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            break;
                        }
                    }
                    break;
                }
            }
        }
    }

    在Spring MVC中,我们可以通过配置找到对应的包下的Controller类,我们这里就不做这个了,将Controller跟BaseServlet置于同一个包目录下,在同一个包目录下进行查找,具体的实现上面注释也写的挺详细的,

    就不在多解释了。

    然后,我们来看看其中的一些小细节吧,我将返回的数据封装到一个接口内,IActionResult接口为:

    public interface IActionResult {
        String getData();
    }

    我们Controller类都返回该接口的实现即可,类似我写了个公用的返回数据类;

    public enum ErrorCodeResponse implements IActionResult {
    
        Success(0), Error(1),
        // 公共错误 10000+
        ParamIsNull(10001, " %s 不能为空  "), AccountIsNull(10002, "用户账号不能为空"), PasswordIsNull(10003, "用户密码不能为空");
    
        private int code;
        private String msg;
    
        private ErrorCodeResponse(int code) {
            this.code = code;
        }
    
        private ErrorCodeResponse(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public IActionResult getError() {
            return this;
        }
    
        public IActionResult getError(String param) {
            this.msg = String.format(this.msg, param);
            return this;
        }
    
        public IActionResult getError(int code, String param) {
            this.code = code;
            this.msg = param;
            return this;
        }
    
        public IActionResult getSuccess(String param) {
            this.msg = param;
            return this;
        }
    
        // 拓展参数value
        private Object data = null;
    
        public ErrorCodeResponse setExtraData(Object data) {
            this.data = data;
            return this;
        }
    
        @Override
        public String getData() {
            Gson gson = new Gson();
            Map<String, Object> params = new HashMap<String, Object>();
            params.put("code", this.code);
            params.put("msg", this.msg);
            if (Types.isNotEmpty(data)) {
                if (data instanceof String) {
                    params.put("data", data);
                } else {
                    params.put("data", gson.toJson(data));
                }
            } else {
                params.put("data", "");
            }
            return gson.toJson(params);
        }
    
    }

    通过这个工具类,我们返回的结果会以JSON字符串进行返回;

    上面还有个ClassUtil工具类,其实可以封装到BaseServlet里面,就像是Spring MVC那样,Controller的参数可以是一个JavaBean,但是我没去搞了,写这个也是闲的蛋疼而已。

    public final class ClassUtil {
    /**
         * 将request请求数据转换为实体
         * @param request
         * @param clazz
         * @return
         */
        public static <T> T getObjectFromResquest(HttpServletRequest request, Class<T> clazz) {
    
            try {
                T t = clazz.newInstance();
                Map<String, Method> methods = new HashMap<>();
                for (Method method : clazz.getMethods()) {
                    methods.put(method.getName(), method);
                }
                Enumeration<String> keys = request.getParameterNames();
                while (keys.hasMoreElements()) {
                    String key = (String) keys.nextElement();
                    String value = request.getParameter(key);
                    if (value.isEmpty())
                        continue;
                    String fieldName = key.substring(0, 1).toUpperCase() + key.substring(1);
                    Method setMethod = methods.get("set" + fieldName);
                    if (setMethod == null)
                        continue;
                    invokeSetMethod(t, setMethod, value);
                }
                return t;
            } catch (InstantiationException | IllegalAccessException | SecurityException | IllegalArgumentException
                    | InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private static void invokeSetMethod(Object obj, Method method, Object value)
                throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            Class<?>[] types = method.getParameterTypes();
            if (types != null && types.length == 1) {
                Class<?> typeClass = types[0];
                if (value.getClass() == typeClass) {
                    method.invoke(obj, value);
                    return;
                }
                if (typeClass == String.class)
                    method.invoke(obj, String.valueOf(value));
                else if (typeClass == Integer.class || typeClass == int.class) {
                    method.invoke(obj, Integer.valueOf(String.valueOf(value)));
                } else if (typeClass == Long.class || typeClass == long.class) {
                    method.invoke(obj, Long.valueOf(String.valueOf(value)));
                }
            }
        }
    }

    以上,就大概就是所有的流程,这样的话,我们就不用像使用HttpServlet那样,每个请求都新建一个Servlet了,通过注解,我们可以很方便的将每一个请求写好;

    大概是闲的蛋疼吧,认真的态度随便的心情写的,多多指教,不喜勿喷。

  • 相关阅读:
    实时控制软件设计-读书笔记
    1月9号
    第一组咖啡机器人部分图形设计
    《构建之法:现代软件工程》阅读笔记
    《实时控制软件设计》第三周作业
    实时控制软件设计第二次作业
    《实时控制软件设计》团队项目个人总结
    《实时控制软件设计》团队项目第三天工作日志
    《实时控制软件设计》团队项目第三天工作日志
    《实时控制软件设计》团队项目第二天工作日志
  • 原文地址:https://www.cnblogs.com/Jhon-Mr/p/9634024.html
Copyright © 2020-2023  润新知