SpringMVC学习总结 + 【手写SpringMVC底层机制核心】

SpringMVC笔记

SpringMVC介绍

基本介绍

  1. SpringMVC 是WEB 层框架, 接管了Web 层组件, 支持MVC 的开发模式/开发架构
  2. SpringMVC 通过注解,让POJO 成为控制器,不需要继承类或者实现接口
  3. SpringMVC 采用低耦合的组件设计方式,具有更好扩展和灵活性.
  4. 支持REST 格式的URL 请求.

Spring MVC和Spring之间的关系

  1. Spring MVC 只是Spring 处理WEB 层请求的一个模块/组件, Spring MVC 的基石是Servlet 【Java WEB】
  2. Spring Boot 是为了简化开发者的使用, 推出的封神框架(约定优于配置,简化了Spring的配置流程), SpringBoot 包含很多组件/框架,Spring 就是最核心的内容之一,也包含SpringMVC
  3. Spring Boot > Spring > Spring MVC

@RequestMapping

基本介绍

@RequestMapping 注解可以指定控制器/处理器的某个方法的请求的url

 @RequestMapping(value = "/buy")
    public String buy(){
        System.out.println("购买商品");
        return "success";
    }

其它使用方式

  1. @RequestMapping 可以修饰方法和类

    • @RequestMapping 注解可以修饰方法,还可以修饰类当同时修饰类和方法时,
      请求的url 就是组合/类请求值/方法请求值
    @RequestMapping(value = "/user" )
    @Controller 
    public class UserHandler {
        
        @RequestMapping(value = "/buy")
        public String buy(){
            System.out.println("购买商品");
            return "success";
        }
    }
    

    此时url = /user/buy

  2. @RequestMapping 可以指定请求方式

    • @RequestMapping 还可以指定请求的方式(post/get/put/delete..), 请求的方式需
      要和指定的一样,否则报错
@RequestMapping(value = "/find", method = RequestMethod.GET)
    public String search(String bookId) {
        System.out.println("查询书籍bookId= " + bookId);
        return "success";
    }
  • SpringMVC 控制器默认支持GET 和POST 两种方式, 也就是你不指定method , 可以接收
    GET 和POST 请求
 @RequestMapping(value = "/buy")//默认支持GET 和POST 两种方式
    public String buy(){
        System.out.println("购买商品");
        return "success";
    }

3.@RequestMapping 可指定params 和headers 支持简单表达式

  1. param1: 表示请求必须包含名为param1 的请求参数

  2. !=param1: 表示请求不能包含名为param1 的请求参数

  3. param1 != value1: 表示请求包含名为param1 的请求参数,但其值不能为value1

  4. {"param1=value1", "param2"}: 请求必须包含名为param1 和param2 的两个请求参数,
    且param1 参数的值必须为value1

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

4.@RequestMapping 支持Ant 风格资源地址

  1. ?:匹配文件名中的一个字符
  2. *:匹配文件名中的任意字符
  3. **:匹配多层路径
    • Ant 风格的url 地址举例
      /user/*/createUser: 匹配/user/aaa/createUser、/user/bbb/createUser 等URL
      /user/**/createUser: 匹配/user/createUser、/user/aaa/bbb/createUser 等URL
      /user/createUser??: 匹配/user/createUseraa、/user/createUserbb 等URL

5.@RequestMapping 可配合@PathVariable 映射URL 绑定的占位符

  • 不需要在url 地址上带参数名了,更加的简洁明了
// 我们希望目标方法获取到username 和userid, value="/xx/{username}" -@PathVariable("username")
    @RequestMapping(value = "/reg/{username}/{userId}")
    public String register(@PathVariable("username") String username,
                             @PathVariable("userId") String userId){
        System.out.println("接收到参数--" + "username= " + username + "--" + "usreid= " + userId);
        return "success";
    }
<a href="user/reg/kristina/300">占位符的演示</a>

6.请求的简写形式

​ @GetMapping @PostMapping @PutMapping @DeleteMapping

Rest请求风格

基本介绍

  1. REST:即Representational State Transfer。(资源)表现层状态转化。是目前流行的请求方式。它结构清晰, 很多网站采用。
  2. HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:
  3. GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。

传统的请求方法

在url中:
getBook?id=1 GET
delete?id=1 GET
update POST
add POST

说明: 传统的url 是通过参数来说明crud 的类型,rest 是通过get/post/put/delete 来说明crud 的类型

REST 的核心过滤器

  1. 当前的浏览器只支持post/get 请求,因此为了得到put/delete 的请求方式需要使用Spring
    提供的HiddenHttpMethodFilter 过滤器进行转换.
  2. HiddenHttpMethodFilter:浏览器form 表单只支持GET 与POST 请求,而DELETE、PUT
    等method 并不支持,Spring 添加了一个过滤器,可以将这些请求转换为标准的http 方
    法,使得支持GET、POST、PUT 与DELETE 请求
  3. HiddenHttpMethodFilter 能对post 请求方式进行转换
  4. 过滤器需要在web.xml 中配置

配置过滤器

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

当我们需要 用delete/put 的方式时,

name="_method" 名字需要写成_method 因为后台的HiddenHttpMethodFilter
就是按这个名字来获取hidden 域的值,从而进行请求转换的.

<form action="user/book/100" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="修改书籍~">
</form>
public String delBook(@PathVariable("id") String id) {
System.out.println("删除书籍id= " + id);
//return "success"; [如果这样返回会报错JSPs only permit GET POST orHEAD]
return "redirect:/user/success"; //重定向到一个没有指定method 的Handler 方法
}

注意事项

  1. HiddenHttpMethodFilter, 在将post 转成delete / put 请求时,是按_method 参数名来读取的
  2. 如果web 项目是运行在Tomcat 8 及以上,会发现被过滤成DELETE 和PUT 请求,到达控制器时能顺利执行,但是返回时(forward)会报HTTP 405 的错误提示:消息JSP 只允许GET、POST 或HEAD。
  3. 因此,将请求转发(forward)改为请求重定向(redirect):重定向到一个Handler,由Handler 转发到页面

SpringMVC映射请求数据

获取参数值

/**
* @RequestParam(value="name", required=false)
* 1.@RequestParam : 表示说明一个接受到的参数
* 2.value="name" : 接收的参数名是name
* 3.required=false : 表示该参数可以有,也可以没有,如果required=true,表示必须传递该参数.
* 默认是required=true
*/
@RequestMapping("vote01")
    public String test01(@RequestParam(value = "name",required = false) String username){
        System.out.println("得到的username= "+username);
        return "success";
    }
  1. @RequestParam 表示会接收提交的参数
  2. value = "name" 表示提交的参数名是name
  3. required = false 表示该参数可以没有,默认是true 表示必须有这个参数
  4. 当我们使用@RequestParam(value = "name",required = false) 就表示 请求的参数名和目标方法的形参名 可以不一致

获取Http请求消息头

@RequestMapping(value = "/vote02")
    public String test02(@RequestHeader(value = "Accept-Encoding")String ae,
                         @RequestHeader(value = "Host")String host) {
        System.out.println("Accept-Encoding =" + ae);
        System.out.println("Host =" + host);
        //返回到一个结果
        return "success";
    }

获取javabean 形式的数据

@RequestMapping(value = "/vote03")
    public String test03(Master master) {
        System.out.println("主人信息= " + master);
        //返回结果
        return "success";
    }
  1. 方法的形参 (Master master) 用对应的对象类型指定即可,SpringMVC会自动的进行封装

  2. 如果自动的完成封装,要求提交的数据参数名和对象的字段名保持一致

  3. 支持级联数据获取: 如果对象的属性任然是一个对象,就通过 字段名.字段名 来提交

    比如Master[Pet] ,可以通过 pet.id ,pet.name 来指定

  4. 如果参数名和字段名不匹配,则对象的属性就是null

ServletAPI

  • 开发中原生的ServletAPI仍然可以使用
 @RequestMapping(value = "/vote04")
    public String test04(HttpServletRequest request,HttpServletResponse response) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        System.out.println("username= "+username+" ,password= "+password);
		//返回结果
        return "success";
    }
  • 除了HttpServletRequest, HttpServletResponse 还可以其它对象也可以这样的形式获取
  1. HttpSession、java.security.Principal,InputStream,OutputStream,Reader,Writer

模型数据

数据放入Request域

