Java框架--Spring MVC

Spring MVC

执行流程

@RequestMapping

value

  • 可以指定控制器/处理器的某个方法的请求的url

  • 可以修饰方法,还可以修饰类 当同时修饰类和方法时,请求的url就是组合 /类请求值/方法请求值

  • Ant风格资源地址

    • ?:匹配文件名中的一个字符(/user/createUser??:匹配/user/createUseraa、/user/createUserbb等URL)
    • * :匹配文件名中的任意字符(/user/*/createUser:匹配/user/aaa/createUser、/user/bbb/createUser等URL)
    • ** :匹配多层路径(/user/**/createUser:匹配/user/createUser、/user/aaa/bbb/createUser等URL)
  • 配合@PathVariable 映射URL绑定占位符

    //@RequestMapping(value = "/reg/{name}/{age}") 这里的name、age叫路径变量
    //@PathVariable("name") String username  这里意思是把路径变量放入方法形参
    
    @RequestMapping(value = "/reg/{name}/{age}")
    public String register(@PathVariable("name") String username,@PathVariable("age")String userid)
    

method

  • 使用 RequestMethod 枚举值
  • 常用枚举:POST、GET、DELECT、PUT

params

  • params = "bookId" 标识请求目标方法时,必须给一个bookId参数,值没有限制
  • params = "bookId=101" 标识请求目标方法时,必须给一个bookId参数,值必须为101

注意事项

  • 映射的url不能重复

  • 请求简写

    • @RequestMapping(value = "/buy",method = RequestMethod.POST) 等 价 @PostMapping(value = "/buy")

    • 简写一览: @GetMapping @PostMapping @PutMapping @DeleteMapping

  • RequestMapping标识的方法如果提供了形参名,会从请求的参数中去匹配同名参数(没找到会设置为null,根据HttpRequest.getParameter方法返回值)

Rset(优雅的url请求风格)

Representational State Transfer。(资源)表现层状态转化。是目前流行的请求方式。它结构清晰,很多网站采用

HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。(传统通过参数来说明crud类型,rest是通过请求类型来说明crud类型

REST的核心过滤器

  • 当前的浏览器form表单只支持GET与POST请求,而DELETE、PUT等method并不支持,Spring添加了一个过滤器,可以将这些请求转换为标准的http方法,使得支持GET、POST、PUT与DELETE请求

  • HiddenHttpMethodFilter能对post请求方式进行转换,原理就是对post请求中的隐藏字段_method进行判断,将此请求转换为目标类型的请求,也就是说,只有post会进行转换,转换的目标是:post、put、delete..请求

  • 此过滤器在web.xml中配置,进行全部请求的过滤转换,同时也配置容器xml文件开启Mvc高级功能

    web.xml
    
        <!--配置HiddenHttpMethodFilter过滤器
        将以post方式提交的delete、put等请求进行请求类型转换
        -->
        <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> 会匹配到/login这样的路径型url,不会匹配到模式为*.jsp这样的后缀型url。
            <url-pattern>/*</url-pattern> 会匹配所有url:路径型的和后缀型的url(包括/login,.jsp,.js和*.html等)。
            <url-pattern>/</url-pattern> 不会匹配到*.jsp,即:*.jsp不会进入springmvc的 DispatcherServlet类 。
            <url-pattern>/*</url-pattern> 会匹配*.jsp,会出现返回jsp视图时再次进入spring的DispatcherServlet 类,导致找不到对应的controller所以报404错。
            -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    
    容器xml
    
        <!--配置两个常规配置(使用HiddenHttpMethodFilter过滤器时配置)-->
        <!--支持springmvc的高级功能,比如JSR303效验,映射动态请求
        注意选择引入xmlns:mvc="http://www.springframework.org/schema/mvc"-->
        <mvc:annotation-driven/>
        <!--将springmvc不能处理的请求交给tomcat处理,比如css\js请求-->
        <mvc:default-servlet-handler/>
    

应用

/**
 * 处理rest风格的请求,包括crud
 */
@RequestMapping("/user")
@Controller
public class BookHandler {

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

    @RequestMapping(value = "/book", method = RequestMethod.POST)
    public String addBook(String bookName){
        System.out.println("添加书籍 = " + bookName);
        return "success";
    }

    @RequestMapping(value = "/book/{bookId}", method = RequestMethod.DELETE)
    public String delBook(@PathVariable("bookId") String bookId){
        System.out.println("delBook ID = " + bookId);
        //return "success"; //HTTP Status 405 - JSPs only permit GET POST or HEAD  由于jsp页面仅支持post、get请求
        return "redirect:/user/success"; //重定向自动转为get,临时使用,后续使用前端框架vue不存在此问题
    }

    @RequestMapping(value = "/book/{bookId}", method = RequestMethod.PUT)
    public String UpdBook(@PathVariable("bookId") String bookId){
        System.out.println("UpdBook ID = " + bookId);
        //return "success"; //HTTP Status 405 - JSPs only permit GET POST or HEAD  由于jsp页面仅支持post、get请求
        return "redirect:/user/success"; //重定向自动转为get,临时使用,后续使用前端框架vue不存在此问题
    }

    @RequestMapping(value = "/success")
    public String successGenecal(){
        return "success";
    }

}

映射请求数据

获取参数

开发中,会遇到请求的参数和实际方法形参名称不一致,这时候可以使用@RequestParam来映射请求中真正的参数,就类似于重新链接请求参数和实际方法形参(前面提到RequestMapping标识的方法如果提供了形参名,会从请求的参数中去匹配同名参数,不一致则置空,这里就可以重新关联)

/**
* 当形参和实际请求中的参数名不一致时,使用RequestParam(形参username,实际请求参数name)
* required = true,请求中就必须提供name参数,否则报错(反之不会报错,继续置空)
* @param username 用户名
* @return 视图解析
*/
@RequestMapping("/vote01")
public String test01(@RequestParam(value = "name", required = false) String username){
    System.out.println("得到username = " + username);
    return "success";
}

获取http请求头

/**
* 获取请求头参数
* @return 视图解析
*/
@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形式的数据

开发中,客户提交表单本身就是一个javabean数据,springmvc支持直接将请求参数封装为一个javabean对象

/**
* 获取javaBean对象
* 直接在方法的形参上用对应的javaBean类型即可自动封装
* 注意1:自动封装会根据javaBean类型的属性字段名到请求参数中去找一致的值,不一致为null
* 注意2:如果属性是对象,请求的参数名为pet.id、pet.name,进行级联封装
* @return 视图解析
*/
@RequestMapping("/vote03")
public String test03(Master master){
    System.out.println("master = " + master);
    return "success";
}

//测试链接:  http://localhost:8080/springmvc/vote/vote03?id=1&name=szl&pet.id=2&pet.name=xh

获取原生servlet api

开发中,如果需要获取原生的servlet-api使用,需要先引入tomcat/lib下的servlet-api.jar,然后直接使用方法形参来获取

/**
* 获取servlet api, 来获取提交的数据
* @return 视图解析
*/
@RequestMapping("/vote04")
public String test04(HttpServletRequest request, HttpServletResponse response){
    String name = request.getParameter("name");
    System.out.println("name = " + name);
    return "success";
}
  • 除了 HttpServletRequest, HttpServletResponse 还有其他api对象可以获取:HttpSession、java.security.Principal,InputStream,OutputStream,Reader,Writer ......

  • 其中一些对象也可以通过 HttpServletRequest / HttpServletResponse 对象获取,比如 Session 对象 ,既可以通过参数传入,也以通过 request.getSession() 获取,效果一样,推荐使用参数形式传入,更加简单明了

模型数据(域数据)

实际应用中,我们常常会用到数据传递,这时候就会用到模型数据的传递(域数据方式-可以回顾servlet基础),因为实际数据来源的地方可能是前端或者数据库

模型数据/域数据最简单的理解就是类似全局映射的map,web相关的代码片段都可以访问此map

数据放入request

开发中,控制器/处理器中获取的数据如何放入request域,然后在前端(VUE/JSP/...)取出显示

  • 默认机制

        /**
         * 将提交的数据封装到java对象时,springmvc 同时会把此对象自动放入到request域,名字就是此对象类型的首字母小写
         * 比如:Master类型对象就是master,此方法调用后,request域中已存在key为master的Master类型封装对象
         * 注意:如果在方法中修改了Master类型对象的值,那么也会影响request域中的类型对象值
         * @return 视图解析
         */
        @RequestMapping("/vote05")
        public String test05(Master m){
            m.setName("aa");
            return "vote_ok";
        }
    
  • 使用servlet-api

        /**
         * 使用HttpServletRequest 对象设置属性
         * @return 视图解析
         */    
    	@RequestMapping("/vote05")
        public String test05(HttpServletRequest request){
            request.setAttribute("address", "beijing");
            return "vote_ok";
        }
    
  • 通过Map<String,Object>

        /**
         * 通过Map<String,Object> 设置数据到request域
         * 原理:数据渲染的时候springmvc会将方法执行完成后的map中的key、value遍历放入到request域中
         * 注意:如果使用 map.put("master", null),会将原本springmvc自动放入request域中的master指向修改
         * @return 视图解析
         */
        @RequestMapping("/vote06")
        public String test06(Master m, Map<String,Object> map){
            map.put("address", "shanghai...");
            //map.put("master", null);
            return "vote_ok";
        }
    
  • 通过ModelAndView

        /**
         * 通过ModelAndView设置数据到request域
         * @return 视图解析
         */
        @RequestMapping("/vote07")
        public ModelAndView test07(Master m){
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("address", "fuzhou...");
            //这里如果执行一下语句,和使用map一样,会将原本springmvc自动放入request域中的master指向修改
            //modelAndView.addObject("master", null);
            //这里我们把原本让springmvc自动封装的动作,自己实现了,然后返回ModelAndView类型对象
            modelAndView.setViewName("vote_ok");
            return modelAndView;
        }
    

数据放入Session域

    /**
     * 把模型数据放入Session域
     * @return 视图解析
     */
    @RequestMapping("/vote08")
    public String test08(Master m, HttpSession httpSession){
        httpSession.setAttribute("address", "guangzhou...");
        httpSession.setAttribute("master", m);
        return "vote_ok";
    }

@ModelAttribute

开发中,有时需要使用某个前置方法(比如prepareXxx(),方法名由程序员定)给目标方法准备一个模型对象,@ModelAttribute注解标注一个方法之后,那么调用该Handler的任何一个方法时,都会先调用这个方法

    /**
     * ModelAttribute 标识后,此方法就变为前置方法
     * 它的作用域在本类所有方法前生效
     */
    @ModelAttribute
    public void prepareModel(){
        System.out.println("prepareModel() -----完成准备工作------");
    }

最佳实践:预先查询数据库准备数据,进行下一步处理

视图和视图解析器

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

自定义视图解析器:在默认情况下,我们都是返回默认的视图,然后这个返回的视图交由SpringMVC的InternalResourceViewResolver视图处理器来处理的;在实际开发中,我们有时需要自定义视图,这样可以满足更多更复杂的需求

视图解析器:原理是循环来执行每个视图解析器,根据配置的order值越小的先执行,如果执行成功返回,既结束后续的视图渲染,失败则继续执行下一视图(当配置了BeanNameViewResolver、InternalResourceViewResolver,根据优先级order值来for循环执行)

自定义视图

  • 配置容器XML启动自定义视图配置,同时设置其执行优先级,确保在InternalResourceViewResolver视图前被调用(如果没有在默认InternalResourceViewResolver前调用,则会被InternalResourceViewResolver拦截,反之则会继续到InternalResourceViewResolver中寻找匹配页面)

        <!--配置自定义视图解析器-->
        <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
            <!--这里设置执行顺序,默认顺序是Integer.MAX_VALUE,最低(InternalResourceViewResolver就是默认最低)-->
            <property name="order" value="99"/>
        </bean>
    
  • 创建继承AbstractView的类,同时实现renderMergedOutputModel 方法 放入容器@Component(value = "szlView")起个beanId

    @Component(value = "szlView")
    public class MyView extends AbstractView {
        @Override
        protected void renderMergedOutputModel(Map<String, Object> map,
                                               HttpServletRequest httpServletRequest,
                                               HttpServletResponse httpServletResponse) throws Exception {
            System.out.println("进入自己的视图");
            //下面就是进行请求转发到/WEB-INF/pages/my_view.jsp
            httpServletRequest.getRequestDispatcher("/WEB-INF/pages/my_view.jsp")
                    .forward(httpServletRequest, httpServletResponse);
        }
    }
    
  • 使用字符串指定视图名,因BeanNameViewResolver会先执行,"szlView"会匹配到@Component(value = "szlView") 的bean视图类

    @RequestMapping("/goods")
    @Controller
    public class GoodsHandler {
    
        @RequestMapping("/buy")
        public String buy(){
            System.out.println("------buy-------");
            return "szlView";
        }
    }
    

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

@RequestMapping("/goods")
@Controller
public class GoodsHandler {
    /**
     * 演示直接指定要请求转发的或者是重定向的页面
     * @return
     */
    @RequestMapping(value = "/order")
    public String order() {
        System.out.println("=======order()=====");
        //请求转发到 /WEB-INF/pages/my_view.jsp
        //下面的 /WEB-INF/pages/my_view.jsp 被解析成 /springmvc/WEB-INF/pages/my_view.jsp
        //return "forward:/WEB-INF/pages/my_view.jsp";
        //return "forward:/aaa/bbb/ok.jsp";

        //直接指定要重定向的页面
        //1. 对于重定向来说,不能重定向到 /WEB-INF/ 目录下
        //2. redirect 关键字,表示进行重定向
        //3. /login.jsp 在服务器解析 /springmvc/login.jsp
        return "redirect:/login.jsp";

        // /WEB-INF/pages/my_view.jsp 被解析 /springmvc/WEB-INF/pages/my_view.jsp
        //return "redirect:/WEB-INF/pages/my_view.jsp";
    }
}

视图类型有很多种:redirect重定向 - RedirectView 、 forward转发 - InternalResourceView ......

它们都实现View接口,只要实现了就代表此类是视图,自定义视图就是实现了View接口,配置好自定义视图解析器即可同步加入使用

数据格式化

基本类型转换

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

SpringMVC可以将提交的字符串数字,比如"28",转成Integer/int(这里指的是接收bean同名字段类型是int),如果不是数字,则给出400的页面(也可以改为自行处理错误方式,方式是通过在处理方法中接受Error参数来告诉SpringMVC,我自己处理错误)

特殊类型转换

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

@DateTimeFormat(pattern="yyyy-MM-dd") 
private Date birthday; 
@NumberFormat(pattern="###,###.##") 
private float salary;

验证及国际化

处理方法代码

/**
     * 编写方法,处理添加妖怪
     * 1. springmvc可以将提交的数据,按照参数名和对象的属性名匹配
     * 2. 直接封装到对象中->老师前面讲解模型数据时,讲过
     * String => Integer
     * 3. @Valid Monster monster :表示对monster接收的数据进行校验
     * 4. Errors errors 表示如果校验出现错误,将校验的错误信息保存 errors
     * 5. Map<String, Object> map  表示如果校验出现错误, 将校验的错误信息保存 map 同时保存monster对象
     * 6. 校验发生的时机: 在springmvc底层,反射调用目标方法时,会接收到http请求的数据,然后根据注解来进行验证
     * , 在验证过程中,如果出现了错误,就把错误信息填充errors 和 map
     *
     * @param monster
     * @return
     */
    @RequestMapping(value = "/save")
    public String save(@Valid Monster monster, Errors errors, Map<String, Object> map) {
        System.out.println("----monster---" + monster);
        //我们为了看到验证的情况,我们输出map 和 errors
        System.out.println("===== map ======");
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            System.out.println("key= " + entry.getKey() + " value=" + entry.getValue());
        }

        System.out.println("===== errors ======");
        if (errors.hasErrors()) {//判断是否有错误
            List<ObjectError> allErrors = errors.getAllErrors();
            for (ObjectError error : allErrors) {
                System.out.println("error=" + error);
            }
            return "datavalid/monster_addUI";
        }
        return "datavalid/success";
    }

