架构师内功心法-----手写springv2.0mvc

手写springv2.0ioc与di


简介

springmvc中有九大组件
MultipleResolver、 LocalResolver、ThemeResolver、HandlerMapping、HandlerAdapter、HandlerExceptionResolver、RequestToViewNameTranstor、ViewResolver、FlashMapping

接上文,ioc与di过程已经处理完成,接下来就是mvc部分,mvc需要初始化九大组件,此处挑选三个组件进行初始化,包括HandlerMapping、HandlerAdapter、ViewResolver。

主要类如下:HandlerMapping、HandlerAdapter、ModelAndView、ViewResolver、View
流程如下:DispatcherServlet初始化时,先通过ApplicationContext进行ioc和di,而后进行mvc初始化。此处mvc初始化过程包括初始化HandlerMapping(保存控制层方法url和方法的映射关系)、初始化HandlerAdapter(用来反射调用方法并返回ModelAndView)、初始化ViewResolver(一种模板引擎对应一个解析器实例)。客户端进行访问时,通过请求url找到HandlerMapping ,再找到对应的HandlerAdapter,HandlerAdapter根据请求实参反射调用方法返回ModelAndView,ViewResolver对ModelAndView进行解析,找到对应的视图(模板引擎),视图再对页面进行渲染。

mvc

application.properties

scanPackage=com.yjf.spring.demo

#两种模板引擎
templateRoot=layouts
anotherTemplateRoot=templates/

resources目录下,layouts/dir/tes1.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
	<meta charset="utf-8">
	<title>手写mvc</title>
</head>
<center>
	<h3>Hello,My name is €{name}</h3>
	<div>{date} test1</div>
</center>
</html>

resources目录下,layouts/404.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8">
    <title>页面去火星了</title>
</head>
<body>
    <font size='25' color='red'>404 Not Found</font><br/><font color='green'>6</font>
</body>
</html>

resources目录下,layouts/500.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8">
    <title>服务器好像累了</title>
</head>
<body>
    <font size='25' color='blue'>500 服务器好像有点累了,需要休息一下</font><br/>
    <b>Message:{detail}</b><br/>
    <b>StackTrace:{stackTrace}</b><br/>
</body>
</html>

resources目录下,layouts/index.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
	<meta charset="utf-8">
	<title>手写mvc</title>
</head>
<center>
	<h3>Hello,My name is €{ name    }</h3>
	<div>{date}</div>
</center>
</html>

resources目录下,templates/anotherindex.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
	<meta charset="utf-8">
	<title>手写mvc</title>
</head>
<center>
	<h3>Hello,My name is £{ name    },this is another ViewResolver</h3>
	<div>£{date}</div>
</center>
</html>

HandlerMapping 控制层方法的信息

@Data
public class MyHandlerMapping {
    /**
     * 反射调用对象===控制层对象
     **/
    private Object instance;
    /**
     * 反射调用方法===控制层方法
     **/
    private Method method;
    /**
     * 方法的url
     **/
    private Pattern pattern;
    /**
     * 方法的形参数组
     **/
    private Parameter[] parameters;

    public MyHandlerMapping(Object instance, Method method, Pattern pattern) {
        this.instance = instance;
        this.method = method;
        this.pattern = pattern;
        this.parameters = method.getParameters();
    }
}

HandlerAdapter 建立方法形参名和形参位置的关系,并根据实参通过反射调用方法获取ModelAndView

public class MyHandlerAdapter {
    /**
     * 形参名-->形参位置   形参名: 1.@MyRequestParam中的value   2.当前形参的名字
     **/
    private Map<String, Integer> paramIndexMapping = new HashMap<>(3);

    //必须形参位置
    int[]  indexes;

    public MyHandlerAdapter(Parameter[] parameters) {
        indexes = new int[parameters.length];
        Arrays.fill(indexes,-1);

        initParamIndexMapping(parameters);
    }