通过HttpServletRequest放入Request域

  • 通过HttpServletRequest放入Request域是默认机制
 @RequestMapping(value = "/vote05")
    public String test05(Master master) {
        return "vote_ok";
    }
  1. 当我们提交的数据和对象名保持一致,那么SpringMVC回自动封装成一个对象【在前面 获取javabean 形式的数据 讲过】
  2. SpringMVC还会有一个机制,会自动将封装的这个对象【model模型】放入Request域
  3. 也可以手动将对象放入Request域
  4. 会以 k-v 的形式放入Request域,k 是类名首字母小写,v 是对象
  • 如果我们需要向Request域中添加新的属性/对象

     request.setAttribute("address","beijing");
    
  • 如果我们要修改默认机制自动放入的对象的属性

master.setName("pp");

通过请求的方法参数Map<String,Object>放入Request域

 @RequestMapping(value = "/vote06")
    public String test06(Master master, Map<String,Object> map) {
        map.put("address","tianjing");
        map.put("master",null);
        //返回到一个结果
        return "vote_ok";
    }
  • SpringMVC会遍历Map,然会将map中的 k-v 存放到Request域
  • 如果 map.put("master",null); 会覆盖默认机制的master,为null

通过返回ModelAndView对象实现Request域数据

@RequestMapping(value = "/vote07")
    public ModelAndView test07(Master master) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("address","shanghai");
        modelAndView.addObject("master",null);
        //指定跳转的视图名称
        modelAndView.setViewName("vote_ok");
        return modelAndView;
    }
  • SpringMVC会遍历ModelAndView,然会将ModelAndView中的 k-v 存放到Request域
  • 如果 modelAndView.addObject("master",null); 会覆盖默认机制的master,为null

注意事项:

  1. 从本质上看,请求响应的方法 return "xx",是返回了一个字符串,其实本质是返回一个ModelAndView对象,只是默认被封装起来了
  2. ModelAndView对象既可以包含model数据,也可以包含视图信息
  3. ModelAndView对象的addObject("","");方法 可以添加key -value数据,默认在Request域中
  4. ModelAndView对象setView方法是指定 视图名称

数据放入Session域

@RequestMapping(value = "/vote08")
    public String test08(Master master,HttpSession session){
        session.setAttribute("master",master);
        session.setAttribute("address","guangzhou");
        return "vote_ok";
    }

@ModelAttribute

 @ModelAttribute
    public void prepareModel(){
        System.out.println("prepareModel()-----完成准备工作-----");
    }
  1. 在某个方法上加上 @ModelAttribute 注解,那么在调用该Handler的任何方法都会调用这个方法
  2. 类似Aop的前置通知

视图和视图解析器

基本介绍

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

自定义视图

  1. 在默认情况下,我们都是返回默认的视图, 然后这个返回的视图交由SpringMVC 的 InternalResourceViewResolver 视图处理器来处理的

​ 前缀 value="/WEB-INF/pages/" 和 后缀 value=".jsp" 之后会拼接 返回给视图解析器的返回值

<!--    配置默认视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

    <!--    配置属性  prefix 前缀  和  suffix 后缀-->
    <!--    这里的前缀 value="/WEB-INF/pages/"  和 后缀 value=".jsp" 之后会拼接 返回给视图解析器的返回值
            例如 UserSerlet return "login ok";
            就会拼接成 /WEB-INF/pages/login_ok.jsp  从而进行跳转-->
        <property name="prefix" value="/WEB-INF/pages/"/>

        <property name="suffix" value=".jsp"/>
    </bean>
2. 在实际开发中,我们有时需要自定义视图,这样可以满足更多更复杂的需求.

需要在spring配置文件 , 增加自定义视图解析器

<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
	<property name="order" value="99"></property>
</bean>
  • name="order" :表示给这个解析器设置优先级,
  • 我们自己的视图解析优先级高,Order 值越小,优先级越高

编写自己的视图

  • 继承 AbstractView 就可以作为一个视图使用
  • @Component(value = "myView")会作为id= myView 的一个组件 注入到容器中
@Component(value = "zyView")
public class MyView extends AbstractView {
    @Override
    protected void renderMergedOutputModel(Map<String, Object> map,
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {

        //该方法完成视图渲染
        //并且可以确定我们要跳转的页面 /WEB-INF/pages/my_view.jsp
        System.out.println("进入到自己的视图..");

        //请求转发到 /WEB-INF/pages/my_view.jsp
        //第一个斜杠会解析成 工程路径-> springmvc/
        httpServletRequest.getRequestDispatcher("/WEB-INF/pages/my_view.jsp")
                .forward(httpServletRequest,httpServletResponse);
    }
}

进行跳转

@RequestMapping("/goods")
@Controller
public class GoodsHandler {
    @RequestMapping(value = "/buy")
    public String buy() {
        System.out.println("--buy()被调用--");
       return "zyView";
    }
}

自定义视图小结

  1. 自定义视图: 创建一个View 的bean, 该bean 需要继承自AbstractView, 并实现 renderMergedOutputModel 方法.

  2. 并把自定义View 加入到IOC 容器中

  3. 自定义视图的视图处理器,使用BeanNameViewResolver, 这个视图处理器也需要配置到ioc 容器

  4. BeanNameViewResolver 的调用优先级需要设置一下,设置order 比Integer.MAX_VAL 小的值. 以确保其在InternalResourceViewResolver 之前被调用

自定义视图-工作流程

  1. SpringMVC 调用目标方法, 返回自定义View 在IOC 容器中的id

  2. SpringMVC 调用BeanNameViewResolver 解析视图: 从IOC 容器中获取返回id 值对应的bean, 即自定义的View 的对象

  3. SpringMVC 调用自定义视图的renderMergedOutputModel 方法渲染视图

  4. 如果在SpringMVC 调用目标方法, 返回自定义View 在IOC 容器中的id不存在, 则仍然按照默认的视图处理器机制处理

自定义解析器的执行流程-源码

/**
 * 自定义解析器的执行流程
 *  1.
 *    @RequestMapping(value = "/buy")
 *     public String buy() {
 *         System.out.println("--buy()被调用--");
 *        return "zyView";  -->
 *     }
 *  2.
 *  ApplicationContext context = obtainApplicationContext();
 * 		if (!context.containsBean(viewName)) {//判断viewName是否在容器中
 * 			// Allow for ViewResolver chaining...
 * 			return null;
 *                }
 * 		if (!context.isTypeMatch(viewName, View.class)) {//判断是否继承了 AbstractView 实际上是判断是否实现了View接口 因为AbstractView实现了View接口
 * 			if (logger.isDebugEnabled()) {
 * 				logger.debug("Found bean named '" + viewName + "' but it does not implement View");
 *            }
 * 			// Since we're looking into the general ApplicationContext here,
 * 			// let's accept this as a non-match and allow for chaining as well...
 * 			return null;
 *        }
 * 		return context.getBean(viewName, View.class);    -->
 * 	}
 * 	3.
 * 	 protected void renderMergedOutputModel(Map<String, Object> map,
 * HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
 *
 *         //该方法完成视图渲染
 *         //并且可以确定我们要跳转的页面 /WEB-INF/pages/my_view.jsp
 *         System.out.println("进入到自己的视图..");
 *
 *         //请求转发到 /WEB-INF/pages/my_view.jsp
 *         //第一个斜杠会解析成 工程路径-> springmvc/
 *         httpServletRequest.getRequestDispatcher("/WEB-INF/pages/my_view.jsp")
 *                 .forward(httpServletRequest,httpServletResponse); -->
 *     }
 * }
 * 	4.
 * 	<h1>进入到my_view页面</h1>
 * <p>是从自定义视图来的...</p>
 *
 *
 */

默认解析器的执行流程-源码

/**
 * 默认解析器的执行流程
 * 1.
 * public String buy() {
 *         System.out.println("--buy()被调用--");
 *        return "zyView";->
 *     }
 * 2.
 * public InternalResourceViewResolver(String prefix, String suffix) {
 * 		this();
 * 		setPrefix(prefix);//用于拼接
 * 		setSuffix(suffix);//用于拼接
 *  }
 * 3.
 * protected AbstractUrlBasedView buildView(String viewName) throws Exception {
 * 		InternalResourceView view = (InternalResourceView) super.buildView(viewName);//调用super.buildView(viewName)
 * 		if (this.alwaysInclude != null) {
 * 			view.setAlwaysInclude(this.alwaysInclude);
 *                }
 * 		view.setPreventDispatchLoop(true);
 * 		return view;
 * }
 * 4.
 * 找不到 报错404
 */

找不到自定义解析器 会调用默认解析器

/**
 * 找不到自定义解析器 会调用默认解析器
 * 1.
 * public String buy() {
 *         System.out.println("--buy()被调用--");
 *        return "zyView"; -->
 *     }
 * 2.
 *  ApplicationContext context = obtainApplicationContext();
 * 		if (!context.containsBean(viewName)) {//找不到返回null
 * 			// Allow for ViewResolver chaining...
 * 			return null;-->
 *     }
 * 3.
 *  if (this.viewResolvers != null) {
 * 			for (ViewResolver viewResolver : this.viewResolvers) { //遍历解析器
 * 				View view = viewResolver.resolveViewName(viewName, locale);//此时以及走默认解析器那一套了
 * 				if (view != null) {
 * 					return view;//进行拼接前缀和后缀 但找不到
 *            }
 *    }
 *  4.
 *  拼接后找不到 报错 404
 */

默认解析器一旦解析 不会去自定义解析器

