我写了一篇SpringMVC笔记

23 阅读10分钟

1. 学习目标

1.掌握基于SpringMVC获取请求参数与响应json数据操作

2.熟练应用基于REST风格的请求路径设置与参数传递

3.能够根据实际业务建立前后端开发通信协议并进行实现

4.基于SSM整合技术开发任意业务模块功能

5.能够理解并实现统一结果封装与统一异常处理

6.能够完成前后台功能整合开发

7.掌握拦截器的编写

2. SpringMVC简介

(1) 简介

Web工作流程:通过浏览器访问页面,通过异步提交的方式访问后端,后端通过三层架构的形式进行开发,最后返回json格式给前端,前端解析数据并交给浏览器

SpringMVC是一种基于Java实现MVC模型的轻量级Web框架,用于表现层功能开发

优点:灵活性强、较Servlet来说使用简单、开发便捷

(2) 配置类使用

原始版

public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    protected WebApplicationContext createRootApplicationContext() {
      AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }
}

优化版

public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    //加载Spring配置类
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }
    //加载SpringMVC配置类
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }
    //设置SpringMVC请求地址拦截规则
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    //设置post请求中文乱码过滤器
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        filter.setForceEncoding(true);
        return new Filter[]{filter};
    }
}

(3) 工作流程

在这里插入图片描述

  1. 服务器启动时,初始化web容器
  2. 加载SpringMVC配置类SpringMvcConfig来初始化SpringMVC的容器(此时创建了WebApplicationContext对象)
  3. 加载了SpringMvcConfig配置类
  4. 执行@ComponentScan对应的bean,扫描了指定包及以下类中的注解
  5. 加载了控制层,以及@RequestMapping的对应方法
  6. 执行了getServletMappings方法,设定了拦截请求路径规则

(4) 单次请求过程

  1. 发送请求,示例:http://localhost/save
  2. web容器发现请求满足SpringMVC拦截规则,将请求交给SpringMVC处理
  3. 解析请求路径/save,匹配执行对应方法save()
  4. 执行save()
  5. 检测到有@ResponseBody,将save()方法返回值作为响应体返回给请求方

(5) bean加载控制

由于SpringMVC负责加载Controller层,为了防止Spring扫描控制层,我们可以有以下方法解决

1.设置扫描范围

将扫描范围定位精准范围即可

@Configuration
@ComponentScan({"com.Coolipa.service","com.Coolipa.dao"})
public class SpringConfig {
}

2.排除bean

通过excludeFilters进行过滤

@Configuration
@ComponentScan(value="com.itheima",
    excludeFilters=@ComponentScan.Filter(
    	type = FilterType.ANNOTATION,
        classes = Controller.class
    )
)
public class SpringConfig {
}
  • excludeFilters属性:设置扫描加载bean时,排除的过滤规则

  • type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除

    • ANNOTATION:按照注解排除
    • ASSIGNABLE_TYPE:按照指定的类型过滤
    • ASPECTJ:按照AspectJ表达式排除,基本上不会用
    • REGEX:按照正则表达式排除
    • CUSTOM:按照自定义规则排除

    只需要知道第一种ANNOTATION即可,其他作为了解

  • classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean

3. 请求与响应

这里着重强调REST风格的请求与响应,其他作为了解

(1) 设置请求映射路径

通过规范开发,避免每人设置不同的请求路径从而冲突

通过设置模块名作为请求路径前缀,从而避免冲突。

示例

@Controller
public class UserController {
    //设置访问路径
    @RequestMapping("/user/save")
    //设置返回值类型
    @ResponseBody
    public String save(){
        System.out.println("user save");
        return "{'module':'springmvc'}";
    }
    //设置访问路径
    @RequestMapping("/user/insert")
    //设置返回值类型
    @ResponseBody
    public String insert(){
        System.out.println("user insert");
        return "{'module':'springmvc'}";
    }
}

注意:

  • 当类上和方法上都添加了@RequestMapping注解,前端发送请求的时候,要和两个注解的value值相加匹配才能访问到。
  • @RequestMapping注解value属性前面加不加/都可以

(2)不同类型参数传递

<1> Get请求和Post请求参数传递

Get请求传参

普通参数:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数(使用Params)

在这里插入图片描述

Post请求参数

普通参数:form表单post请求传参,表单参数名与形参变量名相同,定义传参即可接收参数(使用Body的x-www-form-urlencoded)

在这里插入图片描述

<2>四种类型参数传递

这里分为普通参数POJO参数数组类型参数集合类型参数四种。

普通参数

若请求参数名与形参变量名不同,使用@RequestParam绑定参数关系

@RequestMapping("/commonParam")
    @ResponseBody
    public String commonParamDifferentName(@RequestParam("name") String userName , int age){
      // ......
        return .....;
    }

在这里插入图片描述

POJO参数

请求参数名与形参对象属性名相同,定义POJO类型形参就能接收参数,若为嵌套类型(POJO嵌入其他POJO),使用.即可

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

    private Address address;
    
    //get和set方法...
}
//POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
    //......
    return .....;
}

在这里插入图片描述

数组类型参数

请求参数名与形参对象属性名相同且为多个,定义数组类型形参即可接收参数

  //数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
    @RequestMapping("/arrayParam")
    @ResponseBody
    public String arrayParam(String[] likes){
        //.....
        return ...;
    }

