自己实现SpringMVC 底层机制[三]

简介: 自己实现SpringMVC 底层机制[三]

自己实现SpringMVC 底层机制[三]


实现任务阶段5- 完成Spring 容器对象的自动装配-@Autowried


说明: 完成Spring 容器中对象的注入/自动装配。


分析示意图


加入@AutoWired 注解, 进行对象属性的装配-如图


bd7934deaa7f4f249bc80936087a0dc5.png

浏览器输入http://localhost:8080/monster/list, 返回列表信息.

20cfac48c3224452ab8f3d53eabfa5dc.png


代码实现


创建my-yringmvc\src\main\java\com\myyringmvc\annotation\AutoWired.java

@Target(ElementType.FIELD)//该注解只能声明在一个类的字段前。
@Retention(RetentionPolicy.RUNTIME)//保存到class文件中,jvm加载class文件之后,仍然可读
@Documented//Java生成文档显示注解
public @interface AutoWired {
    String value() default "";
}


修改my-springmvc\src\main\java\com\controller\MonsterController.java

@Controller
public class MonsterController {
    //@AutoWired表示要完成属性的装配.
    @AutoWired
    private MonsterService monsterService;
    //编写方法,可以列出妖怪列表
    //springmvc 是支持原生的servlet api, 为了看到底层机制
    //这里我们设计两个参数
    @RequestMapping(value = "/monster/list")
    public void listMonster(HttpServletRequest request,  HttpServletResponse response) {
        //设置编码和返回类型,防止出现中文乱码
        response.setContentType("text/html;charset=utf-8");
        StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");
        //StringBuilder类可以直接对对象本身进行修改,string需要产生新的对象
        //调用monsterService,获取妖怪集合信息
        List<Monster> monsters = monsterService.listMonster();
        content.append("<table border='1px' width='500px' style='border-collapse:collapse'>");//设计显示样式
        for (Monster monster : monsters) {
            content.append("<tr><td>" + monster.getId()
                    + "</td><td>" + monster.getName() + "</td><td>"
                    + monster.getSkill() + "</td><td>"
                    + monster.getAge() + "</td></tr>");
        }
        content.append("</table>");
        //获取writer返回信息
        try {
            PrintWriter printWriter = response.getWriter();
            printWriter.write(content.toString());//输出显示妖怪信息
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


修改my-springmvc\src\main\java\com\myspringmvc\context\MyWebApplicationContext.java, 增加方法

 //编写方法,完成自己的spring容器的初始化
    public void init() {
        String basePackage =
                XMLParser.getBasePackage(configLocation.split(":")[1]);
        //这时basePackage => com.controller,com.service
        String[] basePackages = basePackage.split(",");
        //遍历basePackages, 进行扫描
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                scanPackage(pack);//扫描
            }
        }
        System.out.println("扫描后的= classFullPathList=" + classFullPathList);
        //将扫描到的类, 反射到ico容器
        executeInstance();
        System.out.println("扫描后的 ioc容器= " + ioc);
        //完成注入的bean对象,的属性的装配
        executeAutoWired();
        System.out.println("装配后 ioc容器= " + ioc);
    }
//编写方法,完成属性的自动装配
    public void executeAutoWired() {
        //判断ioc有没有要装配的对象
        if (ioc.isEmpty()) {
            return; //你也可以抛出异常 throw new RuntimeException("ioc 容器没有bean对象")
        }
        //遍历ioc容器中的所有注入的bean对象, 然后获取到bean的所有字段/属性,判断是否需要装配
  // entry => <String,Object > String 就是你注入对象时名称 Object就是bean对象
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Object bean = entry.getValue();//获取bean对象
            //getDeclaredFields()得到bean的所有字段/属性
            Field[] declaredFields = bean.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //判断当前这个字段,是否有@AutoWired
                if (declaredField.isAnnotationPresent(AutoWired.class)) {//判断是否是@AutoWired
                    //当前这个字段有@AutoWired
                    AutoWired autoWiredAnnotation = declaredField.getAnnotation(AutoWired.class);
                    String beanName = autoWiredAnnotation.value();//得到@AutoWired配置的beanName
                    if ("".equals(beanName)) {//如果没有设置value,按照默认规则
                        //即得到字段类型的名称的首字母小写,作为名字来进行装配
                        Class<?> type = declaredField.getType();
                        beanName = type.getSimpleName().substring(0, 1).toLowerCase() +
                                type.getSimpleName().substring(1);
                    }
                    //如果设置value, 直接按照beanName来进行装配
                    //从ioc容器中获取到bean
                    if (null == ioc.get(beanName)) {//说明你指定的名字对应的bean不在ioc容器
                        throw new RuntimeException("ioc容器中, 不存在你要装配的bean");
                    }
                    //防止属性是private, 我们需要暴力破解
                    declaredField.setAccessible(true);
                    //可以装配属性
                    try {
                        declaredField.set(bean, ioc.get(beanName));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }


启动Tomcat, 完成测试


  1. 启动Tomcat
  2. 浏览器输入http://localhost:8080/monster/list


image.png


实现任务阶段6- 完成控制器方法获取参数-@RequestParam


功能说明:自定义@RequestParam 和方法参数名获取参数。


完成任务说明


前端页面

a5ce494b39ff47d894ba833750664ef0.png


后端Handler 的目标方法

@RequestMapping(value = "/monster/find")
public void findMonstersByName(HttpServletRequest request,  HttpServletResponse response,
@RequestParam(value = "name") String name) {
  //代码....
}


1.完成: 将方法的 HttpServletRequest 和HttpServletResponse 参数封装到参数数组,进行反射调用


代码实现


修改my-springmvc\src\main\java\com\myspringmvc\servlet\MyDispatcherServlet.java

private void executeDispatch(HttpServletRequest req, HttpServletResponse response)
{
    MyHandler myHandler = getMyHandler(req);
    try {
            if (null == myHandler) {//没有匹配的Handler
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            } else {
                        //有匹配的Handler, 就调用
                    //通过反射得到的参数数组-> 在反射调用方法时会使用到
                    //getParameterTypes 或得到当前这个方法的所有参数信息
                    Class<?>[] parameterTypes =
                    myHandler.getMethod().getParameterTypes();
                    //定义一个请求的参数集合, 后面在进行反射调用方法时会使用到
                    Object[] params = new Object[parameterTypes.length];
                    //先搞定HttpServletRequest 和HttpServletResponse 这个两个参数
                    //说明
                    //1. 这里使用的是名字匹配,是简单的处理
                    //2. 标准的方式可以使用类型匹配
                    for (int i = 0; i < parameterTypes.length; i++) {
                            Class<?> parameterType = parameterTypes[i];
                            if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                                params[i] = req;
                            } 
                            else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
                                params[i] = response;
                            }
                    }
                    myHandler.getMethod().invoke(myHandler.getController(), params);
            }
    } catch (Exception e) {
        e.printStackTrace();
    }
}


完成测试(启动tomcat)


浏览器输入http://localhost:8080/monster/list , 仍然可以看到正确的返回。

a8f128d6a9e14ceb981da3a458ad7ecb.png


2. 完成: 在方法参数指定@RequestParam 的参数封装到参数数组,进行反射调用


完成任务说明


测试页面

851e98b7b9d5438ea511a10a15a297f0.png


后端Handler 的目标方法

@RequestMapping(value = "/monster/find")
public void findMonstersByName(HttpServletRequest request,HttpServletResponse response,
@RequestParam(value = "name") String name) {
  //代码....
}


代码实现

创建my-springmvc\src\main\java\com\myspringmvc\annotation\RequestParam.java

@Target(ElementType.PARAMETER)//ElementType.PARAMETER表示该注解只能声明在一个方法参数前
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    String value() default "";
}


