AngeldayAngelday
Home
  • Java
  • MySQL
  • Maven
  • Spring
  • SpringMVC
  • SpringBoot
  • Mybatis-Plus
  • SpringCloud
  • Redis
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • VBA
  • CentOS
  • ApachePOI
  • 摄影
  • Photoshop
  • Premiere
  • Lightroom
  • Game
Home
  • Java
  • MySQL
  • Maven
  • Spring
  • SpringMVC
  • SpringBoot
  • Mybatis-Plus
  • SpringCloud
  • Redis
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • VBA
  • CentOS
  • ApachePOI
  • 摄影
  • Photoshop
  • Premiere
  • Lightroom
  • Game
  • SpringMVC

SpringMVC

  • SpringMVC 技术与 Servlet 技术功能相同,均属于Web层开发技术
  • SpringMVC 是一种基于Java实现的MVC模型的轻量级Web框架
  • 相较于 Servlet 使用简单,开发便捷,灵活性强。

由于目前绝大多数项目都是使用Springboot框架开发,所以本文只讲述一些重要概念,而不去实现。

案例

第一步:导坐标

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

第二步:创建SpringMVC控制器类

等同于Servlet功能

@Controller					    //声明这个类是一个Bean	1
public class userControler {
    @RequestMapping("/save")    //设置当前操作的访问路径	2
    @ResponseBody			    //设置当前操作的返回值类型3
    public String save(){
        System.out.println("user save...");
        return "{'info':'springmvc'}";
    }
}

第三步:创建SpringMVC的配置类

@Configuration 
@ComponentScan("com.protectark.mysqlt")
public class SpringMvcConfig {
}

第四步:定义一个servlet容器启动的配置类,在里面加载spring的配置

public class servletInConfig extends AbstractDispatcherServletInitializer {
    
    //加载SpringMVC容器配置
    @Override
    protected WebApplicationContext createServletApplicationContext() {

        AnnotationConfigWebApplicationContext act = new AnnotationConfigWebApplicationContext();
        //注册配置
        act.register(SpringMvcConfig.class);
        return act;
    }
    
    //设置那些请求归属springMVC处理
    @Override
    protected String[] getServletMappings() {
        //设置所有请求归SpringMVC管理
        return new String[]{"/"};
    }

    //加载spring容器配置
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}

注解说明

在以上案例中,出现了一些新的注解

注解类型位置作用
@Controller类注解SpringMVC控制器类上设定SpringMvc的核心控制器bean
@RequestMapping(地址)方法注解SpringMVC控制器类中方法上设置当前控制器方法请求访问路径
@ResponseBody方法注解SpringMVC控制器方法定义上方设置当前控制器方法响应内容为当前返回值,无需解析

工作流程说明

启动服务器初始化过程

  1. 服务器启动,执行ServletContainersInitConfig类,初始化web容器
  2. 执行createServletApplicationContext方法,创建了webApplicationcontext对象
  3. 加载SpringMvcConfig
  4. 执行@ComponentScan加载对应的bean
  5. 加载UserController,每个@RequestMapping的名称对应一个具体的方法
  6. 执行getServletMappings方法,定义所有的请求都通过springMVC

单次请求过程

  1. 发送请求localhost/ save
  2. web容器发现所有请求都经过springMVC,将请求交给SpringMVC处理
  3. 解析请求路径/save
  4. 由/save匹配执行对应的方法save( )
  5. 执行save()
  6. 检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方

简化配置类

Spring提供了一个AbstractDispatcherServletInitializer的子类: AbstractAnnotationConfigDispatcherServletInitializer

public class servletInConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    //Spring配置路径
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{springConfig.class};
    }

    //SpringMVC配置路径
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{springMvcConfig.class};
    }

    //拦截路径
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

请求与响应

解决路径冲突

在团队多人开发,如何解决请求路径冲突

设置模块名作为请求路径的前缀 两种方法:

  1. 修改每个方法的路径@RuquestMapping如: "/user/save""/book/save"
  2. 修改类的请求路径 "/user" "/save" "/book" "/save"
注解类型位置作用
@RequestMapping(value)方法注解 类注解SpringMVC控制器类中方法上设置当前控制器方法请求访问路径,如果设置在类上统一设置当前控制器方法请求访问路径前缀

示例:

@Controller
@RequestMapping("/user")
public class userControler {
    @RequestMapping("/save")
    @ResponseBody
    public String save(){
        return "{'info':'springmvc'}";
    }
}
  • value(默认):请求访问你路径,或访问路径前缀

