Springmvc学习笔记

本文详细介绍了SpringMVC的设计模式、工作流程,包括请求转发与重定向,以及常用的注解如@Controller、@RequestMapping、@ResponseBody等的使用。此外,还讲解了如何进行SpringMVC的自定义配置,如添加路径前缀和自定义拦截器,并通过@ControllerAdvice实现全局统一的异常处理和响应数据封装。
摘要由CSDN通过智能技术生成


SpringMVC框架围绕 DispatcherServlet(前端控制器)这个核心展开、DispatcherServlet 是SpringMVC框架的总导演、总策划,它负责截获请求并将其分派给相应的处理器处理。传统的基于Spring Framework的web开发需要大量的 xml 配置,在有SpringBoot以后,Web开发的效率得到了很大的提升,几乎大部分配置可以使用默认约定的规则。我们基于SpringBoot的项目来进行SpringMVC的学习。

MVC设计模式

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

  • Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。

  • View(视图)是应用程序中处理数据显示的部分。
    通常视图是依据模型数据创建的。

  • Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。说明:一般现在主流的前后端分离的开发模式,后端人员不再写前端代码,我们也不学习前端,对应MVC中View的部分,我们也就不关心了。

工作流程

在这里插入图片描述

1、用户发送请求至前端控制器DispatcherServlet。

2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。

3、处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

4、 DispatcherServlet调用HandlerAdapter处理器适配器。

5、HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。

6、Controller执行完成返回ModelAndView。

7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。

8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器。

9、ViewReslover解析后返回具体View.

10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

11、DispatcherServlet响应用户。

请求转发与重定向

  • 重定向会改变URL的路径,可以拿到页面外的资源,

  • 请求转发的URL地址不会改变,有可能请求外部资源访问不到,因为是以当前页面路径为根路径开始查找的,而外部资源不在当前路径下,所以找不到。

@Controller
@RequestMapping("/mvc")
@Slf4j
// @Slf4j用来记录日志
public class Mvccontroller {
    @RequestMapping("index")
    public String getIndex(){
        log.error("mvc哈哈");
        return "redirect:/index.html";
        //重定向,redirect实现的是临时重定向
    }

    @RequestMapping("index2")
    public String getIndex2(HttpServletResponse resp){
       resp.setStatus(301);
       resp.setHeader("location","/index.html");
        return null;
        //301永久重定向,redirect实现的是临时重定向
    }

    @RequestMapping("index1")
    public String getIndex1(){
        log.error("mvc哈哈");
        //请求转发
        return "forward:/index.html";
    }
}

Mvc常用的注解

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

  • 2、 @RequestMapping是Spring Web应用程序中最常被用到的注解之一。在对SpringMVC进行配置的时候,需要指定请求与处理方法之间的映射关系,这时候就需要使用@RequestMapping注解。该注解可以在控制器类的级别和其方法级别上使用。默认返回的是一个view试图(或者是页面),如果要返回一个JSON字符串需要用到 @ResponseBody作用在类或者方法上。

@RequestMapping("/3")
@ResponseBody
public String test3(){
    return "好了,已经知道了"; } 12345

@RequestMapping注解能够处理的HTTP请求方法有: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE 。为了能够将一个请求映射到一个特定的HTTP方法,你需要在@RequestMapping中使用method参数声明HTTP请求所使用的方法类型

//指定服务提供的请求方法
@RequestMapping(value = "/2", method = RequestMethod.GET)
public String test2(){
    return "forward:/home.html"; }
  • 3、@ResponseBody
    表示将控制器方法的返回序列化作为响应体内容返回前端。
  1. 返回类型为String,表示响应Content-Type: text/plain,且响应体为控制器方法的字符串返回值
  2. 返回类型为普通Java类型,表示响应Content-Type: application/json,以返回对象序列化为json后作为响应体。
  3. @ResponseBody可以使用在类上,表示该类中所有方法都是默认以返回值作为响应体,也就是所有方法都使用@ResponseBody。如果返回值为null,表示响应体内容为空
@RequestMapping("/5")
@ResponseBody
public String test5(){
    return null; }
@RequestMapping("/6")
@ResponseBody
public Object test6(){
    return null; }
  • 4、组合注解
    @RestController注解使用在类上,和使用两个注解@Controller,@ResponseBody在类上意思一样
    @GetMapping即是:@RequestMapping(method = RequestMethod.GET)
    @PostMapping即是:@RequestMapping(method = RequestMethod.POST)

  • 5、方法的参数注解
    (1)@PathVariable注解。一般的 URI 服务路径都是固定的该注解可以直接从URL中获得参数,URL中名字要与参数名对应相同。url 语义很明确,搜索引擎获取的权限更高。

@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 中的定义名称一致
(2)RequestParam
需要注意@RequestParam注解参数默认为 required=true ,如果不传该参数就会报错,需要指定为: @RequestParam(required = false) 。

//public Object param2(@RequestParam(required = false) Integer count)
//以上代码使用包装类型 Integer ,如果使用 int ,不传入键为 count 的请求数据就会报错,
//因为null 无法转换为 int.建议是:必填的请求数据可以使用基本数据类型,可以不传的参数,
//都要使用包装类型

//public Object param3(@RequestParam MultipartFile file) throws IOException {
//传输文件

@PostMapping("/param1")
public Object param1(@RequestParam String username, @RequestParam String
password){
    Map<String, String> map = new HashMap<>();
    map.put("用户名", username);
    map.put("密码", password);
    return map; }

@RequestParam默认必须传入该请求数据,而 POJO 对象是根据请求数据来填充属性,如果请求数据没有,则属性就是默认值(new对象时每个属性的默认值)。