  • 因为默认解析器会拼接 view != null 就return了
	 * if (this.viewResolvers != null) {
     * 			for (ViewResolver viewResolver : this.viewResolvers) {
     * 				View view = viewResolver.resolveViewName(viewName, locale);
     * 				if (view != null) {
     * 					return view;
     *              }
     *   }

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

  • 默认返回的方式是请求转发,然后用视图处理器进行处理
 @RequestMapping(value = "/buy")
    public String buy(){
        return "success";
    }
  • 也可以在目标方法直接指定重定向或转发的url 地址

请求转发:return "forword:路径"

return "forword:/WEB-INF/pages/my_view.jsp"

重定向:return "redirect:路径"

return "redirect:login.jsp"

注意事项:

  1. 对于重定向,不能重定向到WEB-INF目录下

    重定向在响应头返回的URL中是 /工程路径/login.jsp【此时,浏览器会将第一个 / 处理成 IP:端口/工程路径/login.jsp】

  2. 对于请求转发,是在服务器内部进行,第一个 / 解析成 工程路径,进行转发

数据格式化

基本介绍

引出:在我们提交数据(比如表单时)SpringMVC 怎样对提交的数据进行转换和处理的??

  1. 基本数据类型可以和字符串之间自动完成转换
  2. Spring MVC 上下文中内建了很多转换器,可完成大多数Java 类型的转换工作

基本数据类型和字符串自动转换

<form:form action="save" method="post" modelAttribute="monster">
    妖怪名字: <form:input path="name"/> <form:errors path="name"/> <br><br>
    妖怪年龄~: <form:input path="age"/> <form:errors path="age"/><br><br>
    电子邮件: <form:input path="email"/> <form:errors path="email"/><br><br>
    妖怪生日: <form:input path="birthday"/> <form:errors path="birthday"/>要求以"9999-11-11"的形式<br><br>
    妖怪薪水: <form:input path="salary"/> <form:errors path="salary"/>要求以"123,890.12"的形式<br><br>
    <input type="submit" value="添加妖怪"/>
</form:form>
  1. SpringMVC 表单标签在显示之前必须在request 中有一个bean, 该bean 的属性和表单标签的字段要对应!
  2. request 中的key 为: form 标签的modelAttribute 属性值, 比如这里的monsters
  3. SpringMVC 的form:form 标签的action 属性值中的/ 不代表WEB 应用的根目录.
 @RequestMapping(value = "/addMonsterUI")
    public String addMonsterUI(Map<String, Object> map) {
        //这里需要给request 增加一个monster , 因为jsp 页面的modelAttribute="monster"需要
        //是springMVC 的内部的检测机制即使是一个空的也需要,否则报错
        map.put("monster", new Monster());
        return "datavalid/monster_addUI";
    }

说明:

  1. 当我们在浏览器发送 age=10 时,会把10转换成String类型,到达后端后,又会把String转成 int/Integer
  2. 而发送 age=aaa 时,会把aaa转成 string类型,到达后端后,把String类型 的 aaa转换成 int/Integer,此时会报错

特殊数据类型和字符串间的转换

  • 特殊数据类型和字符串之间的转换使用注解(比如日期,规定格式的小数比如货币形式等)

  • 对于日期和货币可以使用@DateTimeFormat 和@NumberFormat 注解. 把这两个注解标记在字段上即可.(JavaBean上)

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

验证国际化

  1. 对输入的数据(比如表单数据),进行必要的验证,并给出相应的提示信息
  2. 对于验证表单数据,springMVC 提供了很多实用的注解, 这些注解由JSR 303 验证框架提供.

JSR 303 验证框架

  1. JSR 303 是Java 为Bean 数据合法性校验提供的标准框架,它已经包含在JavaEE 中

  2. JSR 303 通过在Bean 属性上标注类似于@NotNull、@Max 等标准的注解指定校验规则,
    并通过标准的验证接口对Bean 进行验证

  3. JSR 303 提供的基本验证注解有:

Hibernate Validator 扩展注解

  1. Hibernate Validator 和Hibernate 没有关系,只是JSR 303 实现的一个扩展.

  2. Hibernate Validator 是JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支
    持以下的扩展注解:

  3. 扩展注解有如下:

	@NotNull(message = "年龄必须1-100")
    @Range(min = 1,max = 100)
    private Integer age;

    @NotNull
    private String name ;
    @NotNull(message = "生日必须符合格式")
    @DateTimeFormat(pattern = "yyy-MM-dd")
    private Date birthday;
    @NotNull(message = "salary必须符合格式")
    @NumberFormat(pattern = "###,###.##")
    private Float salary;
	@RequestMapping(value = "/save")
    public String save(@Valid Monster monster, Errors errors, Map<String,Object> map) {}
  1. @Valid Monster monster:表示对monster 接收的数据进行校验
  2. Errors errors: 表示如果校验出现错误,将校验的错误信息保存到 errors 中
  3. Map<String,Object> map:如果校验出现错误,会将校验的错误信息保存到map,并且同时保存 monster对象
  • 在前端使用<form:errors path="name"/> 回显错误信息
 	妖怪名字: <form:input path="name"/> <form:errors path="name"/> <br><br>
    妖怪年龄~: <form:input path="age"/> <form:errors path="age"/><br><br>
    电子邮件: <form:input path="email"/> <form:errors path="email"/><br><br>
    妖怪生日: <form:input path="birthday"/> <form:errors path="birthday"/>要求以"9999-11-11"的形式<br><br>
    妖怪薪水: <form:input path="salary"/> <form:errors path="salary"/>要求以"123,890.12"的形式<br><br>

自定义验证错误信息

1.需要在Spring配置文件中配置相关bean

 <!-- 配置国际化错误信息的资源处理bean -->
    <bean id="messageSource" class=
            "org.springframework.context.support.ResourceBundleMessageSource">
        <!-- 配置国际化文件名字
        如果你这样配的话,表示messageSource 回到src/i18nXXX.properties 去读取错误信息
        -->
        <property name="basename" value="i18n"></property>
    </bean>

2.需要在src目录下创建 i18nxxx.properties去读取错误信息

NotEmpty.monster.name=\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a
typeMismatch.monster.age=\u5e74\u9f84\u8981\u6c42\u5728\u0031\u002d\u0031\u0035\u0030\u4e4b\u95f4
typeMismatch.monster.birthday=\u751f\u65e5\u683c\u5f0f\u4e0d\u6b63\u786e
typeMismatch.monster.salary=\u85aa\u6c34\u683c\u5f0f\u4e0d\u6b63\u786e

3.用工具将希望自定义的错误信息转换成Unicode编码填入i18nxxx.properties

注意事项

  1. 在需要验证的Javabean/POJO 的字段上加上相应的验证注解.

  2. 目标方法上,在JavaBean/POJO 类型的参数前, 添加@Valid 注解. 告知SpringMVC该bean 是需要验证的

	@RequestMapping(value = "/save")
    public String save(@Valid Monster monster, Errors errors, Map<String,Object> map) {}
  1. 在@Valid 注解之后, 添加一个Errors 或BindingResult 类型的参数, 可以获取到验证的错误信息
  2. 需要使用<form:errors path="email"></form:errors> 标签来显示错误消息, 这个标签,需要写在form:form 标签内生效.
<form:form action="save" method="post" modelAttribute="monster">
    妖怪名字: <form:input path="name"/> <form:errors path="name"/> <br><br>
</form:form>
  1. 错误消息的国际化文件i18n.properties , 中文需要是Unicode 编码,使用工具转码.
    格式: 验证规则.表单modelAttribute 值.属性名=消息信息

    NotEmpty.monster.name=\u540D\u5B57\u4E0D\u80FD\u4E3A\u7A7A
    typeMismatch.monster.age=\u7C7B\u578B\u4E0D\u5339\u914D

  2. 注解@NotNull 和@NotEmpty 的区别说明

  • @NotEmpty 是判断 null 的 并且可以接收 任何类型

  • @NotNull 是判断null 和 empty的,接收String,collection,map和array

  • 如果是字符串验证空, 建议使用@NotEmpty

  1. SpingMVC 验证时,会根据不同的验证错误, 返回对应的信息

注解组合使用

  • 使用@NotNull + @Range 组合使用
 	@NotNull(message = "年龄必须1-100")
    @Range(min = 1,max = 100)
    private Integer age;

取消属性绑定 @InitBinder 注解

  • 不希望接收到某个表单对应的属性的值,则可以通过@InitBinder 注解取消绑定.
  1. 使用@InitBinder 标识的该方法,可以对WebDataBinder 对象进行初始化。
  2. WebDataBinder 是DataBinder 的子类,用于完成由表单字段到JavaBean 属性的绑定
  3. @InitBinder 方法不能有返回值,它必须声明为void。
  4. @InitBinder 方法的参数通常是是WebDataBinder
	@InitBinder
    public void initBinder(WebDataBinder webDataBinder){
        webDataBinder.setDisallowedFields("name");//表示取消属性name的绑定; 这里可以填写多个字段
    }
  • 取消属性的绑定,那么在JavaBean中的校验注解也应该去掉

中文乱码问题处理

  • 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>

处理JSON格式

@ResponseBody

  • 服务器 -----JSON数据-----> 客户端/浏览器
@RequestMapping(value = "/json/dog")
    @ResponseBody
    public Dog getJson(){
        Dog dog = new Dog("大黄", "贝加尔");
        return dog;
    }

@RequestBody

客户端/浏览器-----JSON数据-----> 服务器

@RequestMapping(value = "/save2")
@ResponseBody
public User save2(@RequestBody User user){
    System.out.println("user= "+user);
    return user;
}
  1. 将客户端/浏览器发送的json字符串数据封装成 JavaBean对象
  2. 再把这个 JavaBean对象 以 json对象形式返回
  3. (@RequestBody User user)在形参上指定
  4. SpringMVC就会将提交的json字符串数据填充给指定JavaBean

注意事项

  1. 目标方法正常返回json需要的数据,可以是对象也可以是集合
  2. @ResponseBody可以写在类上,这样对该类所有的方法生效
  3. @ResponseBody + @Controller 可以直接写成@RestController

HttpMessageConverter

  • SpringMVC 处理JSON-底层实现是依靠HttpMessageConverter来进行转换的

工作机制简图

  1. 使用HttpMessageConverter 将请求信息转化并绑定到处理方法的入参中, 或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:

    • 使用@RequestBody / @ResponseBody 对目标方法进行标注

    • 使用HttpEntity / ResponseEntity 作为目标方法的入参或返回值

  2. 当控制器处理方法使用到@RequestBody/@ResponseBody 或HttpEntity/ResponseEntity 时, Spring 首先根据请求头或响应头的Accept 属性选择匹配的HttpMessageConverter, 进而根据参数类型或泛型类型的过滤得到匹配的HttpMessageConverter, 若找不到可用的HttpMessageConverter 将报错

SpringMVC文件上传

  • 在SpringMVC 中,通过返回ResponseEntity的类型,可以实现文件下载的功能
  • 需要构建 ResponseEntity 对象,需要1.得到http响应头 2.http响应状态 3.下载文件的数据
@RequestMapping(value="/downFile")
    public ResponseEntity<byte[]> downFile(HttpSession session) throws  Exception{
        //1.先获取到要下载 的 InputStream
        InputStream resourceAsStream =
                session.getServletContext().getResourceAsStream("/img/1.jpg");

        //2.开辟存放文件的byte数组 -> 支持二进制数据
        //resourceAsStream.available() 返回资源文件的大小
        byte[] bytes = new byte[resourceAsStream.available() ];

        //3. 将要下载的文件数据读入到byte数组
        resourceAsStream.read(bytes);

        /*
        ResponseEntity 的构造器:
            public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {
            this(body, headers, (Object) status);
            }
         */
        //准备构造ResponseEntity 对象
        //4.构建httpStatus
        HttpStatus httpStatus = HttpStatus.OK;
        //5.构建http响应头
        HttpHeaders headers = new HttpHeaders();
        //指定浏览器以附件的形式处理回送数据
        headers.add("Content-Disposition","attachment;filename=1.jpg");
        ResponseEntity<byte[]> responseEntity =
                new ResponseEntity<>(bytes, headers, httpStatus);
        return responseEntity;
    }
  • 文件下载响应头的设置

    content-type 指示响应内容的格式

    content-disposition 指示如何处理响应内容,一般有两种方式:1. inline:直接在页面显示 2.attchment:以附件形式下载

SpringMVC文件上传

基本介绍

  1. Spring MVC 为文件上传提供了直接的支持,
  2. 这种支持是通过即插即用的MultipartResolver 实现的。
  3. Spring 用Jakarta Commons FileUpload 技术实现了一个MultipartResolver 实现类:CommonsMultipartResovler
  4. Spring MVC 上下文中默认没有装配MultipartResovler,因此默认情况下不能处理文件的上传工作,
  5. 如果想使用Spring 的文件上传功能,需现在上下文中配置MultipartResolver
 	<!--文件上传-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    </bean>
  • 需要在form表单添加属性 enctype="multipart/form-data"
<form action="<%=request.getContextPath()%>/fileUpload" method="post" enctype="multipart/form-data">
    文件介绍:<input type="text" name="introduce"><br>
    选择文件:<input type="file" name="file"><br>
    <input type="submit" value="上传文件">
</form>

文件上传

//编写方法 处理文件上传的请求
    @RequestMapping(value = "/fileUpload")
    public String fileUpload(@RequestParam(value = "file") MultipartFile file,
                             HttpServletRequest request, String introduce) throws IOException {

        //接收到提交的文件名
        String originalFilename = file.getOriginalFilename();
        System.out.println("提交的文件名= " + originalFilename);
        System.out.println("文件介绍= " + introduce);
        //获取到文件保存的目标位置/路径
        String filePath =
                request.getServletContext().getRealPath("/img/" + originalFilename);

        //创建文件
        File saveToFile = new File(filePath);

        //将上传的文件转存到 saveToFile
        file.transferTo(saveToFile);

        return "success";

    }

拦截器

基本介绍

  1. Spring MVC 也可以使用拦截器对请求进行拦截处理,
  2. 用户可以自定义拦截器来实现特定的功能.
  3. 自定义的拦截器必须实现HandlerInterceptor 接口

自定义拦截器的三个方法

  1. preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求request 进行处理。

  2. postHandle():这个方法在目标方法处理完请求后执行

  3. afterCompletion():这个方法在完全处理完请求后被调用,可以在该方法中进行一些资源
    清理的操作。

在Spring配置文件中配置拦截器

默认配置是都所有的目标方法都进行拦截, 也可以指定拦截目标方法, 比如只是拦截hi

 <mvc:interceptors>
        <ref bean="myInterceptor01"/>//直接引用对应拦截器
        <mvc:interceptor>
            <mvc:mapping path="/hi"/>
            <ref bean="myInterceptor01"/>
        </mvc:interceptor>
</mvc:interceptor>

mvc:mapping 支持通配符, 同时指定不对哪些目标方法进行拦截

 <mvc:interceptor>
            <mvc:mapping path="/h*"/>
            <mvc:exclude-mapping path="/hello"/>
            <ref bean="myInterceptor01"/>
        </mvc:interceptor>
  • 这样配置会拦截h打头的url指定的方法
<mvc:interceptor>
    <mvc:mapping path="/h*"/>
    <mvc:exclude-mapping path="/hello"/>
    <ref bean="myInterceptor02"/>
</mvc:interceptor>
  • 这样配置会拦截h打头的url指定的方法并且排除 url是hello的方法

自定义拦截器执行流程分析图

● 自定义拦截器执行流程说明

  1. 如果preHandle 方法返回false, 则不再执行目标方法, 可以在此指定返回页面
  2. postHandle 在目标方法被执行后执行. 可以在方法中访问到目标方法返回的 ModelAndView 对象
  3. 若preHandle 返回true, 则afterCompletion 方法在渲染视图之后被执行.
  4. 若preHandle 返回false, 则afterCompletion 方法不会被调用
  5. 在配置拦截器时,可以指定该拦截器对哪些请求生效,哪些请求不生效

注意事项

  1. 拦截器需要配置才生效,不配置是不生效的.
  2. 如果preHandler() 方法返回了false, 就不会执行目标方法(前提是你的目标方法被拦截了), 程序员可以在这里根据业务需要指定跳转页面.

多个拦截器

注意事项

  1. 如果第1 个拦截器的preHandle() 返回false , 后面都不在执行

  2. 如果第2 个拦截器的preHandle() 返回false , 就直接执行第1 个拦截器的
    afterCompletion()方法, 如果拦截器更多,规则类似

  3. 前面说的规则,都是目标方法被拦截的前提

异常处理

基本介绍

  1. Spring MVC 通过HandlerExceptionResolver 处理程序的异常,包括Handler 映射、数据绑定以及目标方法执行时发生的异常。

  2. 主要处理Handler 中用@ExceptionHandler 注解定义的方法。

  3. ExceptionHandlerMethodResolver 内部若找不到@ExceptionHandler 注解的话, 会找@ControllerAdvice 类的@ExceptionHandler 注解方法, 这样就相当于一个全局异常处理器

  4. 如果不去处理异常,tomcat会默认机制处理,用户看到的页面非常不友好

异常处理的优先级

局部异常 > 全局异常 > SimpleMappingExceptionResolver > tomcat默认机制

局部异常

//局部异常就是直接在这个Handler 中编写即可
    @ExceptionHandler({ArithmeticException.class, NullPointerException.class})
    public String localException(Exception ex,HttpServletRequest request){
        System.out.println("异常信息是~" + ex.getMessage());
        //如何将异常的信息带到下一个页面.
        request.setAttribute("reason", ex.getMessage());
        return "exception_mes";
    }

全局异常

@ControllerAdvice//@ControllerAdvice 表示了该注解,就是全局异常
public class MyGlobalException {

    @ExceptionHandler({NumberFormatException.class, ClassCastException.class})
    public String globalException(Exception ex, HttpServletRequest request){
        System.out.println("全局异常处理---"+ex.getMessage());
        request.setAttribute("reason",ex.getMessage());
        return "exception_mes";
    }
  • @ControllerAdvice 表示了该注解,就是全局异常

自定义异常

  • 通过@ResponseStatus 注解, 可以自定义异常
  • 格式:@ResponseStatus(reason = "异常原因",value = httpStatus状态 )
@ResponseStatus(reason = "年龄需要在1-120之间",value = HttpStatus.BAD_REQUEST )
public class AgeException extends RuntimeException{//需要继承RuntimeException/Exception
}
  • 自定义异常类需要继承RuntimeException/Exception
  • httpStatus会有很多状态

  • 如果想在其他页面看到reason信息,加上带String构造器即可

    public AgeException(String message) {
        super(message);
    }
    

统一处理异常信息

基本介绍

  1. 如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionResolver

  2. 它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常

  3. 需要在ioc 容器中配置

<!--    统一处理异常-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.Exception">allEx</prop>
        </props>
    </property>
</bean>
  • 在这个标签内 就可以配置出现异常需要跳转的页面allEx
  • key="java.lang.Exception" 是异常的范围,这样设置可以对未知异常进行统一处理,也就是所有异常都处理

SpringMVC执行流程以及源码剖析

SpringMVC执行流程示意图

执行流程-源码剖析

  1. 发出请求url

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
       logRequest(request);
    
  2. 调用处理器映射器

    //getWebApplicationContext() 就是Spring容器
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    //走完这一步前端控制器就有了Spring容器
    
    doDispatch(request, response);//进入分发
        2.1 HandlerExecutionChain mappedHandler = null;//有属性 处理器链
        2.2 mappedHandler = getHandler(processedRequest);// mappedHandler中已经有 目标Handler 和 拦截器链
    
  3. 调用处理器适配器

    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//得到处理器适配器
    String method = request.getMethod();得到请求方式
    
  4. 调用Handler

    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//调用Handler
    
    mav = invokeHandlerMethod(request, response, handlerMethod);
    
    invocableMethod.invokeAndHandle(webRequest, mavContainer);//调用Handler中的目标方法了
    
    ModelAndView modelAndView = new ModelAndView();//到达目标方法
    
    return invoke0(this.method, var1, var2);//目标方法执行完毕返回
    
    return getModelAndView(mavContainer, modelFactory, webRequest);
    
    return mav;//返回给前端处理器
    
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//回到起点,返回了一个 ModelAndView
    
  5. 调用视图解析器

    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    
    render(mv, request, response);//进行渲染
        //进入render 方法
            String viewName = mv.getViewName();//拿到视图名称
            view = resolveViewName(viewName, mv.getModelInternal(), locale, request);//进行视图解析
    
            //调用视图解析器
            if (this.viewResolvers != null) {
          for (ViewResolver viewResolver : this.viewResolvers) {
             View view = viewResolver.resolveViewName(viewName, locale);//解析完毕返回视图
             if (view != null) {
                return view;//返回视图给前端控制器
             }
         }
       }
    
    
    
  6. 视图渲染

    view.render(mv.getModelInternal(), request, response);
    
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);//渲染合并 输出模型
        //进入方法
            RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);//获得RequestDispatcher
    
