JavaEE学习之旅---->SpringMVC

Posted 赵jc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaEE学习之旅---->SpringMVC相关的知识,希望对你有一定的参考价值。

MVC设计模式介绍

MVC(Model View Controller)是软件工程中的一种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分。
在这里插入图片描述

  • Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。 通常模型对象负责在数据库中存取数据。
  • View(视图)是应用程序中处理数据显示的部分。 通常视图是依据模型数据创建的。
  • Controller(控制器)是应用程序中处理用户交互的部分。 通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

常用注解

@Controller

@Controller注解标注是一个类是Web控制器,其和@Component注解等价,只不过在Web层使用,其 便于区分类的作用。

@RequestMapping

@RequestMapping是Spring Web应用程序中最常被用到的注解之一。
在对SpringMVC进行配置的时候,需要指定请求与处理方法之间的映射关系,这时候就需要使用@RequestMapping注解。该注解可以在控制器类的级别和其方法级别上使用。

@ResponseBody

表示将控制器方法的返回序列化作为响应体内容返回前端。

  • 返回类型为String,表示响应Content-Type: text/plain,且响应体为控制器方法的字符串返回值

  • 返回类型为普通Java类型,表示响应Content-Type: application/json,以返回对象序列化为json后作为响应体。

  • @ResponseBody可以使用在类上,表示该类中所有方法都是默认以返回值作为响应体,也就是所有方法都使用@ResponseBody。
    注意:如果返回值为null,表示响应体内容为空

  • 组合注解

可以使用组合注解来完成同时定义多个注解的效果,如:@RestController,@GetMapping,@PostMapping

@PathVariable

一般的 URI 服务路径都是固定的,SpringMVC提供了 restful 风格可以变化的 URI。示例

@GetMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    return "主人id:"+ownerId+", 宠物id:"+petId; } 

说明:

  • {}是将服务路径 URI 中的部分定义为变量,之后在方法参数中获取该路径变量。更多格式可参考 URI格式。
  • 请求 /arg/owners/1/pets/2 ,显示的网页内容为: 主人id:1, 宠物id:2
  • 变量已经定义了为Long长整形,所以不能转换为Long的 URI 都会报错,如请求
  • /arg/owners/abc/pets/2 就会报错,响应状态码400。 变量名ownerId,petId必须和 URI中的定义名称一致。

@RequestParam

当请求数据要绑定到某个简单对象时,可以使用@RequestParam。
URL 中的请求数据queryString

  • 请求头,Content-Type为表单默认提交的格式 application/x-www-form-urlencoded ,请求体中的数据
  • 请求头,Content-Type为 multipart/form-data ,请求体中的数据。 form-data 可以提交文本数据,也可以提交二进制文件。
  • 以上简单对象包括:基本数据类型、包装类型、MultipartFile(接收二进制文件)

需要注意@RequestParam注解参数默认为 required=true ,如果不传该参数就会报错,需要指定为: @RequestParam(required = false) 。

POJO对象

POJO(Plain Ordinary Java Object):简单的 java 对象,实际就是属性提供了Getter,Setter方法的普通对象。
使用 java 对象和使用@RequestParam注解非常类似,只是有点细节不同:

  • @RequestParam是以方法参数变量名和传入的键对应,POJO对象作为方法参数时,是以POJO对 象中的属性名对应传入的键
  • @RequestParam默认必须传入该请求数据,而 POJO 对象是根据请求数据来填充属性,如果请求数据没有,则属性就是默认值(new对象时每个属性的默认值)。

@RequestBody

当请求的数据类型Content-Type为 application/json 时,需要显示的使用@RequestBody注解

@RequestPart

对于请求的数据类型Content-Type为 multipart/form-data 时,二进制文件除了以上@RequestParam和 POJO 对象的方式外,还可以使用@RequestPart。

SpringMVC自定义配置

SpringBoot中使用SpringMVC非常方便,SpringBoot提供了大部分的MVC默认功能,并且需要自定义某部分功能也非常方便,在配置类中实现 WebMvcConfigurer 接口,根据需要重写方法即可:

package org.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AppConfig implements WebMvcConfigurer {
}

自定义后端路径映射

重写 configurePathMatch 方法,实现时,可以添加统一的服务路径前缀:

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    //Controller路径,统一添加请求的路径前缀,第二个参数,c是Controller类,返回boolean
表示是否添加前缀
    //所有Controller请求路径,都要带/api的前缀
    configurer.addPathPrefix("api", c->true);
}

此时所有定义的服务路径都要在前面加上 /api 再能访问,如 /arg/param1 现在应该访问/api/arg/param1 。

自定义Controller拦截器

重写 addInterceptors 方法,实现时,需要指定拦截器,并配置需要拦截、不拦截的路径。在客户端发起请求时,如果路径最终匹配该规则,则执行拦截器中的接口方法。以下示例,实现用户统一的会话管理:

首先定义一个用户登录接口:

package org.example.demo.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping("/login")
    public Object login(String username, String password, HttpServletRequest
req){
        Map<String, Object> map = new HashMap<>();
        //模拟用户登录时,用户名和密码校验
        if("abc".equals(username) && "123".equals(password)){
            //登录成功,创建session
            HttpSession session = req.getSession();
            //添加用户身份信息到session
            session.setAttribute("user", username+", "+password);
            map.put("用户名", username);
            map.put("密码", password);
       }
        return map;
   }
}

注意现在需要加上路径前缀,使用 /api/user/login 访问。
再定义一个拦截器,需要实现 HandlerInterceptor 接口,根据需要重写接口方法,如以下代码:

package org.example.demo.config.interceptor;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
    /**
 	 * 重写preHandle方法,表示请求路径最终匹配到某个Controller时,在调用控制器
     * 方法前,先执行本拦截器的前置处理逻辑
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        //session不为空,表示已登录
        if(session != null){
            //返回true,允许继续执行Controller中的方法
            return true;
       }
        //响应状态码设置为401
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        //返回false,不再执行Controller中的方法,直接响应一个空的响应体
        return false;
   }
}

最后在SpringMVC配置中,将该拦截器添加到配置中,:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginInterceptor())
       .addPathPatterns("/api/**")//添加路径拦截规则,**表示下级多级目录的任意字符匹.excludePathPatterns("/api/user/login");//排除登录路径
}

以上配置通过将后端路径统一加上前缀 /api ,再拦截除登录以外的其他后端接口,校验session来判断是否允许继续执行控制器方法,很好的实现了统一的用户会话管理,也是经常使用的手段。

@ControllerAdvice

@ControllerAdvice注解(控制器通知、控制器增强)定义的类,会自动注册为一个Bean对象,将扫描指定包中带@Controller注解的类:在客户端发起请求,映射到控制器方法时,结合其他注解或接口完成统一的增强功能。
注:可以不指定扫描的包,对容器中所有@Controller生效。

应用一:统一异常处理

此时需要结合@ExceptionHandler使用,可以实现控制器方法中出现异常后的统一异常处理。先定义一个抛异常的服务,在 org.example.demo.controller.TestRestController 中,定义如下方法:

@GetMapping("/exception")
public Object exception(){
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "张三");
    map.put(2, "李四");
    map.put(3, "王五");
    int i = 1/0;
    return map; }

注意:该接口访问路径为 /api/exception ,且因为配置了会话管理的拦截器,需要先登录以后再访问。接着再定义控制器通知及异常处理逻辑:

package org.example.demo.config;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
//在SpringBoot中使用,会扫描启动类所在包下所有@Controller类
@ControllerAdvice
public class ExceptionAdvice {
    //如果客户端请求,执行控制器方法抛Exception异常,会执行本方法
    @ExceptionHandler(Exception.class)
    //方法返回值作为响应体
    @ResponseBody
    public Object handle(Exception e){//方法参数即为捕获到的异常
        //构造响应数据
        Map<String, Object> map = new HashMap<>();
        map.put("success", false);
        map.put("code", "ERR000");
        map.put("message", e.getMessage());
        return map;
   }
}

此时在登录以后,访问 /api/exception 接口将会报错,抛出的异常属于Exception类,所以会进入@ExceptionHandler注解的方法,由该方法返回响应内容。可以看到,返回的数据为统一的异常信息。

应用二:响应数据格式的统一封装

此时需要结合ResponseBodyAdvice接口,可以实现对控制器方法返回数据的统一封装。

package org.example.demo.config;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
import java.util.Map;
//实现ResponseBodyAdvice接口,表示可以根据条件对返回的数据重写
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    //注入容器中的ObjectMapper,SpringBoot默认使用jackson框架中的ObjectMapper完成
json的序列化
    @Autowired
    private ObjectMapper objectMapper;
    //方法参数可以获取请求调用的控制器类及方法,再决定是否要执行响应内容重写
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) 
{
        return true;
   }
    //响应的内容在返回客户端之前,会执行本方法,重写之后在返回
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, 
MediaType selectedContentType, Class selectedConverterType, 
ServerHttpRequest request, ServerHttpResponse response) {
        //构造一个要重写给客户端的统一数据格式
        Map<String, Object> map = new HashMap<>();
        map.put("success", true);
        map.put("data", body);
        //如果控制器方法返回类型为字符串,响应的Content-Type为text/plain,手动设置为
json,并重写为序列化后的json字符串
        if(body instanceof String){
            try {
               
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                return objectMapper.writeValueAsString(map);
           } catch (JsonProcessingException e) {
                throw new RuntimeException("json序列化失败");
           }
       }
        return map;
   }
}

这种统一数据格式的封装方式,当控制器方法设置的返回类型为Object,返回对象为null时,还是不能封装为统一的格式,

以上是关于JavaEE学习之旅---->SpringMVC的主要内容,如果未能解决你的问题,请参考以下文章

JavaEE学习之旅---->SpringBoot

JavaEE学习之旅---->Spring Framework

SpringMVC 的学习冒险之旅

springmvc学习

JavaEE开发之SpringMVC框架整合1

JAVAEE框架技术之14SSM综合案例