Spring MVC学习

一. Spring MVC介绍

  1. Spring MVC是WEB层框架[接管了web层组件,比如: 控制器、视图、视图解析器、返回给用户的数据格式,同时支持MVC的开发模式]
  2. Spring MVC通过注解,让POJO成为控制器,不需要继承或者实现接口
  3. Spring MVC采用低耦合的组件设计方式,具有更好扩展和灵活性
  4. 支持REST风格的URL请求
  5. Spring MVC是基于Spring的,也就是Spring MVC是在Spring基础上的(SpringBoot > Spring > Spring MVC)

二.快速入门

web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

  <display-name>Archetype Created Web Application</display-name>

  <!--配置前端控制器-->
  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <!--加载springmvc的配置文件-->
      <param-value>classpath:applicationContext-mvc.xml</param-value>
    </init-param>
    <!--在web项目启动时,就加载这个servlet实例-->
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>


</web-app>

applicationContext-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd">

    <!--配置自动扫描包-->
    <context:component-scan base-package="com.zzti.springmvc.controller"/>

    <!--配置springmvc的视图解析器,比如我们的controller return 的是login_ok,
    那么这个页面就是/WEB-INF/pages/login_ok.jsp-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

</beans>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head>
<title>登录</title>

</head>
<body>

<h1>登录页面</h1>
<form action="login" method="post">
u: <input name="username" type="text"><br/>
p: <input name="password" type="password"><br/>
<input type="submit" value="登录" />
</form>

</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head>
<title>登录成功页面</title>

</head>
<body>

<h1>恭喜, 登录成功!!!</h1>


</body>
</html>
package com.zzti.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class UserController {

    @RequestMapping("/login")
    public String login(){
        System.out.println("login ok~~");
        return "login_ok";
    }

}

在这里插入图片描述
在这里插入图片描述
注意细节:

  1. UserController需要注解成@Controller,称为一个Handler处理器
  2. 关于springmvc的DispatcherServlet的配置文件,如果不在web.xml指定applicationContext-mvc.xml文件,默认在/WEB-INF/DispatcherServlet-servlet.xml 找这个配置文件。
    在web.xml文件中,把init-param注释掉,然后再把原来配置的applicationContext-mvc.xml文件,修改一个名字[xx-servlet.xml] (xx是自己配置的DispatcherServlet的名字)
    放在/WEB-INF目录下面,在测试一下。

三.Spring MVC的执行流程

四. @RequestMapping注解

  1. @RequestMapping注解可以指定控制器/处理器的某个方法请求的url
  2. @RequestMapping可以修饰方法和类,当同时修饰方法和类时,url就是组合 /类请求值/方法请求值 ,同时也可以method指定请求方式(method = RequestMethod.POST),若不按照指定方式请求就会报错。
@Controller
@RequestMapping("/user")
public class UserHandler {
    
    @RequestMapping(value = "buy",method = RequestMethod.POST)
    public String buy(){
        System.out.println("购买商品");
        return "success";
    }
}

在这里插入图片描述
在这里插入图片描述
3. @RequestMapping可指定params 和 headers支持简单表达式

  • param1: 表示请求必须包含名为param1的请求参数
  • !=param1: 表示请求不能包含名为param1的请求参数
  • param != value1: 表示请求包含名为param1 的请求参数,但是值不能为value1
  • {"param1=value1","param2"}: 请求必须包含名为param1 和 param2
    的两个参数,并且param1参数的值必须为value1

a) param1: 表示请求必须包含名为param1的请求参数

@RequestMapping(value = "/find",params = "bookId" ,method = RequestMethod.GET)
public String search(String bookId){
    System.out.println("查询书籍 bookId= " + bookId);
    return "success";
}
===================================
查询书籍 bookId= 100

在这里插入图片描述

@RequestMapping(value = "/find",params = "bookId=100" ,method = RequestMethod.POST) 

在这里插入图片描述
b) param != value1: 表示请求包含名为param1 的请求参数,但是值不能为value1

@RequestMapping(value = "/find",params = "bookId!=100" ,method = RequestMethod.POST)

在这里插入图片描述
c) 请求必须包含名为param1 和 param2 的两个参数,并且param1参数的值必须为value1

@RequestMapping(value = "/find", params = { "bookId=100", "bookName"},method = RequestMethod.POST)
public String search(String bookId,String bookName){
    System.out.println("查询书籍 bookId= " + bookId + ",书籍名称= " + bookName);
    return "success";
}
================================
查询书籍 bookId= 100,书籍名称= 三国演义

在这里插入图片描述
4. @RequestMapping支持Ant风格资源地址

? : 匹配文件名中的一个字符
* : 匹配文件名中的任意字符
* * :匹配多层路径

应用举例:

/user/*/createUser ==> /user/aa/createUser、/user/bb/createUser等URL

/user/**/createUser ==> /user//createUser、/user/aa/bb/createUser等URL

/user/createUser?? ==> /user/createUseraaa、/user/createUserbb等URL

@PostMapping(value = "/message/**")
public String im(String bookId,String bookName){
    System.out.println("发送消息");
    return "success";
}
=========================
发送消息

在这里插入图片描述

  • @RequestMapping 可匹配 @PathVariable映射URL绑定的占位符,就不需要在url地址上带参数名了,比较简洁
@RequestMapping(value = "/register/{username}/{userId}")
 public String register(@PathVariable("username") String name, @PathVariable("userId") String id){
     System.out.println("接收到参数~~username= " + name + ",userId= " + id);
     return "success";
 }

在这里插入图片描述
注意细节:

  • 映射的url,不能重复
  • 各种请求的简写形式@GetMapping、@PostMapping、@DeleteMapping、@PutMapping
  • 若我们确定表单或者超链接会提交某个字段数据(比如: email),要求提交的参数名和目标方法参数保持一致