  7. 返回响应

    rd.forward(request, response);//返回响应
    

手写SpringMVC底层机制!

  • 前景提要:实现的是SpringMVC核心机制

  • 对一些细枝末节的代码做了简化,比如字符串的处理...

  • 完成哪些机制

    • 机制一: 通过@RequestMapping ,可以标记一个方法,编写路径url,浏览器就能通过url完成调用
    • 机制二: 进行依赖注入,使之不需要传统的new 一个对象,而是直接从IOC容器中获得
    • 机制三:通过@RequestParam,如果浏览器传递的参数名和目标方法的形参不一致,可以通过value设置进行匹配
    • 机制四:在目标方法完成后,跳转到相关页面 请求转发/重定向
    • 机制五:在目标方法完成后,通过@Response注解,向浏览器发送JSON格式数据

手写添加配置

思路

  1. 需要配置pom.xml的依赖
  2. 需要写一个Servlet 作为前端控制器
  3. 需要配置Web.xml 中的前端控制器 1).url 2)配置spring容器配置文件的classpath: 3)跟随Tomcat 自启动
  4. 需要配置spring容器配置文件
  5. 需要配置spring容器配置文件 扫描的路径 <component-scan ...>

实现

  • 需要配置pom.xml的依赖
<!--  配置原生Servlet-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    <!--  <scope> 表示引入的jar的作用范围
          provided 表示该项目在打包 放到生产环境时,不需要带上 servlet-api.jar
          因为tomcat本身有 servlet-api.jar,到时直接使用tomcat本身的 servlet-api.jar-->
    </dependency>

  <!--  配置dom4j-->
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.1</version>
    </dependency>

  <!--  配置常用的工具类-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.12.0</version>
    </dependency>
  • 需要写一个Servlet 作为前端控制器
