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框架见。觉得不错就点个赞再走吧!

目录
相关文章
|
1月前
|
前端开发 Java 开发者
Java新手指南:在Spring MVC中使用查询字符串与参数
通过结合实际的需求和业务逻辑,开发者可以灵活地利用这些机制,为用户提供更丰富而高效的Web应用体验。
70 15
|
2月前
|
JSON 前端开发 Java
Java新手指南:如何在Spring MVC中处理请求参数
处理Spring MVC中的请求参数是通过控制器方法中的注解来完成的。这些注解包括 `@RequestParam`, `@PathVariable`, `@ModelAttribute`, `@RequestBody`, `@RequestHeader`, `@Valid`, 和 `@RequestMapping`。使用这些注解可以轻松从HTTP请求中提取所需信息,例如URL参数、表单数据或者JSON请求体,并将其转换成Java对象以供进一步处理。
141 17
|
1月前
|
安全 Oracle Java
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
162 0
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
|
2月前
|
并行计算 Java API
Java List 集合结合 Java 17 新特性与现代开发实践的深度解析及实战指南 Java List 集合
本文深入解析Java 17中List集合的现代用法,结合函数式编程、Stream API、密封类、模式匹配等新特性,通过实操案例讲解数据处理、并行计算、响应式编程等场景下的高级应用,帮助开发者提升集合操作效率与代码质量。
128 1
|
2月前
|
安全 Java API
Java 17 及以上版本核心特性在现代开发实践中的深度应用与高效实践方法 Java 开发实践
本项目以“学生成绩管理系统”为例,深入实践Java 17+核心特性与现代开发技术。采用Spring Boot 3.1、WebFlux、R2DBC等构建响应式应用,结合Record类、模式匹配、Stream优化等新特性提升代码质量。涵盖容器化部署(Docker)、自动化测试、性能优化及安全加固,全面展示Java最新技术在实际项目中的应用,助力开发者掌握现代化Java开发方法。
118 1
|
1月前
|
移动开发 Cloud Native 安全
Java:跨平台之魂,企业级开发的磐石
Java:跨平台之魂,企业级开发的磐石
|
2月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
180 0
|
2月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
110 0
|
前端开发 Java 数据库
Java面试题 - Spring
Java面试题 - Spring
180 0
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
223 0