解决中文乱码

关于发送的中文乱码,可以在“servlet容器启动的配置类” servletInConfig中覆盖父类的方法: 为web容器添加过滤器并指定字符集,Spring-web包中提供了专用字符过滤器

//乱码处理
@Override
protected Filter[] getServletFilters() {
    //获取字符过滤器
    CharacterEncodingFilter filter = new CharacterEncodingFilter();
    //设定字符集
    filter.setEncoding("UTF-8");
    return new Filter[]{filter};
}

请求参数与形参不同

例如,定义的方法中形参名为username和age,但是实际传参为name,age

@Controller
public class userControler {
    @RequestMapping("/save")
    @ResponseBody
    public String save(String username, int age){
        System.out.println(username + age);
        return "{'info':'springmvc'}";
    }
}

测试之后就会发现

username为null

此时我们可以使用一个注解,用来声明参数名: @RequestParam("name")

@Controller
public class userControler {
    @RequestMapping("/save")
    @ResponseBody
    public String save(@RequestParam("name") String username,
                       @RequestParam("age") int age){
        System.out.println(username + age);
        return "{'info':'springmvc'}";
    }
}

传递参数为实体类

如果我们需要的参数过多,比如在新增用户时需要大量的数据,这样我们一般会制作一个实体类,来进行接收。

@Controller
public class userControler {
    @RequestMapping("/save")
    @ResponseBody
    public String save(User user){
        return "{'info':'springmvc'}";
    }
}
public class User{
    private String username;
    private int age;
    //等其他属性
    //重写getter,setter,toString方法
}

这样请求时,如果请求的属性名与实体类中的属性名完全一致,则会自动放入user中。

传递JSON数据

传递JSON数据的时候,我们的数据并不是在请求头的参数中,而是在请求体中。

所以我们在接收的时候也要添加注解,来声明这是请求体中的数据

下面就来实际写一下:

首先应该导入JSON数据转化相关坐标

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.0</version>
</dependency>

然后告诉SpringMVC开启JSON转化数据

只需要再SpringMvcConfig中加入@EnableWebMvc注释,开启JSON转化数据

@Configuration 
@ComponentScan("com.protectark.mysqlt")
@EnableWebMvc
public class SpringMvcConfig {
}

由于JSON请求再请求体当中所以,再Controller方法中 对于形参的修饰不能使用@RequestParam 要使用@RequestBody

@RequestMapping("/inputuser")
@ResponseBody
public String init(@RequestBody User user){
    System.out.println("input user ==>"+user);
    return "{'info':'springMVC'}";
}

到此完成。

注解类型位置作用
@EnableWebMvc配置类注解SpringMVC配置类定义上方开启SpringMVC多项辅助功能
@RequestBody形参注解Controller方法形参前将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次

RequestBody与RequestParam对比

  • 区别

    • @RequestParam用于接收ur1地址传参,表单传参【application/x-www-form-urlencoded)
    • @RequestBody用于接收json数据【application/json】
  • 应用

    • 后期开发中,发送json格式数据为主,@RequestBody应用较广

    • 如果发送非json格式数据,选用@RequestParam接收请求参数

后面还有一个@PathVariable 属于形参注解,绑定路径参数与形参的关系

@RequestMapping("/users/{id}")
@ResponseBody
public String init(@PathVariable Integer id){
    System.out.println("input id ==>"+id);
    return "{'info':'springMVC'}";
}

日期类型参数传递

SpringMVC可以直接接收String类型的参数,并自动转为日期类型,如果需要指定格式可以使用注解

@DateTimeFormat

@RequestMapping("/inputuser")
@ResponseBody
public String init(@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date date){
    System.out.println("input date ==>"+date);
    return "{'info':'springMVC'}";
}
注解类型位置作用
@DateTimeFormat形参注解SpringMVC控制器方法形参前设定日期时间型数据格式

属性:pattern:日期时间格式字符串

响应

如果要响应json数据,则只需要返回实体类即可

@RequestMapping("/select")
@ResponseBody
public User select(){
	User user = new User();
    return user;
}

SpringMVC会自动将实体类转化为json数据

注解类型位置作用
@ResponseBody方法注解SpringMVC控制器方法上设置当前控制器方法响应内容为当前返回值,无需解析

REST 风格

概念

