【SpringMVC】SSM框架【二】——SpringMVC超详细

SpringMVC

学习目标:

1.SpringMVC简介

1)web访问流程

  • 1.web服务器通过浏览器访问页面
  • 2.前端页面使用异步提交的方式发送请求到后端服务器
  • 3.后端服务器采用:表现层---业务层---数据层的架构进行开发
  • 4.页面请求由表现层进行接收,获取用户的请求后将参数传递到业务层,再由业务层访问数据层获取数据库中的数据,返回给表现层
  • 5.得到用户需要访问的数据之后,表现层通过JSON的格式发送数据到前端页面
  • 6.前端页面得到数据之后,解析数据并组织对应的页面渲染到浏览器展示给用户

2)SpringMVC概述

  • 是一种基于Java实现的MVC模型的轻量级web框架(表现层框架技术,主要用于表现层开发)
  • 优点:
    • 使用简单,开发便捷
    • 灵活性强

2.SpringMVC入门案例

1)案例实施

  • 1.导入SpringMVCServlet的坐标
xml 复制代码
    <dependencies>
        <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>
    </dependencies>

配置tomcat插件:

xml 复制代码
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <port>80</port>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>
  • 2.创建SpringMVC控制器类:
java 复制代码
@Controller
public class controller {

    @RequestMapping("/print")
    @ResponseBody
    public String print() {
        System.out.println("user print ...");
        return "{user: name}";
    }
}
  • 3.初始化SpringMVC环境,设定SpringMVC加载对应的bean:
    SpringMVCConfig:
java 复制代码
@Configuration
@ComponentScan("com.springmvclearn.controller")
public class SpringMvcConfig {
}
  • 4.初始化Servelet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求:
    SpringMvcContainersInitConfig:
java 复制代码
public class ServeletContainersInitConfig extends AbstractDispatcherServletInitializer {


    //加载SpringMVC容器配置
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(SpringMvcConfig.class);
        return context;
    }

    //设置哪些请求归属springMVC处理
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}
涉及的注解介绍



2)入门案例工作流程

3)不同层次结构Bean的加载控制

  • 在开发中,Spring一般加载业务层和数据层的bean,SpringMVC一般加载表现层的bean,而入门案例中配置时,Spring加载时会直接扫描整个包下的bean,包括表现层的bean
  • 为了避免Spring错误的加载到SpringMVC的bean:我们需要在Spring加载bean时,排除掉SpringMVC的bean
  • 1.方式一:Spring加载的bean设定扫描范围为com.itheima,排除掉controller包内的bean(SpringBoot中会使用这种方式进行bean的细粒度控制
java 复制代码
@Configuration
@ComponentScan(value = "com.springmvclearn", 
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                classes = Controller.class
        )
)
public class SpringConfig {
}
  • 2.方式二:Spring加载的bean设定扫描范围为精确的包:比如直接对应service包,dao包等。
java 复制代码
@Configuration
@ComponentScan({"com.springmvclearn.service", "com.springmvclearn.dao"})
public class SpringConfig {
}

4)简化开发的书写格式

5)PostMan

  • 网页调试与发送网页http请求的Chrome插件

  • 常用于进行接口测试

  • 在workspaces中可以云备份历史请求操作

  • 创建请求:

  • 保持请求,并放入到一个集合中,方便分类复用:

3.请求与响应

1.请求映射路径

  • 当相同层级下的controller有相同的路径时(/save),在访问时就会有路径冲突问题

  • 一般解决就是在不同controller的路径前加入不同模块的请求路径前缀: /user/save/book/save

  • 通过RequestMapping在控制类上统一设置当前控制类下的方法请求路径前缀,然后在每个控制器方法上再加每个控制方法对应的请求访问路径

2.请求参数

1)普通参数请求
java 复制代码
//请求参数
@Controller
public class UserController {

    //普通参数:请求参数与形参名称对应即可完成参数传递
    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(String name ,int age){
        System.out.println("普通参数传递 name ==> "+name);
        System.out.println("普通参数传递 age ==> "+age);
        return "{'module':'common param'}";
    }

    //普通参数:请求参数名与形参名不同时,使用@RequestParam注解关联请求参数名称与形参名称之间的关系
    @RequestMapping("/commonParamDifferentName")
    @ResponseBody
    public String commonParamDifferentName(@RequestParam("name") String userName , int age){
        System.out.println("普通参数传递 userName ==> "+userName);
        System.out.println("普通参数传递 age ==> "+age);
        return "{'module':'common param different name'}";
    }
}
  • 1.GET 请求:
  • 2.POST请求:
  • 3.POST中文参数乱码问题