@Getter
@Setter
@ToString
public class User {
    private String username;
    private String password; 
    } 

@PostMapping("/pojo2")
public Object pojo2(User user){
    Map<String, String> map = new HashMap<>();
    map.put("用户名", user.getUsername());
    map.put("密码", user.getPassword());
    return map;
    } 

(3)@RequestBody
当请求的数据类型Content-Type为 application/json 时,需要显示的使用@RequestBody注解。请求参数需要到body中去拿。
(4)@RequestPart获得二进制文件

@RequestMapping("/reg")
    public String regin(String username, String password,
                        @RequestPart MultipartFile file) throws IOException {

        // 1.动态获取当前项目运行的static路径,将文件保存在static目录下
        String path = ClassUtils.getDefaultClassLoader().
                getResource("static").getPath();
        // 2.文件名(全局唯一id【UUID】)+文件的原始类型
        String fileType = file.getOriginalFilename(); // img.png
        fileType = fileType.substring(fileType.lastIndexOf("."));
        // 文件名
        String fileName = UUID.randomUUID().toString() + fileType;
        // 将文件保存到服务器
        file.transferTo(new File(path + fileName));
       
    }

Servlet API
在控制器方法参数中,可以使用Servlet相关API,SpringMVC会自动将相关Servlet对象装配到方法参数中,如 HttpServletRequest 、 HttpServletResponse 、 HttpSession 等

@GetMapping("/servlet")
public void servlet(HttpServletRequest req, HttpServletResponse resp) throws
IOException {
    req.setCharacterEncoding("UTF-8");
    resp.setCharacterEncoding("UTF-8");
    resp.setContentType("text/html");
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    PrintWriter pw = resp.getWriter();
    pw.println("接收到的请求为:用户名="+username+",密码:"+password);

@RequestHeader 绑定请求头Header信息

@GetMapping("/header")
public String header(@RequestHeader("Accept-Encoding") String encoding,
                     @RequestHeader("User-Agent") String userAgent) {
    return String.format("<p>Accept-Encoding: %s</p><p>User-Agent: %s</p>", 
encoding, userAgent);
}

@CookieValue 绑定请求头Cookie里边的信息

@GetMapping("/cookie")
public String cookie(@CookieValue("SESSIONID") String cookie) {
    return String.format("JSESSIONID: %s", cookie);
}

SpringMVC自定义配置

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

自定义后端路径映射
在配置类中实现 WebMvcConfigurer 接口,重写 configurePathMatch 方法,实现时,可以添加统一的服务路径前缀

@Configuration
public class AppConfig implements WebMvcConfigurer {

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

自定义Controller拦截器
先 实现HandlerInterceptor接口类,即自定义拦截器。 在配置类中重写 addInterceptors 方法,就是将自己的拦截器添加到拦截器链中。实现时,需要指定拦截器,并配置需要拦截、不拦截的路径。在客户端发起请求时,如果路径最终匹配该规则,则执行拦截器中的接口方法。以下示例,实现用户统一的会话管理:
首先定义一个用户登录接口

定义拦截器

public class LoginInterceptor implements HandlerInterceptor {

    // 自定义拦截方法,返回结果是 boolean,当
    // 为 true 表示可以方法后端接口,为 false 表示无权访问后端接口
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, 
                             Object handler) throws Exception {
        // 判断 session 是否有值
        HttpSession session = request.getSession(false);
        if (session != null &&
         session.getAttribute(AppFinal.USERINFO_SESSIONKEY) != null) {
            // 用户已经登录
            return true;
        }
        return false;
    }
}

在配置类中进行配置

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("api", c -> true);
    }
    // 配置拦截规则
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**") // 拦截所有的接口
                .excludePathPatterns("/api/user/login") // 不拦截登录接口
                .excludePathPatterns("/api/user/reg") // 不拦截注册接口
                .excludePathPatterns("/login.html") // 不拦截登录页面
                .excludePathPatterns("/regin.html") // 不拦截注册页面
                .excludePathPatterns("/**/**.html") // 不拦截注册页面               
    }
}

@ControllerAdvice(全局统一)

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

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

@ControllerAdvice
public class ErrorAdvice {
    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public Object err2(Exception e) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("status", "-2");
        map.put("data", "");
        map.put("msg", e.getMessage());
        return map;
    }
    @ExceptionHandler(NullPointerException.class)
    @ResponseBody
    public Object err3(Exception e) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("status", "-3");
        map.put("data", "");
        map.put("msg", "空指针异常");
        return map;
    }
    @ExceptionHandler
    @ResponseBody
    public Object err(Exception e) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("status", "-1");
        map.put("data", "");
        map.put("msg", e.getMessage());
        return map;
    }

}

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

@ControllerAdvice
//实现ResponseBodyAdvice接口,表示可以根据条件对返回的数据重写
public class MyResponseBodyAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper; //注入容器中的ObjectMapper,SpringBoot默
    //认使用jackson框架中的ObjectMapper完成json的序列化

//方法参数可以获取请求调用的控制器类及方法,再决定是否要执行响应内容重写
    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

 //响应的内容在返回客户端之前,会执行本方法,重写之后在返回
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object o,
                                  MethodParameter methodParameter,
                                  MediaType mediaType,
                                  Class aClass,
                                  ServerHttpRequest serverHttpRequest,
                                  ServerHttpResponse serverHttpResponse) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("status", 0);
        map.put("data", o);
        map.put("msg", "");
        if (o instanceof String) {
            // 当前方法要给前端返回一个 json 类型的字符串
            serverHttpResponse.getHeaders().setContentType(
                    MediaType.APPLICATION_JSON);
            // 返回一个 json 字符串
            return objectMapper.writeValueAsString(map);
        }
        return map;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值