    /**
     * 初始化paramIndexMapping
     *
     * @return void
     **/
    private void initParamIndexMapping(Parameter[] parameters) {
        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].getType() == HttpServletRequest.class || parameters[i].getType() == HttpServletResponse.class) {
                paramIndexMapping.put(parameters[i].getType().getName(), i);
                continue;
            }

            //获取@MyRequestParam注解的值,若值存在,则paramIndexMapping中键为该值,若不存在,则paramIndexMapping中键为形参参数名
            MyRequestParam param = parameters[i].getAnnotation(MyRequestParam.class);
            if (null == param) {
                paramIndexMapping.put(parameters[i].getName(), i);
            } else if(StringUtil.isEmpty(param.value())){
                paramIndexMapping.put(parameters[i].getName(), i);
                indexes[i] = 1;
            } else {
                paramIndexMapping.put(param.value(),i);
                if(param.required() == true) {
                    indexes[i] = 1;
                }
            }
        }
    }

    /**
     * 反射执行方法并返回ModelAndView
     *
     * @param req
     * @param resp
     * @param handler
     * @return  com.yjf.spring.mvcframework.v2.mvc.servlet.MyModelAndView
     **/
    public MyModelAndView handler(HttpServletRequest req, HttpServletResponse resp, MyHandlerMapping handler) throws InvocationTargetException, IllegalAccessException {
        //取出方法
        Method method = handler.getMethod();
        //形参数组
        Parameter[] parameters = handler.getParameters();
        //实参数组
        Object[] args = new Object[parameters.length];
        Arrays.fill(args,new Object());
        //控制层对象
        Object instance = handler.getInstance();

        //遍历形参,设置实参
        for (Map.Entry<String, Integer> stringIntegerEntry : paramIndexMapping.entrySet()) {
            //形参参数名
            String key = stringIntegerEntry.getKey();
            //参数名对应的位置
            Integer integer = stringIntegerEntry.getValue();
            if (parameters[integer].getType().isArray()) {
                args[integer] = convert(parameters[integer], req.getParameterValues(key));
            } else {
                args[integer] = convert(parameters[integer], req.getParameter(key));
            }
        }

        //req、resp注入
        if (this.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
            args[this.paramIndexMapping.get(HttpServletRequest.class.getName())] = req;
        }
        if (this.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
            args[this.paramIndexMapping.get(HttpServletResponse.class.getName())] = resp;
        }

        //判断必须参数在实参中是否存在
        for (int i = 0; i < indexes.length; i++) {
            int val = indexes[i];
            if(val == 1 && args[i] == null){
                throw new Error(parameters[i].getName() + " is required!");
            }
        }

        Object result = method.invoke(instance, args);
        if(null == result || method.getReturnType() == Void.class || method.getReturnType() == void.class){
            return null;
        }else {
            return (MyModelAndView)result;
        }

    }

    /**
     * 转换实参值的数据类型,当请求中没有形参需要的值时,形参设定初始值避免反射调用异常
     *
     * @param param  形参
     * @param value  实参值
     * @return  java.lang.Object  转换后的实参
     **/
    private Object convert(Parameter param, String... value) {
        if(param.getType().isArray()){
            if (value == null || value.length == 0) {
                return null;
            }else{
                Class<?> componentType = param.getType().getComponentType();
                IConvertStrategy convertStrategy = ConvertStrategyFactory.getConvertStrategy(componentType.getSimpleName());
                Object result = Array.newInstance(componentType, value.length);
                for (int i = 0; i < value.length; i++) {
                    Array.set(result,i,convertStrategy.convertString(value[i]));
                }
                return result;
            }
        }else{
            Class<?> type = param.getType();
            IConvertStrategy convertStrategy = ConvertStrategyFactory.getConvertStrategy(type.getSimpleName());
            if (value == null || value.length == 0) {
                return convertStrategy.convertString(null);
            }else{
                return convertStrategy.convertString(value[0]);
            }
        }
    }
}

ModelAndView 后端需要返回的视图以及需要设置的数据

@Data
public class MyModelAndView {
    private String viewName;
    private Map<String,?> models;

    public MyModelAndView(String viewName) {
        this.viewName = viewName;
    }