修改my-springmvc\src\main\java\com\service\MonsterService.java

public interface MonsterService{
    //增加方法-返回monster列表
    public List<Monster> listMonster();
    //增加方法,通过传入的name,返回monster列表
    public List<Monster> findMonsterByName(String name);
}


修改my-springmvc\src\main\java\com\service\impl\MonsterServiceImpl.java,增加方法

@Service
public class MonsterServiceImpl implements MonsterService {
    @Override
    public List<Monster> findMonsterByName(String name) {
        List<Monster> monsters =    new ArrayList<>();
        monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));
        monsters.add(new Monster(200, "老猫妖怪", "抓老鼠", 200));
        monsters.add(new Monster(300, "大象精", "运木头", 100));
        //创建集合返回查询到的monster集合
        List<Monster> findMonsters =
                new ArrayList<>();
        //遍历monsters,返回满足条件
        for (Monster monster : monsters) {
            if (monster.getName().contains(name)) {
                findMonsters.add(monster);
            }
        }
        return findMonsters;
    }
}


修改my-springmvc\src\main\java\com\controller\MonsterController.java ,增加方法

    //增加方法,通过name返回对应的monster集合
    @RequestMapping(value = "/monster/find")
    public void findMonsterByName(HttpServletRequest request,  HttpServletResponse response,  String name) {
        //设置编码和返回类型
        response.setContentType("text/html;charset=utf-8");
        System.out.println("--接收到的name---" + name);
        StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");
        //调用monsterService
        List<Monster> monsters = monsterService.findMonsterByName(name);
        content.append("<table border='1px' width='400px' style='border-collapse:collapse'>");
        for (Monster monster : monsters) {
              content.append("<tr><td>" + monster.getId()
                    + "</td><td>" + monster.getName() + "</td><td>"
                    + monster.getSkill() + "</td><td>"
                    + monster.getAge() + "</td></tr>");
        }
        content.append("</table>");
        //获取writer返回信息
        try {
            PrintWriter printWriter = response.getWriter();
            printWriter.write(content.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


修改my-springmvc\src\main\java\com\myspringmvc\servlet\MyDispatcherServlet.java 增加方法

public class MyDispatcherServlet extends HttpServlet {
     //编写方法,完成分发请求任务
    private void executeDispatch(HttpServletRequest request,
                                 HttpServletResponse response) {
        MyHandler myHandler = getMyHandler(request);
        try {
            if (null == myHandler) {//说明用户请求的路径/资源不存在
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            } else {//匹配成功, 反射调用控制器的方法
                //目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
                //1. 得到目标方法的所有形参参数信息[对应的数组]
                Class<?>[] parameterTypes =
                        myHandler.getMethod().getParameterTypes();
                //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时,会使用到
                Object[] params =  new Object[parameterTypes.length];
                //3遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
                for (int i = 0; i < parameterTypes.length; i++) {
                    //取出每一个形参类型
                    Class<?> parameterType = parameterTypes[i];
                    //如果这个形参是HttpServletRequest, 将request填充到params
                    //在原生SpringMVC中,是按照类型来进行匹配,这里简化使用名字来进行匹配
                    if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                        params[i] = request;
                    } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
                        params[i] = response;
                    }
                }
                //将http请求参数封装到params数组中, 提示,要注意填充实参的时候,顺序问题
                //1. 获取http请求的参数集合
                //http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
                //2. 返回的Map<String,String[]> String:表示http请求的参数名
                //   String[]:表示http请求的参数值,为什么是数组
                //处理提交的数据中文乱码
                request.setCharacterEncoding("utf-8");
                Map<String, String[]> parameterMap = request.getParameterMap();
                //2. 遍历parameterMap 将请求参数,按照顺序填充到实参数组params
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    //取出key,这name就是对应请求的参数名
                    String name = entry.getKey();
                    //说明:这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
                    String value = entry.getValue()[0];
                    //我们得到请求的参数对应目标方法的第几个形参,然后将其填充
                    //这里专门编写一个方法,得到请求的参数对应的是第几个形参
                    int indexRequestParameterIndex =
                            getIndexRequestParameterIndex(myHandler.getMethod(), name);
                    if (indexRequestParameterIndex != -1) {//找到对应的位置
                        params[indexRequestParameterIndex] = value;
                    } else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[一会完成]
                       }
                      }
                       myHandler.getMethod().invoke(myHandler.getController(), params);
                    }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //编写方法,返回请求参数是目标方法的第几个形参
    public int getIndexRequestParameterIndex(Method method, String name) {
        //1.得到method的所有形参参数
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //取出当前的形参参数
            Parameter parameter = parameters[i];
            //判断parameter是不是有@RequestParam注解
            boolean annotationPresent = parameter.isAnnotationPresent(RequestParam.class);
            if (annotationPresent) {//说明有@RequestParam
                //取出当前这个参数的 @RequestParam(value = "xxx")
                RequestParam requestParamAnnotation =
                        parameter.getAnnotation(RequestParam.class);
                String value = requestParamAnnotation.value();
                //这里就是匹配的比较
                if (name.equals(value)) {
                    return i;//找到请求的参数,对应的目标方法的形参的位置
                }
            }
        }
        //如果没有匹配成功,就返回-1
        return -1;
    }
}