@GetMapping(value = "/hello")
public String hello(String email){
    System.out.println("hello ~ " + email);
    return "success";
}
=====================
hello ~ tom@163.com

在这里插入图片描述

五. Rest请求风格

  1. REST(Representational State Transfer) 资源表现层转态转化。
  2. HTTP协议里,GET(获取资源)、POST(新建资源)、PUT(更新资源)、DELETE(删除资源)
  3. REST核心过滤器
    3.1 当前的浏览器只支持post/get请求,因此为了得到put/delete请求方式,需要使用Spring提供的HiddenHttpMethodFilter过滤器进行转换
    3.2 HiddenHttpMethodFilter: 浏览器form表单只支持get、post请求,而delete、put等method并不支持,spring添加了一个过滤器,可以将这些请求转换为标准的http方法,使得支持常见的四种请求方式。
    3.3 HiddenHttpMethodFilter能对post请求方式进行转换,需要我没注意这点,这个过滤器需要在web.xml中配置

web.xml 增加配置

<!--配置HiddenHttpMethodFilter过滤器-->
<filter>
  <filter-name>hiddenHttpMethodFilter</filter-name>
  <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>hiddenHttpMethodFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

applicationContext-mvc.xml增加配置

<!--加入两个常规配置-->
<!--能支持Spring MVC高级功能(JSR303校验,映射动态请求)-->
<mvc:annotation-driven/>

<!--将spring mvc不能处理的请求交给Tomcat-->
<mvc:default-servlet-handler/>

BookController.java

package com.zzti.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping(value = "/book")
public class BookController {

    @GetMapping("/getBook/{id}")
    public String getBook(@PathVariable("id") String id){
        System.out.println("查询书籍 id= " + id);
        return "success";
    }

    @PostMapping("/addBook")
    public String addBook(String bookName){
        System.out.println("添加书籍 bookName= " + bookName);
        return "success";
    }

    @PutMapping("/updateBook/{bookId}")
    public String updateBook(@PathVariable("bookId") String id){
        System.out.println("修改书籍 id= " + id);
        //return "success";//这样会报错(JSPs only permit GET POST or HEAD)
        return "redirect:/book/success";
    }

    @DeleteMapping(value = "/delete/{bookId}")
    public String deleteBook(@PathVariable("bookId") String id){
        System.out.println("删除书籍 id= " + id);
        //return "success";//这样会报错(JSPs only permit GET POST or HEAD)
        return "redirect:/book/success";
    }
    

    @RequestMapping(value = "/success")
    public String scucessView(){
        //由该方法转发到success.jsp页面
        return "success";
    }
}

注意事项:

  • HiddenHttpMethodFilter,在将post转成 delete/put 请求时,是按照 _method 参数名来读取的
  • 若web项目时运行在Tomcat8及以上,会发现被过滤成DELETE和PUT请求,到达控制器时能顺利,但是返回(以forward方式返回)时会报错:
    (JSPs only permit GET POST or HEAD)。解决方式: 将请求转发(forward) 改为 请求重定向(redirect), 重定向到一个Handler,再由这个Handler转发到页面。

六. Spring MVC映射请求数据(@RequestParam)

package com.zzti.springmvc.controller;

import com.zzti.springmvc.entity.Master;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Controller
@RequestMapping(value = "/vote")
public class VoteController {

    //获取到url中的参数值
    /**
     * @RequestParam(value = "name",required = false)
     * 表示一个接收到的参数是name,required = false表示参数可有可无,默认是true
     * @param username
     * @return
     */
    @RequestMapping("/vote01")
    public String test01(@RequestParam(value = "name",required = false) String username){
        System.out.println("得到的 username= " + username);
        return "success";
    }

    //获取http请求消息头
    @RequestMapping("/vote02")
    public String test02(@RequestHeader("Accept-Encoding") String ae,
                         @RequestHeader("Host") String host){
        System.out.println("Accept-Encoding= " + ae);
        System.out.println("Host= " + host);
        return "success";
    }

    //获取Javabean(entity/pojo)形式的数据
    /**
     * 1. 支持级联数据获取
     * 2. 表单的控件名称name需要和Javabean对象字段对应,否则就是null
     * @param master
     * @return
     */
    @RequestMapping("/vote03")
    public String test03(Master master){
        System.out.println("主人信息= " + master);
        return "success";
    }

    //获取servlet api

    /**
     * 开发中,我们可能需要使用到原生的servlet api
     * @return
     */
    @RequestMapping("/vote04")
    public String test04(HttpServletRequest request,
                         HttpServletResponse response,
                         HttpSession session){
        System.out.println("name= " + request.getParameter("username"));
        System.out.println("pwd= " + request.getParameter("pwd"));
        System.out.println("session= " + session);

        HttpSession requestSession = request.getSession();
        System.out.println("requestSession= " + requestSession);
        System.out.println(session == requestSession);//true

        return "success";
    }

}

七. 模型数据

一. 模型数据处理-数据放入 request

开发中,控制器中获取的数据如何放入request域,然后再前端(vue/jsp/html)取出显示

  1. 通过HttpServletRequest放入request域
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" isELIgnored="false"%>
<html>
<head>
<title>获取数据成功页面</title>

</head>
<body>

<h1>获取的数据显示页面</h1>
<hr>
<h1>取出request域的数据</h1>
<br>

address: ${address}<br>
主人名= ${requestScope.master.name}<br>
主人信息= ${requestScope.master}<br>
宠物名字= ${requestScope.master.pet.name}<br>

</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head>
<title>测试 模型数据</title>

</head>
<body>

<h1>模型数据</h1>
<form action="vote/vote05" method="post">
主人号: <input name="id" type="text"><br/>
主人名: <input name="name" type="text"><br/>
宠物号: <input name="pet.id" type="text"><br/>
宠物名: <input name="pet.name" type="text"><br/>
<input type="submit" value="添加宠物和主人" />
</form>