REST(Representational state Transfer),表现形式状态转换

  • 传统风格资源描述形式
    • http://localhost/user/getById?id=1
    • http://localhost/user/saveUser
  • REST风格描述形式
    • http://localhost/user/1
    • http://localhost/user

优点:书写简化,隐藏资源访问方式,无法通过地址得知对资源是哪种请求方式。

按照REST风格访问资源时使用行为动作区分对资源进行了何种操作

  • http://localhost/users 查询全部用户信息 GET(查询)
  • http://localhost/users/1 查询指定用户信息 GET(查询)
  • http://localhost/users 添加用户信息 POST(新增/保存)
  • http://localhost/users 修改用户信息 PUT(修改/更新)
  • http://localhost/users/1 删除用户信息 DELETE(删除)

这种方式属于约定方式,称为REST风格。根据这种风格对资源访问的方式称为RESTful

描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts...

RESTful 快速开发

由于REST风格都使用JSON进行通信,所以每一个方法都需要带有@ResponseBody,所以我们可以将这个注解放到控制器类上,又因为我们所有的控制器类都有@Controller和@ResponseBody注解,所以SpringMVC就将这两个合二为一:@RestController

又因为我们使用的请求方式,绝大多数就四种,所以@RequestMapping注解也可以根据请求方式衍生出

  • @PostMapping
  • @GetMapping
  • @PutMapping
  • @DeleteMapping

使用这种方式,我们的Controller就由

@Controller
public class userControler {
    @RequestMapping(value = "/users/save",method = RequestMethod.Get)
    @ResponseBody
    public String save(@Requestbody User user){
        return "{'info':'springmvc'}";
    }
    
    @RequestMapping(value = "/users/delete",method = RequestMethod.Delete)
    @ResponseBody
    public String save(@Requestbody User user){
        return "{'info':'springmvc'}";
    }
}

变为了

@RestController
@RequestMapping("/users")
public class userControler {
	@GetMapping()
     public String save(@Requestbody User user){ 
         return "{'info':'springmvc'}";
    }
    @DeleteMapping()
    public String save(@Requestbody User user){
        return "{'info':'springmvc'}";
    }
}

统一结果类

设置统一结果类有以下好处:

一、提高可维护性

  • 集中管理返回结果,方便统一修改和扩展。
  • 清晰定义错误处理逻辑,便于维护和调试。

二、增强稳定性和可靠性

  • 规范异常信息,避免前端解析错误。
  • 实现全局异常处理,防止系统崩溃。

三、提升团队协作效率

  • 为前后端提供明确规范,减少沟通成本。
  • 可复用性高,减少代码冗余。

四、便于监控和统计

  • 统一日志记录格式,方便分析和监控。
  • 利于数据统计分析,发现问题和瓶颈。

一般我们的统一结果类由三部分组成,分别是code、message、data

/**
 * 后端统一返回结果
 * @param <T>
 */
@Data
public class Result<T> implements Serializable {

    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //错误信息
    private T data; //数据

    public static <T> Result<T> success() {
        Result<T> result = new Result<T>();
        result.code = 1;
        return result;
    }

    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<T>();
        result.data = object;
        result.code = 1;
        return result;
    }

    public static <T> Result<T> error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }
}

对于 code 我们一般将提前定义好的状态码写道一个类中,该类用于存放所有用到的状态码常量。

public class Code {
    public static final Integer SAVE_OK = 200;
    /* 等等 */
}

异常处理器

基本概念

开发中不可避免会出现异常现象。

出现异常现象的常见位置与常见诱因如下:

  • 框架内部抛出的异常:因使用不合规导致
  • 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
  • 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
  • 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
  • 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)

我们可以发现,所有的层级都会抛出异常,所以我们采取的做法是,全部上抛,统一处理。 所有异常均抛到表现层进行处理

Spring提供的异常处理器:

  • 集中统一处理项目中出现的异常
//处理REST风格的Controller
@RestControllerAdvice
public class ProjectExceptionAdvice {
    
    // 拦截异常的种类
    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex){
        return new Result(666,null);
    }
}

注意:

  1. 由于我们拦截器属于SpringMVC技术,对SpringMVC进行加强,所以要吧拦截器放到能被SpringMvcConfig识别到的路径下,一般放到 controller 包下

  2. 前面我们也说了,要返回统一结果类,所以我们要修改异常处理器,使其返回结果类。

项目中异常处理