完成测试(Redeploy Tomcat 即可) , 浏览器输入http://localhost:8080/monster/find?name=%E7%89%9B%E9%AD%94%E7%8E%8B。

192fd0c740f0416da23cf9b17867cad2.png


3.完成: 在方法参数没有指定@RequestParam ,按照默认参数名获取值, 进行反射调用


完成任务说明


前端页面

da8d6d7dc2414486bb56b23e75c3c84d.png


后端Handler 的目标方法

@RequestMapping(value = "/monster/find")
public void findMonstersByName(HttpServletRequest request,HttpServletResponse response,/*@RequestParam(value = "name")*/ String name) {
  //代码....
}


代码实现


修改my-springmvc\src\main\java\com\controller\MonsterController.java

@RequestMapping(value = "/monster/find")
public void findMonstersByName(HttpServletRequest request,HttpServletResponse response,/*@RequestParam(value = "name")*/ String name) {
    response.setContentType("text/html;charset=utf-8");
    try {
            System.out.println("接收到name= " + name);
            if(name == null) {//如果没有匹配到, 设置为""
              name = "";
       }
            List<Monster> monsters = monsterService.findMonstersByName(name);
            StringBuilder content = new StringBuilder("<h1>你找到的妖怪列表</h1>");
            content.append("<table width='500px' style='border-collapse: collapse' border='1px'>");
            for (Monster monster : monsters) {
                content.append("<tr><td>" + monster.getId() + "</td><td>"
                + monster.getName() + "</td><td>" + monster.getSkill() + "</td>");
            }
            content.append("</table>");
            PrintWriter printWriter = response.getWriter();
            printWriter.write(content.toString());
    } catch (IOException e) {
      e.printStackTrace();
    }
}