在这里插入图片描述

集合类型参数

由于SpringMVC会默认把List看错POJO对象,从而导致把前端数据封装到对象中,因此会导致报错,所以使用@RequestParam注解

请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系

//集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
    //...
    return ...;
}

<3> json数据参数传递

使用条件:引入 Jackson(例如 jackson-databind),并确保注册了 MappingJackson2HttpMessageConverter。非 Spring Boot 环境可通过 @EnableWebMvc 或 XML 启用 MVC 以激活消息转换;在 Spring Boot 中无需 @EnableWebMvc

对于JSON数据类型,我们常见的有三种:

  • json普通数组(["value1","value2","value3",...])
  • json对象({key1:value1,key2:value2,...})
  • json对象数组([{key1:value1,...},{key2:value2,...}])

JSON普通数组

使用@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'}";
    }

在这里插入图片描述

JSON对象数据

json数据与形参对象数据名相同,定义POJO类型形参即可接收参数,若是由嵌套POJO类型,在JSON也嵌套即可

@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){
    System.out.println("pojo(json)参数传递 user ==> "+user);
    return "{'module':'pojo for json param'}";
}

在这里插入图片描述

JSON对象数组

json数组数据与集合泛型属性名相同,定义List类型形参即可接收参数

@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>日期型参数传递

在SpringMVC默认支持的字符串转日期的格式为yyyy/MM/dd,如果格式不同,需要使用@DateTimeFormat

@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'}";
}

拓展:内部实现原理

SpringMVC提供了许多类型转换接口和实现类,例如Converter

public interface Converter<S, T> {
    @Nullable
    //该方法就是将从页面上接收的数据(S)转换成我们想要的数据类型(T)返回
    T convert(S source);
}

在这里插入图片描述

4. REST风格写法

<1> 简介

REST(Representational State Transfer) 表现形式状态转换,是一种软件架构风格

传统风格与REST风格对比

  • 传统风格资源描述形式
    • http://localhost/user/getById?id=1 查询id为1的用户信息
    • http://localhost/user/saveUser 保存用户信息
  • REST风格描述形式
    • http://localhost/user/1
    • http://localhost/user

优点:隐藏资源访问行为,书写简化

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

  • 发送GET请求是用来做查询
  • 发送POST请求是用来做新增
  • 发送PUT请求是用来做修改
  • 发送DELETE请求是用来做删除

具体实例:

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

<2> 使用写法

原始使用

http://localhost/users/1为例

@Controller
@RequestMapping("/users")
public class UserController {
    //设置当前请求方法为DELETE,表示REST风格中的删除操作
	@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id) {
        System.out.println("user delete..." + id);
        return "{'module':'user delete'}";
    }
}

简便使用

@RestController
@RequestMapping("/books")
public class BookController{  
    @Autowired
    private BookService bookService;
    
    @GetMapping("/{id}")
	public String getById(@PathVariable Integer id){
    	System.out.println("book getById"+id);
    	return "{'module':'book getById'}";
}
}

5. 拦截器

拦截器( Interceptor ) 是一种控制方法前后执行代码的工作机制,底层是AOP。

作用:

  • 在指定方法调用前后执行预先设定的代码
  • 阻止原始方法的执行

(1) 拦截器与过滤器区别

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

(2) 使用拦截器

<1> 直接配置拦截器类

需要写拦截器接口

@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {
    @Override
    //原始方法调用前执行的内容
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...");
        return true;
    }

    @Override
    //原始方法调用后执行的内容
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    @Override
    //原始方法调用完成后执行的内容
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
    }
}

此处需要被SpringMVC扫到,且需要在单独写SpringMvcSupport类。注意:继承 WebMvcConfigurationSupport 会禁用默认MVC配置,通常更推荐仅实现 WebMvcConfigurer。

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //配置拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**");
    }
}

<2> 直接写在SpringMvcConfig内

@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //配置多拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**");
    }
}

<3> 拦截器参数

前置处理

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

参数:

  • request:请求对象
  • response:响应对象
  • handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再次包装

返回值:

  • 返回值为false,被拦截的处理器将不执行

后置处理

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
	System.out.println("postHandle...")
    }

参数:

  • modelAndView:如果处理器执行完成具有返回效果,可以读取到对应数据与页面信息,并进行调整

不需要使用,了解即可

完成后处理

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
       System.out.println("afterCompletion")
    }

可以通过SpringMVC的异常处理机制替换

(3) 拦截器链

<1> 配置多个拦截器

步骤1:创建拦截器类

实现接口,并重写接口中的方法

@Component
public class ProjectInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...222");
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...222");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...222");
    }
}
步骤2:配置拦截器类
@Configuration
@ComponentScan({"com.itheima.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Autowired
    private ProjectInterceptor2 projectInterceptor2;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //配置多拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books/**");
        registry.addInterceptor(projectInterceptor2).addPathPatterns("/books/**");
    }
}

<2> 多拦截器执行顺序

与栈相同,先进后出,后进先出

  • 当配置多个拦截器时,形成拦截器链
  • 拦截器链的运行顺序参照拦截器添加顺序为准
  • 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
  • 当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作

6. 结语

如果这篇文章对你有帮助,希望可以给一个推荐,收藏和评论