</body>
</html>
/**
 * 演示将提交的数据-->springmvc封装到java对象-->springmvc会自动的将其放到request中
 * 这样我们就可以在调转到的页面取出数据
 * @param master
 * @param request
 * @param response
 * @return
*/
@RequestMapping("/vote05")
public String test05(Master master,HttpServletRequest request,
                    HttpServletResponse response){
   request.setAttribute("address","上海");
   //修改master属性值
   master.setName("nono");
   //分析一下,springmvc默认存放对象到request域中,属性名是类名首字母小写
   return "vote_ok";
}

在这里插入图片描述

  • 通过请求的方法参数 Map<String,Object> 放入request域
/**
 * 演示通过Map<String,Object> 设置数据到request域
 * @param master
 * @return
*/
@RequestMapping("/vote06")
public String test06(Master master, Map<String,Object> map){
   map.put("address","天津~~");
   return "vote_ok";
}
  • 通过返回ModelAndView对象,实现request域对象
@RequestMapping("/vote07")
public ModelAndView test07(Master master, Map<String,Object> map){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("address","北京~~");
    //指定要跳转的视图名称
    modelAndView.setViewName("vote_ok");
    return modelAndView;
}

在这里插入图片描述
注意事项:

  • 从本质看,请求响应的方法return “xxx”,是返回一个字符串,其实本质是返回了一个ModelAndView对象,只是默认被封装起来的
  • ModelAndView既可以包含model数据,也可以包含视图信息
  • ModelAndView对象的addObjet(),可以添加key-value数据,默认在request域中
  • ModelAndView对象setViewName(),可以指定要跳转的视图名称

二. 模型数据处理-数据放入 session

开发中,控制器中获取的数据如何放入session域,然后再前端(vue/jsp/html)取出显示

//模型数据处理-数据放入 session
@RequestMapping("/vote08")
public String test08( Map<String,Object> map,Master master,HttpSession httpSession){
    System.out.println("==========test08=================");
    map.put("address","广州");
    httpSession.setAttribute("master2",master);
    return "vote_ok";
}

在这里插入图片描述

三. @ModelAttribute 实现prepare方法

开发中,有时需要使用某个前置方法(prepareXxx(),方法名有程序员自己指定)给目标方法准备一个模型对象。@ModelAttribute 注解可以实现这样的需求,在某个方法加上这个注解后,调用该Controller(Handler)的任何一个方法时,都会调用这个方法。

@RequestMapping("/vote08")
public String test08( Map<String,Object> map,Master master,HttpSession httpSession){
    System.out.println("==========test08=================");
    map.put("address","广州");
    httpSession.setAttribute("master2",master);
    return "vote_ok";
}

@ModelAttribute
public void prepareModel(){
    System.out.println("prepareModel()....完成准备工作.....");
}
=============================
prepareModel()....完成准备工作.....
==========test08=================

应用场景:
修改用户信息,流程如下:

  1. 在修改前,在前置方法中从数据库查出这个用户
  2. 在修改方法(目标方法)中,可以使用前置方法从数据库查询用户
  3. 如果表单中对用户的某个属性修改了,则以新的数据为准,如果没有修改,则以数据库的信息为准,若某个属性不能修改,就保持原来的值。

八. 视图和视图解析器

一. 基本介绍

在springmvc中的目标方法最终返回都是一个视图,返回的视图都会由一个视图解析器来处理(视图解析器有很多种)

二. 自定义视图

在默认情况下,我们都是返回的默认视图,然后这个返回的视图交给springmvc的InternalResourceViewResolver 视图处理器来处理的。

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

在实际开发中,我们需要自定义视图,这样可以满足更多复杂的需求。

<!--配置自定义图解析器-->
 <!--
     1. BeanNameViewResolver这个就是可以解析自定义视图的解析器
     2. name="order"表示这个解析器设置的优先级 默认优先级很低, 默认值是Integer.MAX_VALUE
     3. order值越小,优先级越高
 -->
 <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
     <property name="order" value="99"></property>
 </bean>

测试自定义视图

/**
 * 自定义视图类
 */
@Component(value = "myView")
public class MyView extends AbstractView {
    @Override
    protected void renderMergedOutputModel(Map<String, Object> map,
                                           HttpServletRequest httpServletRequest,
                                           HttpServletResponse httpServletResponse) throws Exception {
        System.out.println("进入到自己的视图");
        //这里自己确定到哪个页面去, 默认的视图解析机制就无效
        httpServletRequest.getRequestDispatcher("/WEB-INF/pages/my_view.jsp").
                forward(httpServletRequest,httpServletResponse);
    }
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" isELIgnored="false"%>
<html>
<head>
<title>view</title>

</head>
<body>

<h1>测试自定义视图</h1>

<a href="goods/buy">测试自定义视图</a><br/>

</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" isELIgnored="false"%>
<html>
<head>
<title>自定义视图页面</title>

</head>
<body>

<h1>自定义视图页面</h1>
<hr/>
<h1>欢迎来到,自定义视图~~</h1>
<br>


</body>
</html>
@Controller
@RequestMapping("/goods")
public class GoodsController {

    @RequestMapping(value = "/buy")
    public String buy(){
        System.out.println("~~buy~~");
        return "myView";//填写自己定义的视图名字
    }
}
==============测试结果===================
~~buy~~
进入到自己的视图
欢迎来到,自定义视图~~

小结:

  1. 自定义视图: 创建一个View的bean,该bean需要继承AbstractView,并实现renderMergedOutputModel(), 还要把View加入到ioc容器中。
  2. 自定义视图的视图处理器,使用BeanNameViewResolver,这个视图处理器也需要配置ioc容器中。
  3. BeanNameViewResolver的调用优先级需要配置一下,设置order比默认值Integer.MAX_VALUE小的值,以确保在默认视图解析器之前被调用。

自定义视图-工作流程