    public MyModelAndView(String viewName, Map<String, ?> models) {
        this.viewName = viewName;
        this.models = models;
    }
}

view接口

public interface View {
    /**
     * 视图渲染
     *
     * @param models 后端需要设置的参数
     * @param req
     * @param resp
     * @return  void
     * @throws  IOException
     **/
    void render(Map<String,?> models, HttpServletRequest req, HttpServletResponse resp) throws IOException;
}

ViewResolver接口

public interface ViewResolver {
    /**
     * 根据逻辑视图找到真实视图
     *
     * @param viewName
     * @return  com.yjf.spring.mvcframework.v2.mvc.servlet.View
     **/
    View resolveViewName(String viewName);
}

视图解析器

public class MyViewResolver implements ViewResolver{
    private static final String DEFAULT_TEMPLATE_SUFFIX = ".html";
    private String rootPath;

    public MyViewResolver(String rootPath) {
        //存储模板路径
        this.rootPath = this.getClass().getClassLoader().getResource(rootPath).getPath();
    }

    /**
     * 根据名称找到对应的视图
     *
     * @param viewName
     * @return  com.yjf.spring.mvcframework.v2.mvc.servlet.View
     **/
    @Override
    public View resolveViewName(String viewName){
        if(StringUtil.isEmpty(viewName)){
            return null;
        }

        //添加后缀
        viewName = viewName.endsWith(DEFAULT_TEMPLATE_SUFFIX) ? viewName : (viewName + DEFAULT_TEMPLATE_SUFFIX);

        //拼接模板路径和逻辑视图找寻文件
        String filePath = (rootPath + "/" + viewName).replaceAll("/+","/");
        File file = new File(filePath);
        //判断当前视图是否存在
        if(file.exists()){
            return new MyView(file);
        }else {
            return null;
        }
    }
}

视图解析器

public class MyAnotherViewResolver implements ViewResolver{
    private static final String DEFAULT_TEMPLATE_SUFFIX = ".html";
    private String rootPath;

    public MyAnotherViewResolver(String rootPath) {
        //存储模板路径
        this.rootPath = this.getClass().getClassLoader().getResource(rootPath).getPath();
    }

    /**
     * 根据名称找到对应的视图
     *
     * @param viewName
     * @return  com.yjf.spring.mvcframework.v2.mvc.servlet.View
     **/
    @Override
    public View resolveViewName(String viewName){
        if(StringUtil.isEmpty(viewName)){
            return null;
        }

        //添加后缀
        viewName = viewName.endsWith(DEFAULT_TEMPLATE_SUFFIX) ? viewName : (viewName + DEFAULT_TEMPLATE_SUFFIX);

        //拼接模板路径和逻辑视图找寻文件
        String filePath = (rootPath + "/" + viewName).replaceAll("/+","/");
        File file = new File(filePath);
        //判断当前视图是否存在
        if(file.exists()){
            return new MyAnotherView(file);
        }else {
            return null;
        }
    }
}

具体的视图,对应一个文件,模板引擎

public class MyView implements View{
    private File viewFile;

    public MyView(File viewFile) {
        this.viewFile = viewFile;
    }

    /**
     * 根据后端设置的参数渲染页面
     *
     * @param models
     * @param req
     * @param resp
     * @return  void
     **/
    @Override
    public void render(Map<String,?> models, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(viewFile,"r");

        StringBuilder sb = new StringBuilder();
        String line = "";
        while(null != (line = raf.readLine())){
            line = new String(line.getBytes("iso-8859-1"),"utf-8");

            Pattern regex = Pattern.compile("(?<=€\\{).+?(?=})");
            Matcher matcher = regex.matcher(line);
            while (matcher.find()){
                String res = matcher.group();

                Object val = models.get(res.trim());
                if(null == val){continue;}

                line = line.replaceFirst("€\\{" + res + "}",makeStringForRegExp(val.toString()));
                matcher = regex.matcher(line);
            }
            sb.append(line);
        }

        resp.setCharacterEncoding("utf-8");
        resp.getWriter().write(sb.toString());
    }