注意:

  1. 在需要验证的 Javabean/POJO 的字段上加上相应的验证注解.
  2. 目标方法上,在 JavaBean/POJO 类型的参数前, 添加 @Valid 注解. 告知 SpringMVC该 bean 是需要验证的
  3. 在 @Valid 注解之后, 添加一个 Errors 或 BindingResult 类型的参数, 可以获取到验证的错误信息

验证框架

  • JSR 303

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

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

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

  • Hibernate Validator

    扩展注解,是JSR 303实现的一个扩展

  • 应用

    引入验证和国际化相关的jar包

  • @NotNull和@NotEmpty的区别

    如果是字符串验证空,建议使用@NotEmpty,@NotNull可以用在任何类型

国际化

由于验证框架中的默认提示不一定是符合本地语言风格的提示,这时需要使用国际化配置来设置提示内容

  • 配置文件

    1.配置容器xml

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

    2.创建国际化文件D:\idea_java_projects\springmvc\src\i18n.properties 错误消息的国际化文件i18n.properties ,中文需要是Unicode编码,使用工具转码

    格式:验证规则.表单modelAttribute值.属性名=消息信息

    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
    

    DataBinder

Spring MVC通过反射机制对目标方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder,运行机制如下

@InitBinder

在开发中,希望取消某个属性的绑定,也就是说,不希望某个表单对应的属性的值,则可以通过注解@InitBinder取消绑定