public class ZyDispatcherServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ZyDispatcherServlet-doPost--");
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ZyDispatcherServlet-doGet--");
    }
}
  • 需要配置Web.xml 中的前端控制器 1).url 2)配置spring容器配置文件的classpath: 3)跟随Tomcat 自启动

  • 需要配置spring容器配置文件 扫描的路径 <component-scan ...>

 <servlet>
    <servlet-name>ZyDispatcherServlet</servlet-name>
    <servlet-class>com.code_study.zyspringmvc.servlet.ZyDispatcherServlet</servlet-class>
    
    <!--配置参数,指定要操作的spring配置文件-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:zyspringmvc.xml</param-value>
    </init-param>

    <!--跟随tomcat自启动-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>ZyDispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  • 需要配置spring容器配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <!--    要扫描的包-->
    <component-scan base-package="com.code_study.controller,com.code_study.service"></component-scan>
</beans>

完成浏览器可以请求控制层

思路

  • 创建@Controller和自己的Controller
  • 编写工具类XMLParser,解析spring容器配置文件
  • 开发自己的 Spring容器,得到扫描类的全路径列表
  • 开发自己的 前端控制器,实例化对象到容器中
  • 完成请求的URL和控制器方法的映射关系
  • 完成前端控制器分发请求到对应控制器
  1. 自定义注解@Controller
  2. 创建Controller
  3. 需要写一个工具类XMLParser来解析在spring容器配置文件 扫描的路径 <component-scan ...> 的包 返回所有的路径
  4. 这个所有的路径 一个split(",")分隔,都进行扫描
  5. 需要写自己的 前端控制器
  6. 需要写自己的 Spring容器
  7. 在前端控制器中 需要添加方法 scanPackage() 扫描 XMLParser 解析出来的路径
  8. 在Spring容器中 需要添加一个属性 classFullPathList 来保存扫描出来的类的全路径
  9. 需要添加一个属性 ioc 来存放反射生成的bean对象 也就是过滤classFullPathList 中没有@Controller注解的一些路径 并实例化
  10. 需要添加类Handler 这个类要保存 一个url 对应的 一个控制器的方法的 映射 ,也就是说,根据这个url,可以找到对应控制器的对应方法
  11. 需要添加一个属性 HandlerList 用于 保存Handler 【url 和 控制器的映射】
  12. 需要添加三个方法 一个是initHandlerMapping(),完成 url 对应的 一个控制器的方法的 映射,即 将ioc 中bean 中的 方法进行反射,获取url,将 url,method,bean 封装成Handler 放入HandlerList 保存
  13. 添加第二个方法 getHandler(),需要将浏览器发送的request请求中的 uri拿出来,遍历HandlerList 进行配对,如果有 就返回对应的Handler
  14. 添加第三个方法 executeDispatch(),进行分发处理,需要 调用getHandler() 获取浏览器发送的request请求 对应的 Handler ,获取Handler 中的method 进行反射调用,method .invoke() 实现分发请求。