  • Spring MVC调用目标方法,返回自定义View在ioc容器中的id
  • Spring MVC调用BeanNameViewResolver解析视图: 从ioc容器中获取
    返回id值对应的bean,即自定义的view的对象(若返回自定义View在ioc容器中的id不存在,则仍然按照默认的视图处理机制处理)
  • Spring MVC调用自定义视图的renderMergedOutputModel() 渲染视图。

三. 目标方法直接指定转发或重定向

  1. 默认返回的方式是请求转发,然后在视图处理器进行处理
@GetMapping("/getBook/{id}")
public String getBook(@PathVariable("id") String id){
    System.out.println("查询书籍 id= " + id);
    return "success";
}
  1. 也可以在目标方法中直接指定重定向或转发的url,如果指定重定向,不能定向到/WEB-INF目录中
@GetMapping("/getBook/{id}")
public String getBook(@PathVariable("id") String id){
    System.out.println("查询书籍 id= " + id);
    //直接指定转发到那个页面
    //return "forward:/WEB-INF/pages/my_view.jsp";
    //如果是重定向,不能指定到/WEB-INF目录中
    return "redirect:/login.jsp";
}

九. 自己实现Spring MVC底层机制

一. 任务1-开发HspDispatcherServlet(充当原生的前端控制器)

在这里插入图片描述
HspDispatcherServlet.java

/**
 * HspDispatcherServlet充当原生的DispatcherServlet
 * 本质是一个servlet,继承HttpServlet
 */
public class HspDispatcherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("HspDispatcherServlet doGet()执行~~~");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("HspDispatcherServlet doPost()执行~~~");
    }
}

web.xml

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <!--配置HspDispatcherServlet,作为我们自己的前端控制器-->
  <servlet>
    <servlet-name>HspDispatcherServlet</servlet-name>
    <servlet-class>com.zzti.myspringmvc.servlet.HspDispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <!--加载springmvc的配置文件-->
      <param-value>classpath:myspringmvc.xml</param-value>
    </init-param>
    <!--HspDispatcherServlet在Tomcat启动时,就自动加载-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>HspDispatcherServlet</servlet-name>
    <!--拦截所有的请求-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

myspringmvc.xml

<!--配置自动扫描包-->
<context:component-scan base-package="com.zzti.myspringmvc.controller"/>

二. 任务2-完成客户端/浏览器可以请求控制层

创建Controller和自定义注解

package com.zzti.myspringmvc.controller;

import com.zzti.myspringmvc.annotatipn.Controller;
import com.zzti.myspringmvc.annotatipn.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Controller
public class MonsterController {