//取消绑定 monster的name表单提交的值给monster.name属性
    @InitBinder
    public void initBinder(WebDataBinder webDataBinder) {
        /**
         * 老师解读
         * 1. 方法上需要标注 @InitBinder  springmvc底层会初始化 WebDataBinder
         * 2. 调用 webDataBinder.setDisallowedFields("name") 表示取消指定属性的绑定
         *    即:当表单提交字段为 name时, 就不在把接收到的name值,填充到model数据monster的name属性
         * 3. 机制:springmvc 在底层通过反射调用目标方法时, 接收到http请求的参数和值,使用反射+注解技术
         *    取消对指定属性的填充
         * 4. setDisallowedFields支持可变参数,可以填写多个字段
         * 5. 如果我们取消某个属性绑定,验证就没有意义了,应当把验证的注解去掉, name属性会使用默认值null
         *  //@NotEmpty
         *  private String name;
         *
         */
        webDataBinder.setDisallowedFields("name");
    }
  1. 编写一个方法, 使用@InitBinder 标识的该方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定
  2. @InitBinder 方法不能有返回值,它必须声明为 void。
  3. @InitBinder 方法的参数通常是是 WebDataBinder
  4. setDisallowedFields()是可变形参,可以指定多个字段
  5. 当将一个字段/属性,设置为disallowed,就不在接收表单提交的值,那么这个字段/属性的值,就是该对象默认的值 (具体看程序员定义时指定)
  6. 一般来说,如果不接收表单字段提交数据,则该对象字段的验证也就没有意义了可以注销掉(//@NotEmpty )

中文乱码处理

自定义过滤器

/**
 * 编写过滤器,处理中文乱码
 */
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");
        //放行请求,这个规则和前面老韩讲过的java web的过滤器一样
        filterChain.doFilter(servletRequest, servletResponse);

    }

    @Override
    public void destroy() {

    }
}