实现

  1. 自定义注解@Controller
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    String value() default "";
}
  1. 创建Controller
@Controller
public class MonsterController {
}
  1. 需要写一个工具类XMLParser来解析在spring容器配置文件 扫描的路径 <component-scan ...> 的包 返回所有的路径
public class XMLParser {

    public static String getBasePackage(String xmlFile){
        SAXReader saxReader = new SAXReader();
        ClassLoader classLoader = XMLParser.class.getClassLoader();
        InputStream resourceAsStream = classLoader.getResourceAsStream(xmlFile);
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            Element element = rootElement.element("component-scan");
            String basePackage = element.attribute("base-package").getText();
            return basePackage;
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
    }
}
  1. 这个所有的路径 一个split(",")分隔,都进行扫描
  2. 需要写自己的 前端控制器
  3. 需要写自己的 Spring容器
  4. 在前端控制器中 需要添加方法 scanPackage() 扫描 XMLParser 解析出来的路径
public void scanPackage(String pack) {
        //获得包所在的工作路径 [绝对路径]
        URL url =
                this.getClass().getClassLoader().//获取类的加载器
                        //得到指定包对应的工作路径 [绝对路径]
                                getResource("/" + pack.replaceAll("\\.", "/"));

        // System.out.println("url= "+url);
        //根据得到的路径,对其进行扫描,把类的全路径 保存到 classFullPathList
        String path = url.getFile();
        //在io中 把目录也是为一个文件
        File file = new File(path);
        //遍历file 【遍历出文件和子目录】
        for (File f : file.listFiles()) {
            if (f.isDirectory()) {//如果是目录
                //需要递归扫描 找子目录
                scanPackage(pack + "." + f.getName());
            } else {
                //的确是个文件
                //扫描到的文件可能是 .class 文件 也可能是其他文件
                //就算是.class 文件 也需要判断是否需要注入容器 有无加 @Controller注解
                //目前无法拿到注解 因为没法反射 所以先把文件的全路径都保存到 classFullPathList 之后在注入对象到容器时再处理
                String classFullPath =
                        //类的全路径不需要.class 去掉.class
                        pack + "." + f.getName().replaceAll(".class", "");

                //保存到 classFullPathList
                classFullPathList.add(classFullPath);
            }
        }
    }
  1. 在Spring容器中 需要添加一个属性 classFullPathList 来保存扫描出来的类的全路径
//保存扫描的包/子包类的全路径
    private List<String> classFullPathList =
            new ArrayList<>();
  1. 需要添加一个属性 ioc 来存放反射生成的bean对象 也就是过滤classFullPathList 中没有@Controller注解的一些路径 并实例化
//定义属性 ioc -> 存放反射生成的bean对象 比如Controller / Service /Dao
public ConcurrentHashMap<String, Object> ioc =
        new ConcurrentHashMap<>();
  1. 编写方法,将扫描到的类,在满足情况下 反射到ioc容器