修改my-springmvc\src\main\java\com\myspringmvc\servlet\MyDispatcherServlet.java

public class MyDispatcherServlet extends HttpServlet {
     //编写方法,完成分发请求任务
    private void executeDispatch(HttpServletRequest request,   HttpServletResponse response) {
        MyHandler myHandler = getMyHandler(request);
        try {
            if (null == myHandler) {//说明用户请求的路径/资源不存在
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            } else {//匹配成功, 反射调用控制器的方法
                //目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
                //1. 得到目标方法的所有形参参数信息[对应的数组]
                Class<?>[] parameterTypes =
                        myHandler.getMethod().getParameterTypes();
                //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时,会使用到
                Object[] params =  new Object[parameterTypes.length];
                //3遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
                for (int i = 0; i < parameterTypes.length; i++) {
                    //取出每一个形参类型
                    Class<?> parameterType = parameterTypes[i];
                    //如果这个形参是HttpServletRequest, 将request填充到params
                    //在原生SpringMVC中,是按照类型来进行匹配,这里简化使用名字来进行匹配
                    if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                        params[i] = request;
                    } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
                        params[i] = response;
                    }
                }
                //将http请求参数封装到params数组中, 提示,要注意填充实参的时候,顺序问题
                //1. 获取http请求的参数集合
                //http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
                //2. 返回的Map<String,String[]> String:表示http请求的参数名
                //   String[]:表示http请求的参数值,为什么是数组
                //处理提交的数据中文乱码
                request.setCharacterEncoding("utf-8");
                Map<String, String[]> parameterMap = request.getParameterMap();
                //2. 遍历parameterMap 将请求参数,按照顺序填充到实参数组params
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    //取出key,这name就是对应请求的参数名
                    String name = entry.getKey();
                    //说明:这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
                    String value = entry.getValue()[0];
                    //我们得到请求的参数对应目标方法的第几个形参,然后将其填充
                    //这里专门编写一个方法,得到请求的参数对应的是第几个形参
                    int indexRequestParameterIndex =
                            getIndexRequestParameterIndex(myHandler.getMethod(), name);
                    if (indexRequestParameterIndex != -1) {//找到对应的位置
                        params[indexRequestParameterIndex] = value;
                    }  else {
                                //如果没有找到, 我们就按照默认的参数名的匹配规则来做[一会完成]
                                //编写getParameterNames() 方法获取到该方法的所有参数名
                                List<String> parameterNames =
                                getParameterNames(myHandler.getMethod());
                                for (int i = 0; i < parameterNames.size(); i++) {
                                //如果请求参数和方法参数名一致,就匹配到
                                        if (name.equals(parameterNames.get(i))) {
                                                params[i] = value;
                                                break;
                                        }
                                }
      }
    }
    myHandler.getMethod().invoke(myHandler.getController(), params);
      }
  } catch (Exception e) {
    e.printStackTrace();
  }
    }
