自己实现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, 完成测试
- 启动Tomcat
- 浏览器输入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。