java 复制代码
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    //乱码处理
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
    }
}
2)POJO参数传递
  • 当大量参数需要传递时,一般将参数封装成一个实体类,在参数传递时,直接将参数赋值给实体类对象,然后从实体类对象中拿取对应参数值

User:

java 复制代码
package com.itheima.domain;

public class User {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

在controller中:

java 复制代码
//请求参数
@Controller
public class UserController {

    //POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
    @RequestMapping("/pojoParam")
    @ResponseBody
    public String pojoParam(User user){
        System.out.println("pojo参数传递 user ==> "+user);
        System.out.println("userName ==> "+user.getName());
        System.out.println("userAge ==> "+user.getAge());
        return "{'module':'pojo param'}";
    }
}


3)嵌套POJO参数传递
  • 当POJO中还有引用的POJO时,如何传递参数
  • 也就是在POJO中将该POJO注入,并给出setter和getter方法:

user:

java 复制代码
package com.itheima.domain;

public class User {
    private String name;
    private int age;

    private Address address;
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address=" + address +
                '}';
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

然后在controller中同样接收POJO对象即可:

java 复制代码
//请求参数
@Controller
public class UserController {
    //嵌套POJO参数:嵌套属性按照层次结构设定名称即可完成参数传递
    @RequestMapping("/pojoContainPojoParam")
    @ResponseBody
    public String pojoContainPojoParam(User user){
        System.out.println("pojo嵌套pojo参数传递 user ==> "+user);
        System.out.println("userName ==> "+user.getName());
        System.out.println("userAge ==> "+user.getAge());
        System.out.println("userAddress ==> "+user.getAddress());
        return "{'module':'pojo contain pojo param'}";
    }
}
4)数组参数传递
  • 需要传入一个数组类型的参数时:
java 复制代码
//请求参数
@Controller
public class UserController {
    //数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
    @RequestMapping("/arrayParam")
    @ResponseBody
    public String arrayParam(String[] hobby){
        System.out.println("数组参数传递 hobby ==> "+ Arrays.toString(hobby));
        return "{'module':'array param'}";
    }
}
5)集合类型参数
  • 需要集合类型参数时:我们需要设置该集合参数为RequestParam,否则无法识别需要传递的是集合
java 复制代码
//请求参数
@Controller
public class UserController {
    //集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
    @RequestMapping("/listParam")
    @ResponseBody
    public String listParam(@RequestParam List<String> likes){
        System.out.println("集合参数传递 likes ==> "+ likes);
        return "{'module':'list param'}";
    }
}

请求则同上,结果也是一样的。

3.JSON格式参数传递

  • 首先要加入解析JSON的坐标:
xml 复制代码
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version>
    </dependency>
1)JSON格式的集合参数
  • 1.开启支持集合JSON格式的数据类型自动转换:
java 复制代码
@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
  • 2.加上在参数前面加入@RequestBody,表示传入的参数是在JSON的body中
java 复制代码
//请求参数
@Controller
public class UserController {
    //集合参数:json格式
    //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
    @RequestMapping("/listParamForJson")
    @ResponseBody
    public String listParamForJson(@RequestBody List<String> likes){
        System.out.println("list common(json)参数传递 list ==> "+likes);
        return "{'module':'list common for json param'}";
    }
}


2)JSON格式的POJO参数传递(以及嵌套POJO)
  • 接收参数格式:
java 复制代码
//请求参数
@Controller
public class UserController {
    //POJO参数:json格式
    //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数据映射到形参的实体类对象中,要求属性名称一一对应
    @RequestMapping("/pojoParamForJson")
    @ResponseBody
    public String pojoParamForJson(@RequestBody User user){
        System.out.println("pojo(json)参数传递 user ==> "+user);
        return "{'module':'pojo for json param'}";
    }
}
  • 参数请求格式:

  • 结果:

3)JSON格式对象集合参数传递
  • 接收参数格式:
java 复制代码
//请求参数
@Controller
public class UserController {
    //集合参数:json格式
    //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数组数据映射到形参的保存实体类对象的集合对象中,要求属性名称一一对应
    @RequestMapping("/listPojoParamForJson")
    @ResponseBody
    public String listPojoParamForJson(@RequestBody List<User> list){
        System.out.println("list pojo(json)参数传递 list ==> "+list);
        return "{'module':'list pojo for json param'}";
    }
}
  • 参数请求格式:
  • 结果:

4.对比

5.日期型参数传递


  • 接收参数格式:
java 复制代码
//请求参数
@Controller
public class UserController {
    //日期参数
    //使用@DateTimeFormat注解设置日期类型数据格式,默认格式yyyy/MM/dd
    @RequestMapping("/dataParam")
    @ResponseBody
    public String dataParam(Date date,
                            @DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
                            @DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2){
        System.out.println("参数传递 date ==> "+date);
        System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
        System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
        return "{'module':'data param'}";
    }
}
  • 参数请求格式:
  • 结果:

6.响应

  • 1.响应页面
  • 2.响应文本
  • 3.响应POJO
  • 4.响应POJO集合

使用@ResponseBody

  • 将其注解在SpringMVC控制器方法定义的上方
  • 作用:设置当前控制器返回值为响应体(POJO转JSON,POJO集合转JSON)
java 复制代码
//请求参数
@Controller
public class UserController {
	//返回页面
    @RequestMapping("/pageParam")
    public String pageParam() {
        System.out.println("返回页面");
        return "index.jsp";
    }
	//返回文本数据
    @RequestMapping("/textParam")
    @ResponseBody
    public String totext() {
        System.out.println("返回文本数据");
        return "context text";
    }
	//返回POJO
    @RequestMapping("/userParam")
    @ResponseBody
    public User userParam(User user) {
        System.out.println("返回POJO");
        User new_user = new User();
        new_user.setName(user.getName());
        new_user.setAge(user.getAge());
        new_user.setAddress(user.getAddress());
        return new_user;
    }
	//返回POJO集合
    @RequestMapping("/usersParam")
    @ResponseBody
    public List<User> usersParam(List<User> user) {
        System.out.println("返回POJO集合");
        List<User> new_users = new ArrayList<>();
        new_users.addAll(user);
        return new_users;
    }
}

4.REST风格

  • 是一种定义资源描述的形式

  • REST风格访问资源时,使用行为动作区分对资源进行了何种操作:路径+请求方式

1)RESTful
  • 在请求时通过不同的请求方式

  • @PathVariable接收路径参数或者路径变量(路径中的参数获取:http://localhost/users/3 -> 3的获取)

java 复制代码
package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class UserController {

    //设置当前请求方法为POST,表示REST风格中的添加操作
    @RequestMapping(value = "/users",method = RequestMethod.POST)
    @ResponseBody
    public String save(){
        System.out.println("user save...");
        return "{'module':'user save'}";
    }

    //设置当前请求方法为DELETE,表示REST风格中的删除操作
    //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
    @RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id){
        System.out.println("user delete..." + id);
        return "{'module':'user delete'}";
    }

    //设置当前请求方法为PUT,表示REST风格中的修改操作
    @RequestMapping(value = "/users",method = RequestMethod.PUT)
    @ResponseBody
    public String update(@RequestBody User user){
        System.out.println("user update..."+user);
        return "{'module':'user update'}";
    }

    //设置当前请求方法为GET,表示REST风格中的查询操作
    //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
    @RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
    @ResponseBody
    public String getById(@PathVariable Integer id){
        System.out.println("user getById..."+id);
        return "{'module':'user getById'}";
    }

    //设置当前请求方法为GET,表示REST风格中的查询操作
    @RequestMapping(value = "/users",method = RequestMethod.GET)
    @ResponseBody
    public String getAll(){
        System.out.println("user getAll...");
        return "{'module':'user getAll'}";
    }
}
2)@RequestBody@RequestParam@PathVariable
3)RESTful快速开发
  • 在1)RESTful的编写中,同一个路径前缀等在每个控制器方法上都有,为了简化书写,以下注解和简化写法是标准的RESTful开发:

  • 标准RESTful开发:

java 复制代码
package com.itheima.controller;

import com.itheima.domain.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