web.xml------------------------
    
    <!--配置处理中文乱码的过滤器
    拦截所有请求,处理编码, 提醒,把过滤器配置到web.xml最前面
    -->
    <filter>
    <filter-name>MyCharacterFilter</filter-name>
    <filter-class>com.hspedu.web.filter.MyCharacterFilter</filter-class>
        </filter>
        <filter-mapping>
        <filter-name>MyCharacterFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Spring提供的过滤器

<!--配置Spring提供的过滤器,解决中文乱码问题,记得放在servlet之前-->

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

HttpMessageConverter

处理Json与文件下载

  • 引入包

  • 前端

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>json提交</title>
        <!-- 引入jquery -->
        <script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
    
        <!-- 编写jquery代码和ajax请求 -->
        <script type="text/javascript">
            $(function () {
                //给id="getJson"绑定点击事件
                $("#getJson").click(function () {
                    //console.log("ok ....")
    
                    var url = this.href;
                    var args = {"time": new Date};//这是老师要发送数据,为了防止页面缓存
                    $.post(
                        url,
                        args,
                        function (data) {//data 就是返回的数据,是json格式=>如果是多个json数据,可以遍历
                            console.log("dataa= ", data);
                            console.log("dog.name=", data.name)
                            console.log("dog.addresss=", data.address)
                        },
                        "json"
                    );
                    return false;//这里我们返回false,就不使用href默认机制
                })
    
                //绑定按钮点击事件, 提交json数据
                //springmvc 可以在在台將json轉成對象
                $("button[name='butt1']").click(function () {
                    //目标:将userName 和 age 封装成json字符串,发送给目标方法
                    var url = "/springmvc/save2";
                    var userName = $("#userName").val();
                    var age = $("#age").val();
                    //将json对象转成json字符串
                    var args = JSON.stringify({"userName": userName, "age": age});
                    $.ajax({
                        url: url,
                        data: args,
                        type: "POST",
                        success: function (data) {
                            console.log("返回的data= ", data);
                        },
                        //下面这个contentType参数,是指定发送数据的编码和格式
                        contentType: "application/json;charset=utf-8"
                    })
                })
            })
        </script>
    
    </head>
    <body>
    <h1>请求一个json数据</h1>
    <%--老师处理
    1.当用户点击超链接时,我们发出一个ajax请求
    2. 接收到请求后,我们查看这个数据
    3. 使用老韩前面讲过的jquery发出ajax请求知识
    --%>
    <a href="<%=request.getContextPath()%>/json/dog" id="getJson">点击获取json数据</a>
    
    <h1>发出一个json数据</h1>
    u:<input id="userName" type="text"><br/>
    a:<input id="age" type="text"><br/>
    <button name="butt1">添加用户</button>
    
    <h1>下载文件的测试 </h1>
    <a href="<%=request.getContextPath()%>/downFile">点击下载文件</a>
    </body>
    </html>
    
  • 后端

    /**
     * @author 韩顺平
     * @version 1.0
     */
    //@Controller
    //@ResponseBody //类上有了此注解,下面的方法都默认为ResponseBody
    @RestController //这个是上面两个注解和融合体
    public class JsonHandler {
    
        /**
         * 老师解读
         * 1. 目标方法 @ResponseBody,表返回的数据是json格式
         * 2. springmvc底层根据目标方法@ResponseBody, 返回指定格式, 根据的http请求来进行处理
         * 3. 底层原理我们在前面自定义@ResponseBody讲过, 这里原生的springmvc使用转换器
         * 4. HttpMessageConverter [一会老师debug]
         *
         * @return
         */
        @RequestMapping(value = "/json/dog")
        //@ResponseBody
        public Dog getJson() {
    
            //返回对象
            //springmvc会根据你的设置,转成json格式数据返回
            Dog dog = new Dog();
            dog.setName("大黄狗");
            dog.setAddress("小新的家");
            return dog;
    
        }
    
        //编写方法,以json格式返回多个Dog
        @RequestMapping(value = "/json/dogs")
        //@ResponseBody
        public List<Dog> getJsons() {
    
            List<Dog> dogs = new ArrayList<>();
            dogs.add(new Dog("大黄狗", "小新的家"));
            dogs.add(new Dog("大黄狗2", "小新2的家"));
            dogs.add(new Dog("大黄狗3", "小新3的家"));
    
            return dogs;
    
        }
    
        /**
         * 老师解读
         * 1. @RequestBody User user 在形参指定了 @RequestBody
         * 2. springmvc就会将提交的json字符串数据填充给指定Javabean
         *
         * @param user
         * @return
         */
        @RequestMapping(value = "/save2")
        //@ResponseBody
        public User save2(@RequestBody User user) {
            //将前台传过来的数据 以json的格式相应回浏览器
            System.out.println("user~= " + user);
            return user;
        }
    
        //响应用户下载文件的请求
        @RequestMapping(value = "/downFile")
        public ResponseEntity<byte[]> downFile(HttpSession session)
                throws Exception {
    
    
            //1. 先获取到下载文件的inputStream
            InputStream resourceAsStream =
                    session.getServletContext().getResourceAsStream("/img/2.jpg");
    
            //2. 开辟一个存放文件的byte数组, 这里老师使用byte[] 是可以支持二进制数据(图片,视频。)
            byte[] bytes = new byte[resourceAsStream.available()];
            //3. 将下载文件的数据,读入到byte[]
            resourceAsStream.read(bytes);
    
            //public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {}
            //4. 创建返回的HttpStatus
            HttpStatus httpStatus = HttpStatus.OK;
            //5. 创建 headers
            HttpHeaders headers = new HttpHeaders();
            //指定返回的数据,客户端应当以附件形式处理
            headers.add("Content-Disposition", "attachment;filename=2.jpg");
    
            //构建一个ResponseEntity 对象1. 的http响应头headers 2. http响应状态 3. 下载的文件数据
            ResponseEntity<byte[]> responseEntity =
                    new ResponseEntity<>(bytes, headers, httpStatus);
            //如果出现找不到文件,解决方法 rebuild project -> 重启tomcat
            return responseEntity;
        }
    }
    