/**
* 得到控制器方法的参数名, 比如public void  findMonstersByName(HttpServletRequest request,
* HttpServletResponse response, @RequestParam(value = "name") String  name)
* 里面的request, response, name
* 注意:
* 1. 在默认情况下,返回的并不是request, response ,name 而是arg0, arg1,arg2
* 2. 需要使用到jdk8 的新特性,并需要在pom.xml 配置maven 编译插件(可以百度搜索到),才能得到request, response, name
*/
  public List<String> getParameterNames(Method method) {
            List<String> parametersList = new ArrayList<>();
            //获取到所以的参数名->这里有一个小细节
            //在默认情况下 parameter.getName() 得到的名字不是形参真正名字
            //而是 [arg0, arg1, arg2...], 这里我们要引入一个插件,使用java8特性,这样才能解决
            Parameter[] parameters = method.getParameters();
            //遍历parameters 取出名称,放入parametersList
            for (Parameter parameter : parameters) {
                parametersList.add(parameter.getName());
            }
            System.out.println("目标方法的形参列表=" + parametersList);
            return parametersList;
    }
}


修改my-springmvc\pom.xml , 保证版本一致

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.7.0</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <compilerArgs>
          <arg>-parameters</arg>
        </compilerArgs>
        <encoding>utf-8</encoding>
    </configuration>
</plugin>


完成测试


点击maven 管理,clean 项目,在重启一下tomcat。

5fc87ffb867345be9273c8ee7e5f2176.png


3e5d20d369b34eaaa2ad831e519fed78.png

700335553a9e4d7a99b9e763b70c0dca.png

相关文章
|
6月前
|
设计模式 前端开发 Java
【深入浅出Spring原理及实战】「夯实基础系列」360全方位渗透和探究SpringMVC的核心原理和运作机制(总体框架原理篇)
【深入浅出Spring原理及实战】「夯实基础系列」360全方位渗透和探究SpringMVC的核心原理和运作机制(总体框架原理篇)
69 0
|
XML JSON 前端开发
SpringMVC系列(六)之JSON数据返回以及异常处理机制
SpringMVC系列(六)之JSON数据返回以及异常处理机制
|
JSON 前端开发 Java
SpringMVC之JSON数据返回&异常处理机制
SpringMVC之JSON数据返回&异常处理机制
91 0
|
JSON 前端开发 Java
【SpringMVC】JSON注解&全局异常处理机制(二)
【SpringMVC】JSON注解&全局异常处理机制(二)
66 0
|
存储 JSON 前端开发
SpringMVC之JSON返回&异常处理机制(带你学习新的SpringMVC武功秘籍)
SpringMVC之JSON返回&异常处理机制(带你学习新的SpringMVC武功秘籍)
203 0
|
XML JSON 开发框架
【推荐】SpringMVC与JSON数据返回及异常处理机制的使用
【推荐】SpringMVC与JSON数据返回及异常处理机制的使用
109 0
|
1月前
|
XML 存储 前端开发
手动开发-实现SpringMVC底层机制--小试牛刀
手动开发-实现SpringMVC底层机制--小试牛刀
14 0
|
JSON 前端开发 Java
SpringMVC之JSON返回及异常处理机制
SpringMVC之JSON返回及异常处理机制
59 0
|
6月前
SpringMVC异常处理机制
SpringMVC异常处理机制
36 0
|
SQL JSON fastjson
SpringMVC之JSON返回&异常处理机制
SpringMVC之JSON返回&异常处理机制
64 1