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

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

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

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

分析示意图

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

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

代码实现

创建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

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

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

完成任务说明

前端页面

后端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 , 仍然可以看到正确的返回。

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

完成任务说明

测试页面

后端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。

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

完成任务说明

前端页面

后端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。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晨犀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值