底层原理

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

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

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

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

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

文件上传

SpringMVC为文件上传提供了直接的支持,这种支持是通过即插即用的MultipartResolver实现的。Spring用JakartaCommonsFileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResovler

SpringMVC上下文中默认没有装配MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用Spring的文件上传功能,需现在上下文中配置MultipartResolver(注意,此bean id 固定为multipartResolver 才可正常使用)

springDispatcherServlet-servlet.xml
<!--配置文件上传需要的bean-->
    <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
          id="multipartResolver"/>
  • 引入包

  • 前端

    <%--
      Filename: fileUpload
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>文件上传</title>
    </head>
    <body>
    <h1>文件上传的演示</h1>
    <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>
    </body>
    </html>
    
    
  • 后端

    /**
     * 处理文件上传的handler
     */
    @Controller
    public class FileUploadHandler {
    
        //编写方法,处理文件上传的请求
    
        @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=" + introduce);
            //得到要把上传文件保存到哪个路径[全路径:包括文件名]
            String fileFullPath =
                    request.getServletContext().getRealPath("/img/" + originalFilename);
            //创建文件
            File saveToFile = new File(fileFullPath);
            //将上传的文件,转存到saveToFile
            file.transferTo(saveToFile);
            return "success";
    
        }
    }
    

