Java开发 - Spring MVC框架初体验(四)

简介: Java开发 - Spring MVC框架初体验

了解RESTful


RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以 使用XML格式定义或JSON格式定义。RESTFUL适用于移动互联网厂商作为业务接口的场景,实现第三方OTT调用移动网络资源的功能,动作类型为新增、变更、删除所调用资源。


RESTful的设计风格表现为将某些唯一的请求参数的值放在 URL中,使之成为URL的一部分,比如:http://localhost:8080/springmvc_war_exploded/user/100021


URL后面的一串数字就是RESTful的表现,这里的数字可以根据用户ID,替换为其他的用户ID 。RESTful只是一种设计风格,并不是一种规定,所以没有明确的执行方式,实际开发中,我们多会这么设计:


http://localhost:8080/springmvc_war_exploded/user/100021 查询ID为100021的用户信息


http://localhost:8080/springmvc_war_exploded/user/100021/delete 删除ID为100021的用户信息


RESTful建议根据希望获取数据的方式来选择请求方式,比如增加用post,删除用delete,修改用put,查询用get。而在实际开发中,我们多数只使用get和post,以查询为目的的URL用get,否则都用post。


在服务端,当涉及这样的URL时,需要使用占位参数{参数},使用@PathVariable注解请求参数, 可以将占位符的实际值注入到请求参数中!

    @GetMapping("/{id}/info.page")
    public UserVO userInfo(@PathVariable Long id) {
        UserVO userVO = new UserVO();
        userVO.setUsername("codingfire");
        userVO.setPassword("123456");
        return userVO;
    }

有时候,我不想把id这个参数暴漏给客户端,那么我就可以通过此注解来指定一个名字给客户端用,而我这里还是使用id。

    @GetMapping("/{id}/info.page")
    public UserVO userInfo(@PathVariable("userID") Long id) {
        UserVO userVO = new UserVO();
        userVO.setUsername("codingfire");
        userVO.setPassword("123456");
        return userVO;
    }

上传id的时候,这个占位符我希望必须时数字,数字之外的我就不处理,这种时候,可以通过正则表达式来进行处理:

    @GetMapping("/{id:[0-9]+}/info.page")
    public UserVO userInfo(@PathVariable Long id) {
        UserVO userVO = new UserVO();
        userVO.setUsername("codingfire");
        userVO.setPassword("123456");
        return userVO;
    }

还有一种情况,多种不冲突的正则表达式用在同一个URL上也是允许的,只要参数不一致就可以,看下怎么做:

    @GetMapping("/{id:[0-9]+}/info.page")
    public UserVO userInfo(@PathVariable Long id) {
        UserVO userVO = new UserVO();
        userVO.setUsername("codingfire");
        userVO.setPassword("123456");
        return userVO;
    }
    @GetMapping("/{username:[a-zA-Z]+}/info.page")
    public UserVO userInfo(@PathVariable String username) {
        UserVO userVO = new UserVO();
        userVO.setUsername("codingfire");
        userVO.setPassword("123456");
        return userVO;
    }

以上,我们都是用了正则表达式,如果我不使用正则,会怎么样呢?先看看怎么写:

    @GetMapping("/list/info.page")
    public UserVO userList() {
        ...
    }

当我们使用这个URL:/list/info.page时,就不会因为匹配到了/{id:[0-9]+}/info.page而不执行这个URL,这里明确是没有使用正则的,所以使用正则并不会影响到精确的URL匹配。


关于响应正文


先前我们已经说过了,响应正文可以是String类型,也可以是json类型,但在实际开发中,我们看到的响应正文都是比较规范的,比如响应码,响应信息,响应数据,我们可以通过抓包来看其他应用的响应格式,会发现,不同的公司,他们的响应格式几乎是一样的,这就是行业内不成文的规定,大家默认的一种通用方式。


下面我们来写下这个通用数据格式的类:

package cn.codingfire.springmvc.web;
public class JsonResult<T> {
    //状态码
    private Integer state;
    //消息,包括成功信息和失败信息
    private String message;
    //返回数据,我们希望它可以转化为任何类型,所以一般会使用泛型
    private T data;
    public Integer getState() {
        return state;
    }
    public void setState(Integer state) {
        this.state = state;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
}

通用返回的类写好后,我们来用一下:

    @GetMapping("/codingFireInfo.page")
    public JsonResult<UserVO> codingFireInfo() {
        UserVO userVO = new UserVO();
        userVO.setUsername("codingfire");
        userVO.setPassword("123456");
        JsonResult jsonResult = new JsonResult();
        jsonResult.setState(200);
        jsonResult.setMessage("请求成功");
        jsonResult.setData(userVO);
        return jsonResult;
    }

接着直接重启Tomcat,在浏览器输入我们的URL,来来返回的结果,注意乱码问题,上面已经给出解决方案:

http://localhost:8080/springmvc_war_exploded/user/codingFireInfo.page

1.png

我们发现这么做是OK的。我们再来仔细看我们的代码,封装度其实并不高,我们希望状态码是枚举类型的,且我们可以直接使用成功失败的方法,不需要我们手写状态码和状态,因为这样很容易出错,这些值我们称之为魔法值,在阿里开发手册中,这种情况是严厉禁止的,因为会代码的可读性降低,所以还需要对这个类进行高度封装,这并不难,博主就不再给出封装部分的代码,大家可以自己手写一下,做个练习。


统一处理异常


异常在代码中还是比较经常出现的,基于面向对象语言的特点,我们希望异常可以统一处理,统一管理,Spring MVC恰好提供了这种方式,使我们在任何地方都可以直接抛出异常,交由统一的异常处理机制来处理,前提是你没有显示使用try...catch来捕获并处理异常。


处理异常有一种在当前类处理当前类异常的方式,直接添加方法如下:

    @ExceptionHandler
    public String handleException(NullPointerException e) {
        return "NullPointerException!";
    }

缺点是只能处理本类的异常,对于其他类的异常则鞭长莫及,这样就会造成重复写代码的情况,所以我们多会使用统一的异常处理机制,这种方式只做说明,知道就可以。


关于统一异常处理,需要自定义类来统一处理,我们来看看这个自定义类该怎么写:

package cn.codingfire.springmvc.exception;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler
    public String handleException(NullPointerException e) {
        return "NullPointerException";
    }
    @ExceptionHandler
    public String handleNumberFormatException(NumberFormatException e) {
        return "NumberFormatException";
    }
    @ExceptionHandler
    public String handleThrowable(Throwable e) {
        e.printStackTrace();
        return "Throwable";
    }
}

这样,处理异常的代码就可以放在专门的类中,在类上添加@ControllerAdvice注解,由于目前主流的响应方式都是“响应正文”的,所以可将@ControllerAdvice替换为 @RestControllerAdvice 。


不同的异常类型,可以使用不同的方法来单独处理,就像上面写的方式那样。一般我们会将异常的相关信息进行输出,这样可以方便开发者观察和分析问题。


了解这部分内容的同学会知道,@ExceptionHandler注解指定的异常类型优先级高于通过参数指定的异常类型的方式,而且此注解指定异常类型时,同一个方法也可以处理多种异常的情况,所以我们处理如下:

package cn.codingfire.springmvc.exception;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler({NullPointerException.class, NullPointerException.class})
    public String handleException(Throwable e) {
        return "Throwable";
    }
    @ExceptionHandler(NumberFormatException.class)
    public String handleNumberFormatException(Throwable e) {
        return "NumberFormatException";
    }
    @ExceptionHandler(Throwable.class)
    public String handleThrowable(Throwable e) {
        return "Throwable";
    }
}


Interceptor(拦截器)


Interceptor是拦截器,在Spring MVC框架中,拦截器可以在请求处理前后执行一些额外的代码,比如在请求用户信息之前判断用户登录状态来决定是否拒绝访问或者放行。虽然可以这么做,但拦截器的目的并不是拦截并阻止运行,其目的是处理多种不同请求的处理过程。多发生在不同的请求需要执行高度相似的代码,比如验证用户的登录状态。


使用拦截器和使用统一异常处理一样,需要使用自定义的类,并实现HandlerInterceptor接口,重写里面的三个方法,我们来看看这个类怎么写:

package cn.codingfire.springmvc.Interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //根据用户登录状态选择是否放行,不放行选择false
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //请求执行过程中
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //在请求执行结束后再统一执行某些操作
    }
}

拦截器写好了,但是却不能自动生效,因为拦截器都需要被注册才能生效,注册过程通过重写 WebMvcConfigure接口中的addInterceptors()方法即可,我们会到Spring MVC的配置类中看看该怎么注册:

package cn.codingfire.springmvc.config;
import cn.codingfire.springmvc.Interceptor.LoginInterceptor;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ComponentScan("cn.codingfire.springmvc")
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/user/codingFireInfo.page");
    }
}

以上是匹配单个接口,但一般不会这么用,前面讲过,匹配的是某一类接口。


匹配用户模块所有接口:


//匹配user下所有接口
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/user/*");

通配符的运用,可以匹配user下所有接口,addPathPatterns的参数也可以传数组,可以把要添加的URL放在数组中。


匹配多层级


//匹配user下所有接口
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/user/**");

匹配多层级需要两个通配符。


不能匹配的情况


//匹配user下所有接口
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/user/10021/info.page");

是不是刚刚讲过的RESTful风格?这里的数字是变化的,所以没法匹配。


能匹配路径,也能排除某些路径


registry.addInterceptor(new LoginInterceptor())
        .addPathPatterns("/user/codingFireInfo.page")
        .excludePathPatterns("/user/register.page", "/user/login.page");

通过excludePathPatterns方法排除路径,路径可以是多个,也可以使用通配符,也可以给数组,和添加路径的用法一样。


结语


写到这里,Spring MVC框架就跟大家分享完了,看到这里没你已经可以写一个简单的Spring MVC框架的项目了。甚至一些小的项目,使用这个框架也完全够了,只要有一台服务器,它就可以正常工作,给客户端提供服务。下一篇,咱们SSM框架见。觉得不错就点个赞再走吧!

目录
相关文章
|
25天前
|
Java 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
19天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
38 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
6天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
52 13
|
11天前
|
算法 Java API
如何使用Java开发获得淘宝商品描述API接口?
本文详细介绍如何使用Java开发调用淘宝商品描述API接口,涵盖从注册淘宝开放平台账号、阅读平台规则、创建应用并申请接口权限,到安装开发工具、配置开发环境、获取访问令牌,以及具体的Java代码实现和注意事项。通过遵循这些步骤,开发者可以高效地获取商品详情、描述及图片等信息,为项目和业务增添价值。
44 10
|
5天前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
40 2
|
14天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
20天前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
31 4
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
223 2
|
3天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
35 14
|
25天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
42 1
SpringBoot入门(7)- 配置热部署devtools工具