项目异常分类

  • 业务异常(BusinessException)
    • 规范的用户行为产生的异常
    • 不规范的用户行为操作产生的异常
  • 系统异常(SystemException)
    • 项目运行过程中可预计且无法避免的异常
  • 其他异常(Exception)
    • 编程人员未预期到的异常

项目异常处理方案

  • 业务异常(BusinessException)
    • 发送对应消息传递给用户,提醒规范操作
  • 系统异常(SystemException)
    • 发送固定消息传递给用户,安抚用户
    • 发送特定消息给运维人员,提醒维护
    • 记录日志
  • 其他异常(Exception)
    • 发送固定消息传递给用户,安抚用户
    • 发送特定消息给编程人员,提醒维护(纳入预期范围内)
    • 记录日志

定义异常

依据上面的分类,可以定义出两种异常,系统异常与业务异常

我们创建新的包exception,用于存放自定义异常

/**
 * 自定义系统异常
 */
public class SystemException extends RuntimeException{
    
    //给异常编号
    private Integer code;

    /* 此处省略getter&setter */

    //重写所有的构造方法
    public SystemException(Integer code) {
        this.code = code;
    }

    public SystemException(String message, Integer code) {
        super(message);
        this.code = code;
    }

    public SystemException(String message, Throwable cause, Integer code) {
        super(message, cause);
        this.code = code;
    }

    public SystemException(Throwable cause, Integer code) {
        super(cause);
        this.code = code;
    }

    public SystemException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Integer code) {
        super(message, cause, enableSuppression, writableStackTrace);
        this.code = code;
    }
}

同理创造出BusinessException

抛出自定义异常

到业务层中可能出现异常的位置,将代码包装,转化为抛出异常

public User getById(Integer id){
    User user = userMapper.getById(id);
    if (user == null) {
        //此处code值我们定义到Code常量中。
        throw new SystemException(Code.SYSTEM_NULL,"结果为空");
    }
    return user;
}

处理自定义异常

@RestControllerAdvice
public class ProjectExceptionAdvice {
    
    @ExceptionHandler(SystemException.class)
    public Result doSystemException(SystemException ex){

        /* 记录日志 */
        /* 发送消息给运维 */
        return new Result(ex.getCode,ex.getMessage);
    }

    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex){
        return new Result(666,null);
    }
}

拦截器

拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVc中动态拦截控制器方法的执行 作用:

  • 在指定的方法调用前后执行预先设定的代码
  • 阻止原始方法的执行 拦截器的作用主要包括:
  • 权限与访问控制:验证用户身份及权限,限制对特定资源的访问,保障系统安全。
  • 日志与性能监控:记录请求信息,分析性能瓶颈,为优化提供数据支持。
  • 数据处理:对请求数据预处理和响应数据后处理,确保数据规范、安全。
  • 防止重复提交与验证:防止表单重复提交,验证表单数据合法性。
  • 系统集成:控制系统间交互,执行统一业务逻辑,提高复用性和可维护性。

与过滤器区别

  • 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
  • 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVc的访问进行增强
  • 设计目的不同:拦截器侧重于业务逻辑前后处理,与业务关联性强;过滤器主要对请求和响应进行一般性过滤和处理。
  • 执行顺序:拦截器按在 Spring MVC 中的注册或注解顺序执行特定方法;过滤器由 Web 容器的部署描述符或注解指定顺序执行doFilter方法。

使用

首先创建一个新的包interceptor,这个包可以放到controller包下,因为拦截器就是对表现层做增强的。

创建拦截器类,并实现 HandlerInterceptor 接口

/**
 * 该拦截器要作为bean被SpringMVC配置类识别
 */
@Component
public class Interceptor implements HandlerInterceptor {
    /* 控制器执行前 */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
        // 此处true为全部放行
        return true;
    }

    /* 控制器执行后 */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /* Post执行之后 */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

创建SpringMVC配置类

@Configuration
public class SpringMvcConfig extends WebMvcConfigurationSupport {

    @Autowired
    private Interceptor interceptor;

    /* /user 拦截路径 */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor).addPathPatterns("/users");
    }
}

配置多个拦截器

根据 Interceptor 创建一个 Interceptor2 的拦截器 修改 SpringMvcConfig

@Configuration
public class SpringMvcConfig extends WebMvcConfigurationSupport {

    @Autowired
    private Interceptor interceptor;

    @Autowired
    private Interceptor2 interceptor2;
    /* /user 拦截路径 */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor).addPathPatterns("/users");
        registry.addInterceptor(interceptor2).addPathPatterns("/users");
    }
}