自定义拦截器

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

HandlerInterceptor接口的三个方法:

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

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

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

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

1.如果preHandle方法返回false,则不再执行目标方法,可以在此指定返回页面

2.postHandle在目标方法被执行后执行.可以在方法中访问到目标方法返回的ModelAndView对象

3.若preHandle返回true,则afterCompletion方法在渲染视图之后被执行.

4.若preHandle返回false,则afterCompletion方法不会被调用

5.在配置拦截器时,可以指定该拦截器对哪些请求生效,哪些请求不生效

  • XML
<!--配置自定义拦截器-spring配置文件-->
    <mvc:interceptors>
        <!--
        1. 第一种配置方式
        2. 使用ref 引用到对应的myInterceptor01
        3. 这种方式,会拦截所有的目标方法
        -->
        <!--<ref bean="myInterceptor01"/>-->

        <!--
        1. 第二种配置方式
        2. mvc:mapping path="/hi" 指定要拦截的路径
        3. ref bean="myInterceptor01" 指定对哪个拦截器进行配置
        -->
        <!--<mvc:interceptor>-->
        <!--    <mvc:mapping path="/hi"/>-->
        <!--    <ref bean="myInterceptor01"/>-->
        <!--</mvc:interceptor>-->

        <!--
        1. 第3种配置方式
        2. mvc:mapping path="/h*" 通配符方式 表示拦截 /h 打头的路径
        3. mvc:exclude-mapping path="/hello" /hello不拦截
        4. ref bean="myInterceptor01" 指定对哪个拦截器配置
        -->
        <mvc:interceptor>
            <mvc:mapping path="/h*"/>
            <mvc:exclude-mapping path="/hello"/>
            <ref bean="myInterceptor01"/>
        </mvc:interceptor>

        <!--
        1.配置的第二个拦截器
        2.多个拦截器在执行时,是顺序执行
        -->
        <mvc:interceptor>
            <mvc:mapping path="/h*"/>
            <ref bean="myInterceptor02"/>
        </mvc:interceptor>
    </mvc:interceptors>
  • 后端
