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 数据库连接
Spring框架初识
Spring 是一个分层的轻量级开源框架,核心功能包括控制反转(IOC)和面向切面编程(AOP)。主要模块有核心容器、Spring 上下文、AOP、DAO、ORM、Web 模块和 MVC 框架。它通过 IOC 将配置与代码分离,简化开发;AOP 提供了声明性事务管理等增强功能。
33 21
Spring框架初识
|
7天前
|
存储 缓存 Java
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
32 3
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
|
19天前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
110 29
|
8天前
|
XML Java 开发者
通过springboot框架创建对象(一)
在Spring Boot中,对象创建依赖于Spring框架的核心特性——控制反转(IoC)和依赖注入(DI)。IoC将对象的创建和管理交由Spring应用上下文负责,开发者只需定义依赖关系。DI通过构造函数、setter方法或字段注入实现依赖对象的传递。Spring Boot的自动配置机制基于类路径和配置文件,自动为应用程序配置Spring容器,简化开发过程。Bean的生命周期包括定义扫描、实例化、依赖注入、初始化和销毁回调,均由Spring容器管理。这些特性提高了开发效率并简化了代码维护。
|
1月前
|
并行计算 算法 Java
Java中的Fork/Join框架详解
Fork/Join框架是Java并行计算的强大工具,尤其适用于需要将任务分解为子任务的场景。通过正确使用Fork/Join框架,可以显著提升应用程序的性能和响应速度。在实际应用中,应结合具体需求选择合适的任务拆分策略,以最大化并行计算的效率。
51 23
|
23天前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
42 6
|
1月前
|
开发框架 运维 监控
Spring Boot中的日志框架选择
在Spring Boot开发中,日志管理至关重要。常见的日志框架有Logback、Log4j2、Java Util Logging和Slf4j。选择合适的日志框架需考虑性能、灵活性、社区支持及集成配置。本文以Logback为例,演示了如何记录不同级别的日志消息,并强调合理配置日志框架对提升系统可靠性和开发效率的重要性。
|
1月前
|
人工智能 Java API
阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手
本次分享的主题是阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手,由阿里云两位工程师分享。
阿里云工程师跟通义灵码结伴编程, 用Spring AI Alibaba来开发 AI 答疑助手
|
29天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
233 17
Spring Boot 两种部署到服务器的方式
|
29天前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
61 17
springboot自动配置原理

热门文章

最新文章