    //处理需转义正则字符
    public static String makeStringForRegExp(String str) {
        return str.replace("\\", "\\\\").replace("*", "\\*")
                .replace("+", "\\+").replace("|", "\\|")
                .replace("{", "\\{").replace("}", "\\}")
                .replace("(", "\\(").replace(")", "\\)")
                .replace("^", "\\^").replace("$", "\\$")
                .replace("[", "\\[").replace("]", "\\]")
                .replace("?", "\\?").replace(",", "\\,")
                .replace(".", "\\.").replace("&", "\\&");
    }
}

具体的视图,对应一个文件,模板引擎

public class MyAnotherView implements View{
    private File viewFile;

    public MyAnotherView(File viewFile) {
        this.viewFile = viewFile;
    }

    /**
     * 根据后端设置的参数渲染页面
     *
     * @param models
     * @param req
     * @param resp
     * @return  void
     **/
    @Override
    public void render(Map<String,?> models, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(viewFile,"r");

        StringBuilder sb = new StringBuilder();
        String line = "";
        while(null != (line = raf.readLine())){
            line = new String(line.getBytes("iso-8859-1"),"utf-8");

            Pattern regex = Pattern.compile("(?<=£\\{).+?(?=})");
            Matcher matcher = regex.matcher(line);
            while (matcher.find()){
                String res = matcher.group();

                Object val = models.get(res.trim());
                if(null == val){continue;}

                line = line.replaceFirst("£\\{" + res + "}",makeStringForRegExp(val.toString()));
                matcher = regex.matcher(line);
            }
            sb.append(line);
        }

        resp.setCharacterEncoding("utf-8");
        resp.getWriter().write(sb.toString());
    }

    //处理需转义正则字符
    public static String makeStringForRegExp(String str) {
        return str.replace("\\", "\\\\").replace("*", "\\*")
                .replace("+", "\\+").replace("|", "\\|")
                .replace("{", "\\{").replace("}", "\\}")
                .replace("(", "\\(").replace(")", "\\)")
                .replace("^", "\\^").replace("$", "\\$")
                .replace("[", "\\[").replace("]", "\\]")
                .replace("?", "\\?").replace(",", "\\,")
                .replace(".", "\\.").replace("&", "\\&");
    }
}

控制层

@MyController
@MyRequestMapping("/web")
public class ModelAndViewController {
    @MyRequestMapping("/index")
    public MyModelAndView test(@MyRequestParam String name,@MyRequestParam(value = "index",required = false)Integer index){
        System.out.println(index);
        String viewName = "index";
        MyModelAndView myModelAndView = new MyModelAndView(viewName);
        Map<String,Object> models = new HashMap<>();
        models.put("name","hx");
        models.put("date",new Date().toLocaleString());
        myModelAndView.setModels(models);

        return myModelAndView;
    }

    @MyRequestMapping("/anotherIndex")
    public MyModelAndView test2(@MyRequestParam String name,@MyRequestParam(value = "index",required = false)Integer index){
        System.out.println(index);
        String viewName = "anotherindex";
        MyModelAndView myModelAndView = new MyModelAndView(viewName);
        Map<String,Object> models = new HashMap<>();
        models.put("name","hx");
        models.put("date",new Date().toLocaleString());
        myModelAndView.setModels(models);

        return myModelAndView;
    }

    @MyRequestMapping("/test")
    public MyModelAndView test3(@MyRequestParam String name,@MyRequestParam(value = "index",required = false)Integer index){
        System.out.println(index);
        String viewName = "dir/test1";
        MyModelAndView myModelAndView = new MyModelAndView(viewName);
        Map<String,Object> models = new HashMap<>();
        models.put("name","hx");
        models.put("date",new Date().toLocaleString());
        myModelAndView.setModels(models);

        return myModelAndView;
    }

    @MyRequestMapping("/add*")
    public MyModelAndView testRegex(HttpServletRequest req){
        System.out.println(req.getRequestURI().replace(req.getContextPath(),""));

        return null;
    }

    @MyRequestMapping("/404")
    public MyModelAndView test404(HttpServletRequest req){
        return new MyModelAndView("404");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值