    @RequestMapping(value = "/list")
    public void getListMonster(HttpServletRequest request, HttpServletResponse response){
        response.setContentType("text/html;charset=utf-8");
        try {
            PrintWriter writer = response.getWriter();
            writer.write("<h1>妖怪列表信息</h1>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    String value() default "";
}
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String value() default "";
}

配置myspringmvc.xml文件要扫描的包

 <!--配置自动扫描包-->
 <context:component-scan base-package="com.zzti.myspringmvc.controller,com.zzti.myspringmvc.service"/>

编写XMLParser工具类,可以解析myspringmvc.xml,得到要扫描的包

/**
 * XMLParser 用于解析 myspringmvc.xml文件
 */
public class XMLParser {
    public static String getBasePackage(String xmlFile){

        SAXReader saxReader = new SAXReader();
        //通过得到来的加载路径,来获取到myspringmvc配置文件
        InputStream inputStream = XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);
        try {
            //得到xmlFile文档
            Document document = saxReader.read(inputStream);
            Element rootElement = document.getRootElement();
            Element componentScanElement = rootElement.element("component-scan");
            Attribute attribute = componentScanElement.attribute("base-package");
            String basePackage = attribute.getText();
            return basePackage;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

开发HspWebApplicationContext,充当Spring容器,得到扫描类的全路径列表

/**
 * HspWebApplicationContext 表示我们自己的 spring容器
 */
public class HspWebApplicationContext {
    //定义属性classFullPathList, 保存扫描包/ 子包的类的全路径
    private List<String> classFullPathList = new ArrayList<>();

    //用于存放反射后的bean对象
    public ConcurrentHashMap<String,Object> ioc = new ConcurrentHashMap<>();

    public void init(){
        String basePackage = XMLParser.getBasePackage("myspringmvc.xml");
        System.out.println("basePackage= " + basePackage);
        String[] basePackages = basePackage.split(",");
        //遍历basePackages, 进行扫描
        if (basePackages.length > 0){
            for (String pack : basePackages) {
                scanPackage(pack);
            }
        }
        System.out.println("扫描后的classFullPathList= " + classFullPathList);
        executeInstance();
        System.out.println("扫描后的= " + ioc);
    }

    //创建方法,完后对包的扫描
    public void scanPackage(String pack){
        //得到包的工作路径
        URL url = this.getClass().getClassLoader()
                .getResource("/" + pack.replaceAll("\\.", "/"));
        System.out.println("url= " + url);

        //根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
        String path = url.getFile();
        System.out.println("path= " + path);
        File dir = new File(path);
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {
                //如果是一个目录,需要递归扫描
               scanPackage(pack +"."+ f.getName());
            }else{
                //是一个文件的话,先把文件的全路径保存到集合,在注入对象到容器
                String classFullPath =
                        pack +"."+ f.getName().replaceAll(".class","");
                classFullPathList.add(classFullPath);
            }
        }

    }
}

在HspDispatcherServlet.java 中增加如下方法

private HspWebApplicationContext hspWebApplicationContext;
 @Override
 public void init() throws ServletException {
     hspWebApplicationContext = new HspWebApplicationContext();
     hspWebApplicationContext.init();
 }
=====================测试结果=======================
basePackage= com.zzti.myspringmvc.controller,com.zzti.myspringmvc.service
url= file:/D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/controller/
path= /D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/controller/
url= file:/D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/controller/xx/
path= /D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/controller/xx/
url= file:/D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/service/
path= /D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/service/
url= file:/D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/service/impl/
path= /D:/Exercise_items/hsp_springmvc/myspringmvc/target/classes/com/zzti/myspringmvc/service/impl/
扫描后的classFullPathList= [com.zzti.myspringmvc.controller.GoodsController, com.zzti.myspringmvc.controller.MonsterController, com.zzti.myspringmvc.controller.xx.xxController, com.zzti.myspringmvc.service.impl.MonsterServiceImpl, com.zzti.myspringmvc.service.MonsterService]

完善HspWebApplicationContext,充当spring容器,实例化对象到容器中。将扫描到的类,在满足条件下(有相应的注解@Controller @Service),反射注入到ioc容器。

在HspWebApplicationContext.java中加入如下方法

//用于存放反射后的bean对象
public ConcurrentHashMap<String,Object> ioc = new ConcurrentHashMap<>();
 //实例化扫描到的类->创建对象->放入到ioc容器
  public void executeInstance(){
       if (classFullPathList.size() == 0){
           return;
       }
       try {
           for (String classFullPath : classFullPathList) {
               Class<?> clazz = Class.forName(classFullPath);
               if (clazz.isAnnotationPresent(Controller.class)) {
                   //得到该类的类名
                   String beanName = clazz.getSimpleName().substring(0,1).toLowerCase() + clazz.getSimpleName().substring(1);
                   ioc.put(beanName,clazz.newInstance());
               }
           }
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       }
   }
===========测试结果=============
扫描后的ioc= {monsterController=com.zzti.myspringmvc.controller.MonsterController@22ba6643}   

完成请求URL和控制器方法的映射关系
在这里插入图片描述
将配置的@RequestMapping的url和对应的 控制器方法 映射关系保存到集合中

//将配置的@RequestMapping的url和对应的 控制器方法 映射关系保存到集合中
public class HspHandler {
    private String url;
    private Object controller;
    private Method method;

    public HspHandler() {
    }

    public HspHandler(String url, Object controller, Method method) {
        this.url = url;
        this.controller = controller;
        this.method = method;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    @Override
    public String toString() {
        return "HspHandler{" +
                "url='" + url + '\'' +
                ", controller=" + controller +
                ", method=" + method +
                '}';
    }
}

在 HspDispatcherServlet.java中加入如下方法

//存放url和控制器-方法映射关系
private List<HspHandler> handlerList = new ArrayList<>();

@Override
public void init() throws ServletException {
    hspWebApplicationContext = new HspWebApplicationContext();
    hspWebApplicationContext.init();
    initHandlerMapping();
    System.out.println("handlerList= " + handlerList);
}

/**
 * 1. 完成控制层url-->Controller-->方法的映射关系
 * 2. 并放入到 handlerList集合中
 * 3. 通过 handlerList集合 找到某个url请求对应的控制器方法
 */
private void initHandlerMapping(){
    if (hspWebApplicationContext.ioc.isEmpty()) {
        throw new RuntimeException("spring ioc 容器为空");
    }
    for (Map.Entry<String, Object> entry : hspWebApplicationContext.ioc.entrySet()) {
        Class<?> clazz = entry.getValue().getClass();
        if (clazz.isAnnotationPresent(Controller.class)) {
            Method[] declaredMethods = clazz.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping requestMapping = declaredMethod.getAnnotation(RequestMapping.class);
                    String url = requestMapping.value();
                    handlerList.add(new HspHandler(url,entry.getValue(),declaredMethod));
                }

            }
        }
    }
}
==============测试结果=======================
handlerList= [HspHandler{url='/list', controller=com.zzti.myspringmvc.controller.MonsterController@22ba6643, method=public void com.zzti.myspringmvc.controller.MonsterController.getListMonster(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)}]

完成HspDispatcherServlet分发请求到对应控制器方法

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //System.out.println("HspDispatcherServlet doPost()执行~~~");
        executeDispather(request,response);
    }

    //根据request请求,返回对应的HspHandler对象
    private HspHandler getHspHandler(HttpServletRequest request){
        String requestURI = request.getRequestURI();
        for (HspHandler hspHandler : handlerList) {
            if (requestURI.equals(hspHandler.getUrl())) {
                //匹配
                return hspHandler;
            }
        }
        //不匹配,就返回null
        return null;
    }