@Component
public class MyInterceptor01 implements HandlerInterceptor {

    /**
     * 1. preHandle() 在目标方法执行前被执行
     * 2. 如果preHandle() 返回false , 不再执行目标方法
     * 3. 该方法可以获取到request, response, handler
     * 4. 这里根据业务,可以进行拦截,并指定跳转到哪个页面
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("--MyInterceptor01--preHandle()---");
        //获取到用户提交的关键字
        String keyword = request.getParameter("keyword");
        if("病毒".equals(keyword)) {
            //请求转发到warning
            request.getRequestDispatcher("/WEB-INF/pages/warning.jsp")
                    .forward(request,response);

            return false;
        }
        System.out.println("得到到keyword= "+ keyword);
        return true;
    }

    /**
     * 1. 在目标方法执行后,会执行postHandle
     * 2. 该方法可以获取到 目标方法,返回的ModelAndView对象
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("--MyInterceptor01--postHandle()--");
    }

    /**
     * 1. afterCompletion() 在视图渲染后被执行, 这里可以进行资源清理工作
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("--MyInterceptor01--afterCompletion()--");
    }
}


@Component
public class MyInterceptor02 implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("--MyInterceptor02--preHandle--");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("--MyInterceptor02--postHandle--");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("--MyInterceptor02--afterCompletion--");
    }
}

异常处理

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

异常优先级: 局部异常>全局异常>SimpleMappingExceptionResolver>tomcat默认机制

局部异常

/**
	 * 如果方法上标注了@ExceptionHandler,就是一个局部异常处理方法
     * 1. localException 方法处理局部异常
     * 2. 这里老师处理ArithmeticException.class,NullPointerException.class
     * 3. Exception ex: 生成的异常对象,会传递给ex, 通过ex可以得到相关的信息
     *    , 这里程序员可以加入自己的业务逻辑
     * @return
     */
    @ExceptionHandler({ArithmeticException.class,NullPointerException.class,NumberFormatException.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 {

    /**
     * 老师解读
     * 1. 全局异常就不管是哪个Handler抛出的异常,都可以捕获 , @ExceptionHandler({异常类型})
     * 2. 这里老师处理的全局异常是NumberFormatException.class,ClassCastException.class
     * 3. Exception ex 接收抛出的异常对象
     *
     * @return
     */
    //
    @ExceptionHandler({NumberFormatException.class, ClassCastException.class, AgeException.class})
    public String globalException(Exception ex, HttpServletRequest request) {
        System.out.println("全局异常处理-" + ex.getMessage());
        //如何将异常的信息带到下一个页面.
        request.setAttribute("reason", ex.getMessage());
        return "exception_mes";
    }

}

SimpleMappingExceptionResolver

springDispatcherServlet-servlet.xml
	<!--配置统一处理异常Bean-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!--数组越界单个异常处理-->
                <!--<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>-->
                <!--全部未知异常处理-->
                <!--<prop key="java.lang.Exception">allEx</prop>-->
            </props>
        </property>
    </bean>

自定义异常

/**
 * @ResponseStatus(reason = "年龄需要在1-120之间", value = HttpStatus.BAD_REQUEST)  注解,可以自定义异常的说明提供给tomcat或其他反射框架
 * public AgeException(String message) 这个则是给异常额外提供的消息,可以提供给程序员自己使用
 */
@ResponseStatus(reason = "年龄需要在1-120之间", value = HttpStatus.BAD_REQUEST)
public class AgeException extends RuntimeException {

    public AgeException() {
    }

    public AgeException(String message) {
        super(message);
    }
}
posted @ 2022-07-03 23:21  邵泽龙  阅读(283)  评论(0编辑  收藏  举报