//编写方法,将扫描到的类,在满足情况下 反射到ioc容器
    public void executeInstance() {
        if (classFullPathList.size() == 0) {
            //说明没有扫描到类
            return;
        }

        //遍历classFullList
        for (String classFullPath : classFullPathList) {
            try {
                Class<?> clazz = Class.forName(classFullPath);
                if (clazz.isAnnotationPresent(Controller.class)) {//处理@Controller
                    String className = clazz.getSimpleName();

                    Object instance = clazz.newInstance();
                    String value = clazz.getAnnotation(Controller.class).value();
                    if (!"".equals(value)) {
                        className = value;
                    } else {
                        className = StringUtils.uncapitalize(className);
                    }
                    ioc.put(className, instance);
                }
                else if (clazz.isAnnotationPresent(Service.class)) {//处理@Service
                    String className = clazz.getSimpleName();//类名

                    Service serviceAnnotation = clazz.getAnnotation(Service.class);
                    String value = serviceAnnotation.value();

                    if (!"".equals(value)) {
                        className = value;
                        Object instance = clazz.newInstance();
                        ioc.put(className, instance);
				}
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
  1. 需要添加类Handler 这个类要保存 一个url 对应的 一个控制器的方法的 映射 ,也就是说,根据这个url,可以找到对应控制器的对应方法
ZyHandler {
    private String url;
    private Method method;
    private Object controller;

    public ZyHandler() {
    }

    public ZyHandler(String url, Method method, Object controller) {
        this.url = url;
        this.method = method;
        this.controller = controller;
    }
//需要提供getter和setter方法...
  1. 需要添加一个属性 HandlerList 用于 保存Handler 【url 和 控制器的映射】
//定义属性 HandlerList -> 保存ZyHandler 【url 和 控制器的映射】
    private List<ZyHandler> HandlerList =
            new ArrayList<>();
  1. 需要添加三个方法 一个是initHandlerMapping(),完成 url 对应的 一个控制器的方法的 映射,即 将ioc 中bean 中的 方法进行反射,获取url,将 url,method,bean 封装成Handler 放入HandlerList 保存
 private void initHandlerMapping(){
        //遍历 ioc
        for (Map.Entry<String,Object> entry:  zyWebApplicationContext.ioc.entrySet()) {
            if (zyWebApplicationContext.ioc.isEmpty()){
                return;
            }
            Object bean = entry.getValue();
            Class<?> clazz = bean.getClass();
            if (clazz.isAnnotationPresent(Controller.class)){
                Method[] declaredMethods = clazz.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {
                    if (declaredMethod.isAnnotationPresent(RequestMapping.class)){

                        String url = declaredMethod.getAnnotation(RequestMapping.class).value();

                        ZyHandler zyHandler = new ZyHandler(url, declaredMethod, bean);

                        HandlerList.add(zyHandler);
                    }
                }
            }
        }
    }
  1. 添加第二个方法 getHandler(),需要将浏览器发送的request请求中的 uri拿出来,遍历HandlerList 进行配对,如果有 就返回对应的Handler
 //编写方法,通过request对象 返回ZyHandler对象 ,如果没有返回null
    private ZyHandler getZyHandler(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        //遍历HandlerList
        for (ZyHandler zyHandler : HandlerList) {
            if (requestURI.equals(zyHandler.getUrl())){
                return zyHandler;
            }
        }
        return null;
    }
  1. 添加第三个方法 executeDispatch(),进行分发处理,需要 调用getHandler() 获取浏览器发送的request请求 对应的 Handler ,获取Handler 中的method 进行反射调用,method .invoke() 实现分发请求。
   public void executeDispatch(HttpServletRequest request,HttpServletResponse response){
        ZyHandler zyHandler = getZyHandler(request);
        try {
            if (null == zyHandler){
               response.getWriter().write("<h1>404 NOT FOUND</h1>");
            }
            Method method = zyHandler.getMethod();
            method.invoke(zyHandler.getController(),request,response);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

从web.xml文件中动态获取spring配置文件

思路

  1. 首先,我们自己写的前端控制器是一个Servlet,它有 servletConfig,可以servletConfig.getInitParameter("xxx") 来获取之前在web.xml配置的classpath:
  2. 工具类XMLParser 是在spring容器中解析的 ,web.xml配置的classpath: 是在前端控制器中获取的,因此需要spring容器提供有参构造器,在前端控制器添加参数 spring容器,将classpath 传到spring容器中进行解析。

实现

  1. 首先,我们自己写的前端控制器是一个Servlet,它有 servletConfig,可以servletConfig.getInitParameter("xxx") 来获取之前在web.xml配置的classpath:
  2. 工具类XMLParser 是在spring容器中解析的 ,web.xml配置的classpath: 是在前端控制器中获取的,因此需要spring容器提供有参构造器,在前端控制器添加参数 spring容器,将classpath 传到spring容器中进行解析。
@Override
    public void init() throws ServletException {
        String configLocation = getServletConfig().getInitParameter("contextConfigLocation");
        System.out.println("ZyDispatcherServlet 初始化---");
        zyWebApplicationContext = new ZyWebApplicationContext(configLocation);
        zyWebApplicationContext.init();

        initHandlerMapping();
        System.out.println("HandlerList= "+HandlerList);

    }
 private  String configLocation;
    public ZyWebApplicationContext(String configLocation) {
        this.configLocation = configLocation;
    }
 public void init(){
        System.out.println("ZyWebApplicationContext 初始化---");
        String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
        String[] basePackages = basePackage.split(",");
        if (basePackages.length >0) {
            for (String  pack : basePackages) {
                scanPackage(pack);
            }
        }
 }

@Service注解

思路

  1. @Service注解是写在类上的 即@Target(ElementType.TYPE)
  2. 这个注解标识的类就是一个Service,那么同样是在executeInstance()方法中判断是否有注解@Service,有的话就保存到ioc容器中
  3. 由于是Service,那么保存进ioc 的 k-v 中的 k 就有三种。第一种,就是默认值,用接口的类型的名字首字母小写;第二种,在@Service注解种设置了value属性,那么 k = value;第三章,需要用类名首字母小写也可以获取bean

实现

  1. @Service注解是写在类上的 即@Target(ElementType.TYPE)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}
  1. 这个注解标识的类就是一个Service,那么同样是在executeInstance()方法中判断是否有注解@Service,有的话就保存到ioc容器中
  2. 由于是Service,那么保存进ioc 的 k-v 中的 k 就有三种。第一种,就是默认值,用接口的类型的名字首字母小写;第二种,在@Service注解种设置了value属性,那么 k = value;第三章,需要用类名首字母小写也可以获取bean
 public void executeInstance() {
        if (classFullPathList.size() == 0){
            return;
        }
        //遍历 classFullPathList
        for (String classFullPath : classFullPathList) {
            try {
                Class<?> clazz = Class.forName(classFullPath);

                if (clazz.isAnnotationPresent(Controller.class)){
                    Controller controller = clazz.getAnnotation(Controller.class);
                    String value = controller.value();
                    String className = clazz.getSimpleName();
                    Object instance = clazz.newInstance();
                    if ("".equals(value)){
                        className = StringUtils.uncapitalize(className);
                    }else {
                        className = value;
                    }
                    ioc.put(className,instance);
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    Service serviceAnnotation = clazz.getAnnotation(Service.class);
                    String annoattionValue = serviceAnnotation.value();
                    Object instance = clazz.newInstance();

                    if ("".equals(annoattionValue)){
                        Class<?>[] interfaces = clazz.getInterfaces();
                        for (Class<?> anInterface : interfaces) {
                            String simpleName = anInterface.getSimpleName();
                            simpleName = StringUtils.uncapitalize(simpleName);
                            ioc.put(simpleName,instance);
                        }

                        //可以通过类名首字母小写
                        String simpleName = clazz.getSimpleName();
                        simpleName = StringUtils.uncapitalize(simpleName);
                        ioc.put(simpleName,instance);

                    }else {
                        ioc.put(annoattionValue,instance);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

@Autowried 依赖注入

思路

  1. @Autowried注解是用在字段上,通过ioc容器自动装配,因此 @Target(ElementType.FIELD)
  2. 需要遍历ioc中所有的 bean 中的所有字段,来进行判断是否需要自动装配
  3. 通过反射获取字段,判断该字段是否带有@Autowried 注解,有的话就判断value值
  4. value为"",说明按默认规则 用字段类型首字母小写 去ioc容器进行查找,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");
  5. 如果有值,去ioc容器进行查找该值对应的bean,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");如果有该bean,就用该字段的set()方法将本身的bean 和 查找ioc获取的bean进行装配
  6. 即 declaredField.set(bean, beanInIOC);
  7. 由于字段是私有属性private,需要暴力破解declaredField.setAccessible(true);

实现

  1. @Autowried注解是用在字段上,通过ioc容器自动装配,因此 @Target(ElementType.FIELD)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    String value() default "";
}
  1. 需要遍历ioc中所有的 bean 中的所有字段,来进行判断是否需要自动装配

  2. 通过反射获取字段,判断该字段是否带有@Autowried 注解,有的话就判断value值

  3. value为"",说明按默认规则 用字段类型首字母小写 去ioc容器进行查找,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");

  4. 如果有值,去ioc容器进行查找该值对应的bean,如果没有抛出空指针异常throw new NullPointerException("ioc 没有该bean");如果有该bean,就用该字段的set()方法将本身的bean 和 查找ioc获取的bean进行装配

  5. 即 declaredField.set(bean, beanInIOC);

  6. 由于字段是私有属性private,需要暴力破解declaredField.setAccessible(true);

public void  executeAutoWired(){
    //遍历ioc
    if (ioc.isEmpty()){
        return;
    }

    //获取容器里的所有bean 以及 bean对应的字段
    for (Map.Entry<String, Object> entry : ioc.entrySet()) {
        Object bean = entry.getValue();

        Class<?> clazz = bean.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {

            //通过反射获取字段,判断该字段是否带有@Autowried 注解,有的话就判断value值
            if (declaredField.isAnnotationPresent(Autowired.class)){
                Autowired annotation =
                        declaredField.getAnnotation(Autowired.class);
                String beanName = annotation.value();

                try {
                    //value为"",说明按默认规则 用字段类型首字母小写 去ioc容器进行查找,
                    if ("".equals(beanName)){
                        beanName = declaredField.getType().getSimpleName();
                        beanName = StringUtils.uncapitalize(beanName);

                    }
                        Object iocBean = ioc.get(beanName);
                    //如果没有抛出空指针异常
                        if (null == iocBean){
                            throw new NullPointerException("ioc 没有该Bean");
                        }
                        declaredField.setAccessible(true);//暴力破解
                        declaredField.set(bean,iocBean);

                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

@RequestParam

思路

  1. @RequestParam 是写在PARAMETER上的 也就是说 @Target(ElementType.PARAMETER)
  2. 这个注解实现的功能 大方向是 分发请求 也就是说 写在 executeDispatch()方法中的
  3. 要实现 @RequestParam 需要将形参列表封装到一个数组中 , 因为反射 invoke 可以传入一个数组作为可变参数解析
  4. 那么就需要两个数组 一个数组是新创建的数组(大小应与,目标数组的形参个数一致) 将实参放入进去 ;另一个是 反射拿到的目标方法的形参的数组
  5. 这两个数组的内容需要一一对应,因为反射需要顺序一致
  6. 需要获取到request 中请求的参数Map 获取参数名和参数值
  7. 可以将功能具体化到 完成这个新数组的实参填写 【完成新数组之后放入invoke方法进行反射就行】
  8. 将步骤拆解成 1)完成HttpServletRequest 和 HttpServletResponse 的填写 ;2)完成带有@RequestParam注解的形参的填写;3)完成普通的方法参数没有@RequestParam注解的形参的填写
  9. 完成HttpServletRequest 和 HttpServletResponse 的填写:就需要先拿到目标方法的所有参数。根据类型的名称 和 形参列表进行匹配 填写
  10. 完成带有@RequestParam注解的形参的填写:需要添加方法,获取目标方法中 带有@RequestParam注解的形参是属于第几个参数的,返回int 代表第几个参数,如果有@RequestParam注解,就会返回相应的索引,如果没有就会返回-1 进行普通方法参数的填写 处理
  11. 普通方法参数的 填写:需要添加方法,将目标方法的所有的形参的名称 反射保存到 List 返回,再通过遍历判断 请求的参数名 和 List中一致的 填写到 数组中
  12. 普通方法参数的 填写 需要插件,使用java8的特性 解决在默认情况下 parameter.getName() 获取的名字不是形参真正的名字,而是[arg0,arg1,arg2...]的问题

实现

  1. @RequestParam 是写在PARAMETER上的 也就是说 @Target(ElementType.PARAMETER)
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    String value() default "";
}
  1. 这个注解实现的功能 大方向是 分发请求 也就是说 写在 executeDispatch()方法中的
  2. 要实现 @RequestParam 需要将形参列表封装到一个数组中 , 因为反射 invoke 可以传入一个数组作为可变参数解析
Method method = zyHandler.getMethod();
Class<?>[] parameterTypes = method.getParameterTypes();//形参数组
Object[] params = new Object[parameterTypes.length];
  1. 那么就需要两个数组 一个数组是新创建的数组(大小应与,目标数组的形参个数一致) 将实参放入进去 ;另一个是 反射拿到的目标方法的形参的数组
  2. 这两个数组的内容需要一一对应,因为反射需要顺序一致
  3. 需要获取到request 中请求的参数Map 获取参数名和参数值
 request.setCharacterEncoding("utf-8");
 Map<String, String[]> parameterMap = request.getParameterMap();
 for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
    String name = entry.getKey();//参数 键
    String value = entry.getValue()[0];//参数 值
  1. 可以将功能具体化到 完成这个新数组的实参填写 【完成新数组之后放入invoke方法进行反射就行】

  2. 将步骤拆解成 1)完成HttpServletRequest 和 HttpServletResponse 的填写 ;2)完成带有@RequestParam注解的形参的填写;3)完成普通的方法参数没有@RequestParam注解的形参的填写

  3. 完成HttpServletRequest 和 HttpServletResponse 的填写:就需要先拿到目标方法的所有参数。根据类型的名称 和 形参列表进行匹配 填写

 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. 完成带有@RequestParam注解的形参的填写:需要添加方法,获取目标方法中 带有@RequestParam注解的形参是属于第几个参数的,返回int 代表第几个参数,如果有@RequestParam注解,就会返回相应的索引,如果没有就会返回-1 进行普通方法参数的填写 处理
 //获取目标方法中 带有@RequestParam注解的形参是属于第几个参数的,返
 private int getRequestParamterIndex(Method method,String name){
            Parameter[] parameters = method.getParameters();
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                if (parameter.isAnnotationPresent(RequestParam.class)) {

                    String value = parameter.getAnnotation(RequestParam.class).value();

                    if (name.equals(value)) {
                        return i;//返回int 代表第几个参数
                    }
                }
        }
        return -1;
    }
int requestParamterIndex = getRequestParamterIndex(zyHandler.getMethod(), name);
                    if (requestParamterIndex != -1) {
                        params[requestParamterIndex] = value;
                    } 
  1. 普通方法参数的 填写:需要添加方法,将目标方法的所有的形参的名称 反射保存到 List 返回,再通过遍历判断 请求的参数名 和 List中一致的 填写到 数组中
	//将目标方法的所有的形参的名称 反射保存到 List 返回
    private List<String> getParameterNames(Method method){
        List<String> parameterList = new ArrayList<>();

        //获取到所有的参数名称
        Parameter[] parameters = method.getParameters();

        for (Parameter parameter : parameters) {
            //在默认情况下 parameter.getName() 获取的名字不是形参真正的名字
            //而是[arg0,arg1,arg2...]
            //需要插件,使用java8的特性 解决
            String name = parameter.getName();
            parameterList.add(name);
        }
        System.out.println("目标方法的形参列表=" + parameterList);
        return parameterList;
    }
else {
                        //没找到@RequestParam 对应参数--使用默认机制
                        //1. 得到目标方法的所有形参名
                        //2. 对得到目标方法的所有形参名进行遍历,
                        //如果匹配就把当前请求的参数值放入params
                        List<String> parameterNames = getParameterNames(zyHandler.getMethod());
                        for (int i = 0; i < parameterNames.size(); i++) {
                            if (name.equals(parameterNames.get(i))) {
                                params[i] = value;
                                break;
                            }
                        }
                    }
  1. 普通方法参数的 填写 需要插件,使用java8的特性 解决在默认情况下 parameter.getName() 获取的名字不是形参真正的名字,而是[arg0,arg1,arg2...]的问题
  <!--可以进行json操作-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.14.0</version>
    </dependency>

  • 完整代码
 public void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
        ZyHandler zyHandler = getZyHandler(request);
        try {
            if (null == zyHandler) {
                response.getWriter().write("<h1>404 NOT FOUND!</h1>");
            } else {
                Method method = zyHandler.getMethod();
                Class<?>[] parameterTypes = method.getParameterTypes();//形参数组
                Object[] params = new Object[parameterTypes.length];


                //遍历 parameterTypes
                //获取 HttpServletRequest , HttpServletResponse 在形参数组中的位置
                //将request 和 response 保存到  params相应的位置上
                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;
                    }
                }

                //获取request中的
                request.setCharacterEncoding("utf-8");
                Map<String, String[]> parameterMap = request.getParameterMap();
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    String name = entry.getKey();//参数 键
                    String value = entry.getValue()[0];//参数 值

                    //获取形参数组中 带有@RequestParam 的形参 的位置
                    //将带有@RequestParam 保存到  params相应的位置上
                    int requestParamterIndex = getRequestParamterIndex(zyHandler.getMethod(), name);
                    if (requestParamterIndex != -1) {
                        params[requestParamterIndex] = value;
                    } else {
                        //没找到@RequestParam 对应参数--使用默认机制
                        //1. 得到目标方法的所有形参名
                        //2. 对得到目标方法的所有形参名进行遍历,
                        //如果匹配就把当前请求的参数值放入params
                        List<String> parameterNames = getParameterNames(zyHandler.getMethod());
                        for (int i = 0; i < parameterNames.size(); i++) {
                            if (name.equals(parameterNames.get(i))) {
                                params[i] = value;
                                break;
                            }
                        }
                    }
                }

视图解析

思路

  1. 首先 视图解析 它是在分发请求后 在目标方法中进行视图的跳转 forward 或者 redirect,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用
  2. 在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理
  3. 当返回的是String类型后 我们就可以根据 splic(":")进行分隔
  4. splic(":")[0] 就是进行跳转的方式 forward 或者 redirect
  5. splic(":")[1] 就是进行跳转的页面
  6. 如果没有":" ,就说明是默认情况,forward 处理即可

实现

  1. 首先 视图解析 它是在分发请求后 在目标方法中进行视图的跳转 forward 或者 redirect,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用

  2. 在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理

    Object result = zyHandler.getMethod().invoke(zyHandler.getController(), params);
    
  3. 当返回的是String类型后 我们就可以根据 splic(":")进行分隔

  4. splic(":")[0] 就是进行跳转的方式 forward 或者 redirect

  5. splic(":")[1] 就是进行跳转的页面

  6. 如果没有":" ,就说明是默认情况,forward 处理即可

 if (result instanceof String){
                   String viewName =  (String) result;
                    if (viewName.contains(":")) {
                        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);
                    }
                }

@ResponseBody 返回JSON数据

实现

  1. 首先@ResponseBody 是写在方法上的 因此 @Target(ElementType.METHOD)
  2. 没有默认值 @ResponseBody仅仅作为一个标识
  3. @ResponseBody它是在分发请求后 在目标方法中标识该方法返回JSON格式的数据给浏览器,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用
  4. 在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理
  5. 当返回的是一个集合类型,并且判断该方法有注解@ResponseBody ,就说明是需要向浏览器返回JSON格式数据
  6. 用jackson 包下的 objectWriter对象的 objectMapper.writeValueAsString()方法 ,可以很轻松的将集合转化为JSON进行返回
  7. 不要忘了设置response格式,防止乱码,response.setContentType("text/html;charset=utf-8");
  8. 直接用response.getWriter.writer()将转化后的结果返回给浏览器即可

思路

  1. 首先@ResponseBody 是写在方法上的 因此 @Target(ElementType.METHOD)
  2. 没有默认值 @ResponseBody仅仅作为一个标识
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
  1. @ResponseBody它是在分发请求后 在目标方法中标识该方法返回JSON格式的数据给浏览器,所以这部分的代码应该在 前端处理器的executeDispatch 中 对目标方法进行反射后调用
  2. 在对目标方法进行反射 method.invoke() 就会有返回值,根据返回值的类型进行相应业务处理
  3. 当返回的是一个集合类型,并且判断该方法有注解@ResponseBody ,就说明是需要向浏览器返回JSON格式数据
  4. 用jackson 包下的 objectWriter对象的 objectMapper.writeValueAsString()方法 ,可以很轻松的将集合转化为JSON进行返回
else if (result instanceof ArrayList) {
    if (zyHandler.getMethod().isAnnotationPresent(ResponseBody.class)) {
        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(result);
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(json);
        writer.flush();
        writer.close();
    }
}
  1. 不要忘了设置response格式,防止乱码,response.setContentType("text/html;charset=utf-8");
  2. 直接用response.getWriter.writer()将转化后的结果返回给浏览器即可

本文学习内容来自韩顺平老师的课程

仅供个人参考学习

热门相关:九星毒奶   傅爷怀里的假千金真绝了   闪婚总裁很惧内   全民女神,重生腹黑千金   万界点名册