    private void executeDispather(HttpServletRequest request,HttpServletResponse response){
        HspHandler hspHandler = getHspHandler(request);
        try {
            if (hspHandler == null) {
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            }else {
                hspHandler.getMethod().invoke(hspHandler.getController(),request,response);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

注意:

  1. 因为getRequestURI() 返回的是 主机后所有内容
  2. 为了和handler mapping中存放的url一致,我们需要把Tomcat配置application context 修改成 /
  3. 否则匹配不上,按照原来得到的是 /myspringmvc/list
  4. HspHandler找的是MonsterController中方法上的@RequestMappingurl路径,不包括类上面的@RequestMappingurl
    在这里插入图片描述
    在这里插入图片描述

三. 任务3-从web.xml动态获取myspringmvc.xml

因为之前是在HspWebApplicationContext中的init(),直接写的String basePackage = XMLParser.getBasePackage("myspringmvc.xml"),不是动态获取的,现在改成从web.xml文件中动态获取自定义的myspringmvc.xml配置文件。

修改HspWebApplicationContext.java文件

  //这个contextConfigLocation形式为 classpath:xx.xml
    private String contextConfigLocation = "";
    public HspWebApplicationContext(String contextConfigLocation){
        this.contextConfigLocation = contextConfigLocation;
    }

    public void init(){
        //String basePackage = XMLParser.getBasePackage("myspringmvc.xml");
        String basePackage = XMLParser.getBasePackage(contextConfigLocation.split(":")[1]);

        System.out.println("basePackage= " + basePackage);
        String[] basePackages = basePackage.split(",");
        //遍历basePackages, 进行扫描
        if (basePackages.length > 0){
            for (String pack : basePackages) {
                scanPackage(pack);
            }
        }
        System.out.println("扫描后的classFullPathList= " + classFullPathList);
        executeInstance();
        System.out.println("扫描后的ioc= " + ioc);
    }

修改HspDispatcherServlet.java中的init(),增加一个参数servletConfig,用来获取web.xml配置文件中的contextConfigLocation

 @Override
 public void init(ServletConfig servletConfig) throws ServletException {
     String contextConfigLocation = servletConfig.getInitParameter("contextConfigLocation");
     hspWebApplicationContext = new HspWebApplicationContext(contextConfigLocation);
     hspWebApplicationContext.init();
     initHandlerMapping();
     System.out.println("handlerList= " + handlerList);
 }

同时在添加一个GoodsController.java

@Controller
public class GoodsController {

    @RequestMapping("/goods/buy")
    public void buy(HttpServletRequest request, HttpServletResponse response){
        response.setContentType("text/html;charset=utf-8");
        try {
            PrintWriter writer = response.getWriter();
            writer.write("<h1>购买商品~~</h1>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @RequestMapping("/goods/add")
    public void add(HttpServletRequest request, HttpServletResponse response){
        response.setContentType("text/html;charset=utf-8");
        try {
            PrintWriter writer = response.getWriter();
            writer.write("<h1>添加商品~~</h1>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

四. 任务4-完成自定义@Service注解功能

若给某个类加上@Service,则可以将其注入到我们的spring容器

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}
@Service
public class MonsterServiceImpl implements MonsterService {
    
    @Override
    public List<Monster> listMonster() {
        List<Monster> list = new ArrayList<>();
        list.add(new Monster(100,"牛魔王","芭蕉扇"));
        list.add(new Monster(200,"蜘蛛精","吐丝啦"));
        return list;
    }
}
public class Monster {
    private Integer id;
    private String name;
    private String skill;
    。。。。。。。
}
//实例化扫描到的类->创建对象->放入到ioc容器
public void executeInstance(){
    if (classFullPathList.size() == 0){
        return;
    }
    try {
        for (String classFullPath : classFullPathList) {
            Class<?> clazz = Class.forName(classFullPath);
            if (clazz.isAnnotationPresent(Controller.class)) {
                //得到该类的类名
                String beanName = clazz.getSimpleName().substring(0,1).toLowerCase() + clazz.getSimpleName().substring(1);
                ioc.put(beanName,clazz.newInstance());
            }else if (clazz.isAnnotationPresent(Service.class)) {
                Service serviceAnnotation = clazz.getAnnotation(Service.class);
                String beanName = serviceAnnotation.value();
                if ("".equals(beanName)) {
                    //如果@Service没有指定value,得到该Service的所有接口名(首字母小写)
                    //相当于可以通过该类的多个接口名来注入该Service实例
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        String beanName2 = anInterface.getSimpleName().
                                substring(0,1).toLowerCase() + anInterface.getSimpleName().substring(1);
                        ioc.put(beanName2,clazz.newInstance());
                    }
                }else {
                    //若指定了value, 就直接放入spring容器中
                    ioc.put(beanName,clazz.newInstance());
                }
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
}
================测试结果==========================
扫描后的ioc= {goodsController=com.zzti.myspringmvc.controller.GoodsController@358b3b84, monsterService=com.zzti.myspringmvc.service.impl.MonsterServiceImpl@4f410d6b, monsterController=com.zzti.myspringmvc.controller.MonsterController@6fb35c58}

五. 任务5-完成Spring容器对象的自动装配-@Autowired

完成Spring容器中的对象的注入/自动装配,加入@Autowried注解,进行对象属性的装配

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    String value() default "";
}
@Controller
@RequestMapping(value = "/monster")
public class MonsterController {

    @Autowired
    private MonsterService monsterService;

    @RequestMapping(value = "/list")
    public void getListMonster(HttpServletRequest request, HttpServletResponse response){
        response.setContentType("text/html;charset=utf-8");
        try {
            List<Monster> monsters = monsterService.listMonster();
            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></tr>");
            }
            content.append("</table>");
            PrintWriter writer = response.getWriter();
            writer.write(content.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在HspWebApplicationContext.java中添加executeAutoWired();并在iniit()中加入测试

 public void executeAutoWired(){
        if (ioc.isEmpty()) {
            throw new RuntimeException("容器中,没有可以装配的bean");
        }
        //遍历ioc容器中所有,已经实例化的bean
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            String key = entry.getKey();
            Object bean = entry.getValue();
            Field[] declaredFields = bean.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (declaredField.isAnnotationPresent(Autowired.class)) {
                    Autowired annotation = declaredField.getAnnotation(Autowired.class);
                    String beanName = annotation.value();
                    //没有设置要注入的bean名字,则按照默认规则
                    if ("".equals(beanName)) {
                        Class<?> type = declaredField.getType();
                        //获取被标注@Autowired字段的类型名字
                        beanName = type.getSimpleName().substring(0,1).toLowerCase() + type.getSimpleName().substring(1);
                    }
                    declaredField.setAccessible(true);
                    //属性注入
                    try {
                        if (ioc.get(beanName) == null) {
                            throw new RuntimeException("容器中,没有可以装配的bean");
                        }
                        declaredField.set(bean,ioc.get(beanName));
                    }catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
    }
 public void init(){
        //String basePackage = XMLParser.getBasePackage("myspringmvc.xml");
        String basePackage = XMLParser.getBasePackage(contextConfigLocation.split(":")[1]);

        System.out.println("basePackage= " + basePackage);
        String[] basePackages = basePackage.split(",");
        //遍历basePackages, 进行扫描
        if (basePackages.length > 0){
            for (String pack : basePackages) {
                scanPackage(pack);
            }
        }
        System.out.println("扫描后的classFullPathList= " + classFullPathList);
        executeInstance();
        System.out.println("扫描后的ioc= " + ioc);
        //完成Spring容器中对象的自动装配/注入
        executeAutoWired();
        System.out.println("装配后,ioc容器= " + ioc);
}

在这里插入图片描述

六. 任务6-完成控制器方法获取参数-@RequestParam

@RequestMapping(value = "/find")
public void findMonster(HttpServletRequest request,
                         HttpServletResponse response,
                         @RequestParam(value = "name") String name){
     response.setContentType("text/html;charset=utf-8");
     try {
         System.out.println("接收到的name= " + name);
         List<Monster> monsters = monsterService.findMonster(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></tr>");
         }
         content.append("</table>");
         PrintWriter writer = response.getWriter();
         writer.write(content.toString());
     } catch (IOException e) {
         e.printStackTrace();
     }
 }

修改HspDispatcherServlet.java

private void executeDispather(HttpServletRequest request,HttpServletResponse response){
        HspHandler hspHandler = getHspHandler(request);
        try {
            if (hspHandler == null) {
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            }else {
                //将HttpServletRequest HttpServletResponse封装到参数数组
                //1.得到目标方法的所有参数(形参)信息
                Class<?>[] parameterTypes = hspHandler.getMethod().getParameterTypes();
                //2.创建一个参数(实参)数组
                Object[] params = new Object[parameterTypes.length];
                //3.遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    //将形参填充到实参数组中
                    if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                        params[i] = request;
                    }else if("HttpServletResponse".equals(parameterType.getSimpleName())){
                        params[i] = response;
                    }
                }

                //匹配成功,反射调用控制器的方法
                /**
                 * 1.下面这样写法,只适用于目标方法参数是HttpServletRequest request, HttpServletResponse response
                 * 2.将需要传递给目标方法的实参=>封装到参数数组中=>然后以反射调用的方式传递给目标方法
                 */
                //hspHandler.getMethod().invoke(hspHandler.getController(),request,response);
                hspHandler.getMethod().invoke(hspHandler.getController(),params);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    String value() default "";
}
public interface MonsterService {
    public List<Monster> listMonster();
    public List<Monster> findMonster(String name);
}
@Override
    public List<Monster> findMonster(String name) {
        List<Monster> list = new ArrayList<>();
        list.add(new Monster(100,"牛魔王","芭蕉扇"));
        list.add(new Monster(200,"蜘蛛精","吐丝啦"));
        list.add(new Monster(300,"蜘蛛精2","吐丝啦"));
        list.add(new Monster(400,"蜘蛛精3","吐丝啦"));
        list.add(new Monster(500,"蜘蛛精4","吐丝啦"));


        List<Monster> findMonsters = new ArrayList<>();
        for (Monster monster : list) {
            if (monster.getName().contains(name)) {
                findMonsters.add(monster);
            }
        }
        return findMonsters;
    }

修改HspDispatcherServlet.java

 //编写方法,返回请求参数是目标方法的第几个形参
public int getIndexRequestParameterIndex(Method method,String name){
    //得到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 requestParameterAnnotation = parameter.getAnnotation(RequestParam.class);
            String value = requestParameterAnnotation.value();
            if (name.equals(value)) {
                //找到请求的参数,对应的目标方法的形参的位置
                return i;
            }
        }
    }
    return -1;
}
//编写方法,得到目标方法的所有形参的名称,并放入到集合中
public List<String> getParameterNames(Method method){
    List<String> parameterList = new ArrayList<>();
    Parameter[] parameters = method.getParameters();
    for (Parameter parameter : parameters) {
        parameterList.add(parameter.getName());
    }
    System.out.println("目标方法的形参列表= " + parameterList);
    return parameterList;
}
private void executeDispather(HttpServletRequest request,HttpServletResponse response){
        HspHandler hspHandler = getHspHandler(request);
        try {
            if (hspHandler == null) {
                response.getWriter().print("<h1>404 NOT FOUND</h1>");
            }else {
                //将HttpServletRequest HttpServletResponse封装到参数数组
                //1.得到目标方法的所有参数(形参)信息
                Class<?>[] parameterTypes = hspHandler.getMethod().getParameterTypes();
                //2.创建一个参数(实参)数组
                Object[] params = new Object[parameterTypes.length];
                //3.遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    //将形参填充到实参数组中
                    if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                        params[i] = request;
                    }else if("HttpServletResponse".equals(parameterType.getSimpleName())){
                        params[i] = response;
                    }
                }

                //将http请求参数封装到params数组中(要注意参数顺序)
                Map<String, String[]> parameterMap = request.getParameterMap();
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    String name = entry.getKey();
                    String value = entry.getValue()[0];
                    int indexRequestParameterIndex = getIndexRequestParameterIndex(hspHandler.getMethod(), name);
                    if (indexRequestParameterIndex != -1) {
                        params[indexRequestParameterIndex] = value;
                    }else {
                        //没有找到@RequestParam注解,就使用默认的机制进行匹配
                        //1.得到目标方法的所有形参名称
                        List<String> parameterNames = getParameterNames(hspHandler.getMethod());
                        //2.对得到的目标方法的所有形参进行遍历
                        for (int i = 0; i < parameterNames.size(); i++) {
                            if (name.equals(parameterNames.get(i))) {
                                params[i] = value;
                                break;
                            }
                        }


                    }
                }

                //匹配成功,反射调用控制器的方法
                /**
                 * 1.下面这样写法,只适用于目标方法参数是HttpServletRequest request, HttpServletResponse response
                 * 2.将需要传递给目标方法的实参=>封装到参数数组中=>然后以反射调用的方式传递给目标方法
                 */
                //hspHandler.getMethod().invoke(hspHandler.getController(),request,response);
                hspHandler.getMethod().invoke(hspHandler.getController(),params);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

七. 任务7-完成简单视图解析

通过方法返回的String转发或者重定向到指定页面

@RequestMapping("/login")
public String login(HttpServletRequest request,
                    HttpServletResponse response,
                    String loginName){
    boolean login = monsterService.login(loginName);
    request.setAttribute("loginName",loginName);
    if (login){
        //重定向
        //return "redirect:/loginOk.jsp";
        //默认是转发
        //return "liginOk.jsp";
        //转发
        return "forward:/login_ok.jsp";
    }else {
        return "forward:/login_error.jsp";
    }
}
<body>
<h1>妖怪登录页面</h1>
<form action="/monster/login" method="post">
    妖怪名: <input type="text" name="loginName"/><br/>
    <input type="button" value="登录"/>
</form>
</body>

在HspDispatcherServlet.java中的executeDispather()最后面加上如下代码

	Object result = hspHandler.getMethod().invoke(hspHandler.getController(), params);
	if (result instanceof String) {
	    String viewName = (String) result;
	    if (viewName.contains(":")) {
	        //说明是forward:/xx  或者是 redirect:/xxx
	        String viewType = viewName.split(":")[0];
	        String viewPage = viewName.split(":")[1];
	        if ("forward".equals(viewType)) {
	            request.getRequestDispatcher(viewPage).forward(request,response);
	        }else if ("redirect".equals(viewType)){
	            response.sendRedirect(viewPage);
	        }else {
	            //默认是转发
	            request.getRequestDispatcher(viewName).forward(request,response);
	        }
	    }
	}

八. 任务8-完成返回json格式数据-@ResponseBody

在pom.xml文件中引入jackson jar 包

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface  ResponseBody {
}
//返回json格式数据
 @RequestMapping("/json")
 @ResponseBody
 public List<Monster> listMonsterByJson(HttpServletRequest request,
                                             HttpServletResponse response){
     List<Monster> monsters = monsterService.listMonster();
     return monsters;
 }

json底层是一个List数组,所以要用数组来判断result的类型

	 Object result = hspHandler.getMethod().invoke(hspHandler.getController(), params);
       if (result instanceof String) {
            String viewName = (String) result;
            if (viewName.contains(":")) {
                //说明是forward:/xx  或者是 redirect:/xxx
                String viewType = viewName.split(":")[0];
                String viewPage = viewName.split(":")[1];
                if ("forward".equals(viewType)) {
                    request.getRequestDispatcher(viewPage).forward(request,response);
                }else if ("redirect".equals(viewType)){
                    response.sendRedirect(viewPage);
                }else {
                    //默认是转发
                    request.getRequestDispatcher(viewName).forward(request,response);
                }
            }
        }else if (result instanceof ArrayList){
            //返回json格式数据
            Method method = hspHandler.getMethod();
            if (method.isAnnotationPresent(ResponseBody.class)) {
                //把返回值转成json字符串(使用Jackson工具类)
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(result);
                response.setContentType("text/html;charset=utf-8");
                PrintWriter writer = response.getWriter();
                writer.print(json);
                writer.flush();
                writer.close();
            }
        }
    }
} catch (Exception e) {
    e.printStackTrace();
}
}

在这里插入图片描述

9. 数据格式化

在我们提交数据(表单)Spring MVC怎样对提交的数据进行转换和处理的
1.基本数据类型可以和字符串之间完成转换

<h1>Spring MVC[数据格式/验证等]</h1>
<hr/>
<a href="addMonsterUI">添加妖怪</a>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head>
<title>Spring MVC[数据格式/验证等]</title>

</head>
<body>

<form:form action="save" method="post" modelAttribute="monster">
    妖怪名字:<form:input path="name"/><br/><br/>
    妖怪年龄:<form:input path="age"/><br/><br/>
    电子邮件:<form:input path="email"/><br/><br/>
    <input type="submit" value="添加妖怪"/>
</form:form>

</body>
</html>
@Controller
public class MonsterController {

    @RequestMapping("/addMonsterUI")
    public String addMonsterUI(Map<String,Object> map){
        map.put("monster",new Monster());
        return "datavalid/monster_addUI";
    }

    @RequestMapping("/save")
    public  String save(Monster monster){
        System.out.println("--save--monster= " + monster);
        return "datavalid/success";
    }
}

2.特殊数据类型可以和字符串之间完成转换,使用注解
在Monster.java中加入以下属性

@DateTimeFormat(pattern = "yyyy-MM-dd")
 private Date birthday;

 @NumberFormat(pattern = "###,###,###")
 private float salary;

若birthday和salary是按照格式输入,则通过,否则就报错400

10. 中文乱码处理

1.自定义中文乱码处理
在web.xml文件中配置

<!--配置自定义中文乱码处理器-->
<filter>
  <filter-name>myCharacterFilter</filter-name>
  <filter-class>com.zzti.springmvc.filter.MyCharacterFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>myCharacterFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
package com.zzti.springmvc.filter;

import javax.servlet.*;
import java.io.IOException;

public class MyCharacterFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding("utf-8");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}
========================================================
--save--monster= Monster{id=null, email='123@163.com', age=21, name='张三', birthday=Sun Dec 12 00:00:00 CST 1999, salary=123.12}

2.Spring提供的处理中文乱码过滤器
修改web.xml,换成Spring提供的过滤器,处理中文乱码

<!--使用Spring提供的过滤器,处理中文乱码-->
<filter>
  <filter-name>CharacterEncodingFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>CharacterEncodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值