//@Controller
//@ResponseBody配置在类上可以简化配置,表示设置当前每个方法的返回值都作为响应体
//@ResponseBody
@RestController     //使用@RestController注解替换@Controller与@ResponseBody注解,简化书写
@RequestMapping("/books")
public class BookController {

//    @RequestMapping( method = RequestMethod.POST)
    @PostMapping        //使用@PostMapping简化Post请求方法对应的映射配置
    public String save(@RequestBody Book book){
        System.out.println("book save..." + book);
        return "{'module':'book save'}";
    }

//    @RequestMapping(value = "/{id}" ,method = RequestMethod.DELETE)
    @DeleteMapping("/{id}")     //使用@DeleteMapping简化DELETE请求方法对应的映射配置
    public String delete(@PathVariable Integer id){
        System.out.println("book delete..." + id);
        return "{'module':'book delete'}";
    }

//    @RequestMapping(method = RequestMethod.PUT)
    @PutMapping         //使用@PutMapping简化Put请求方法对应的映射配置
    public String update(@RequestBody Book book){
        System.out.println("book update..."+book);
        return "{'module':'book update'}";
    }

//    @RequestMapping(value = "/{id}" ,method = RequestMethod.GET)
    @GetMapping("/{id}")    //使用@GetMapping简化GET请求方法对应的映射配置
    public String getById(@PathVariable Integer id){
        System.out.println("book getById..."+id);
        return "{'module':'book getById'}";
    }

//    @RequestMapping(method = RequestMethod.GET)
    @GetMapping             //使用@GetMapping简化GET请求方法对应的映射配置
    public String getAll(){
        System.out.println("book getAll...");
        return "{'module':'book getAll'}";
    }
}
4)案例:基于RESRful页面数据交互
  • 1.制作控制器,通过PostMan测试接口:

  • 2.放行静态资源访问:

    由于SpringMVC设置了访问路径拦截,当访问前端静态页面时,会被拦截,导致无法访问到,于是我们需要制作放行器,将对应的静态数据进行放行:

SpringMVCSupport:

java 复制代码
package com.itheima.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class SpringSupport extends WebMvcConfigurationSupport {

    //当访问/pages/????的时候,走/pages目录下的内容
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
}
  • 3.异步提交访问后端控制器获取数据:

5.拦截器

  • 动态拦截方法调用的机制,在SpringMVC中动态拦截控制器的执行
  • 作用:
    • 在指定的方法调用前后执行预先设定的代码
    • 阻止原始方法的执行(权限控制)

1)拦截器与过滤器区别

  • 归属不同:Filter属于Serverlet技术,Interceptor属于SpringMVC技术
  • 拦截内容不同:Filter针对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强

2)拦截器入门案例

  • 1.声明拦截器的bean,并实现HandlerInterceptor接口:
java 复制代码
@Component
public class MyProjectInterceptor implements HandlerInterceptor {
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyProjectInterceptor posHandle");
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyProjectInterceptor preHandle");
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyProjectInterceptor afterCompletion");
    }
}
  • 2.定义配置类,继承WebMvcConfigurationSupport,实现addInterceptor方法:
java 复制代码
@Configuration
public class MySpringMvcSupport extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    	
    }
}
  • 3.添加拦截器并设定拦截的访问路径,路径可以通过可变参数设置多个:
java 复制代码
@Configuration
public class MySpringMvcSupport extends WebMvcConfigurerAdapter {

    @Autowired
    private MyProjectInterceptor projectInterceptor;
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/page/**").addResourceLocations("classpath:/pages/");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    	//拦截器配置,当遇到哪些路径时,会将其拦截下来执行对应的postHandle和preHandle方法
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*");
    }
}
  • 4.发送请求:
  • 5.拦截器拦截对应的路径下方法的调用,并在方法调用前后执行预先定义的方法:
拦截器执行流程:

3)拦截器参数(了解即可,主要用到的可能是前置处理)

  • 前置处理,拿到handler(原始方法对象的封装)可以对原始方法进行一些处理
  • 主要用处为在方法执行前作校验


4)拦截器链(了解即可)

  • 当配置多个拦截器是,形参拦截器链
  • 拦截器链的运行顺序参照拦截器添加的顺序为准

    *注:上述内容均来自黑马程序员SSM框架的课程学习,仅用作学习交流,不作为商业用途,如有侵权,联系删除。
相关推荐
the beard35 分钟前
Feign整合Sentinel实现服务降级与Feign拦截器实战指南
java·spring·sentinel
练习时长一年3 小时前
Spring代理的特点
java·前端·spring
毅航7 小时前
Spring WebFlux 原理与实践全解析
后端·spring·trae
杨杨杨大侠12 小时前
第10篇:测试与实战 - 构建完整的示例应用
java·spring·apache log4j
杨杨杨大侠12 小时前
第9篇:监控与运维 - 集成Actuator健康检查
java·spring·apache log4j
阿龟在奔跑1 天前
Spring Security 传统 web 开发场景下开启 CSRF 防御原理与源码解析
java·spring·web安全·java-ee·csrf
布朗克1681 天前
OpenTelemetry 通过自动埋点(Java Agent) 应用于springboot项目
java·spring boot·spring·opentelemetry
3Cloudream1 天前
互联网大厂Java面试:从基础到微服务云原生的深度解析
java·spring·微服务·电商·技术架构·面试解析
孤狼程序员1 天前
【Spring Cloud微服务】6.通信的利刃:深入浅出 Spring Cloud Feign 实战与原理
spring·spring cloud·微服务