SpringMVC的使用

198 阅读13分钟

SpringMVC是Spring家族中的重要一员,基于MVC的开发模式用于优化控制器

  • M(Model):模型层, 包含了实体类(Entity),业务逻辑层(Service),数据访问层(DAO)
  • V(View):视图层,用户能看到的所有均为视图层
  • C(Controller):控制器,用来接收客户端的请求,并返回响应到客户端(Servlet)

SpringMVC具有轻量级,易上手,功能强的优势,是现代JavaWeb应用不可缺少的一部分,看这篇文章前需要掌握ServletSpring

使用IDEA基于Maven搭建一个SpringWeb项目

选择maven-archetype-webapp模板

在对应的pom.xml的依赖添加spring-mvc的支持

<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.20</version>
  </dependency>
</dependencies>

接着配置Tomcat服务器支持

编写SpringMvc的配置

有两种方式可以使用,一种是基于xml文件的,用的相对少了

一种是基于注解式开发,目前的是这种,xml的方法简单了解下基本用法即可,在学习Springboot后基本上都是注解式开发

基于XMl的配置

修改WEB-INF中web.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--        初始化时,加载配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <!--        Servlet默认在容器启动时是不会加载的,所以需要提高DispatcherServlet加载优先级,在容器启动时就去加载-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

resource资源目录下新建一个spring-mvc.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--控制类扫包
    这个文件相当于SpringmvcConfig 加了@Configuration注解
    并且加了@ComponentScan("cn.mgl.controller")
    -->
    <context:component-scan base-package="cn.mgl.controller"/>
    <!--    开启springmvc的注解支持-->
    <mvc:annotation-driven/>
</beans>

SpringMvc的核心在于DispatcherServlet,而DispatcherServlet则是封装了最底层的Servlet,所有请求访问之前都会经过Dispatch处理一遍,再访问对应的方法

编写一个控制器,实际上就是Servlet

@RestController
public class MainController {
    @GetMapping("/halo")
    public String fuck() {
        return "halo springmvc";
    }
}

接着访问对应的地址,Tomcat容器访问这里设置的/,具体路径看容器配置(因人而异),访问后成功输出返回的字符串

基于Java代码和注解式开发

有两点注意事项

  1. 使用注解式开发需要单独引入对应的servlet类,注意是javax的包
  2. 把原本web.xml中添加的servlet相关配置都去掉

添加一个继承AbstractDispatcherServletInitializer的类,会在容器启动时执行

/**
 * 当项目启动时,实现类会被执行,并调用相关方法
 *
 */
public class ServletInitial extends AbstractDispatcherServletInitializer {
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        // <context:component-scan base-package="cn.mgl.controller"/>
        // 相当于通过Spring容器去加载配置类,而类上又有包扫描的注解,等价于上面这段xml
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(SpringMvcConfig.class);
        return context;
    }

    @Override
    protected String[] getServletMappings() {
        // 等价于配置DispatcherServlet对应的ServletMapping
        // 所有资源都会经过分发器
        return new String[]{"/"};
    }

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}

对应的配置类,添加注解交给Spring容器管理

@Configuration
@ComponentScan("cn.mgl.controller")
public class SpringMvcConfig {
}

接着启动服务器访问/halo,就能看到效果与xml配置是一模一样的

SpringMvc的数据传输

需要先了解几个相关注解

  • @Controller 实际上也是@Componnet,只不过语法语义上更清晰,使用这个注解的类会被Spring容器管理,并且会将类中相关方法的映射参数进行映射
  • @RequestMapping地址映射,相当于在Servletmapping映射添加地址,既可以添加到类,也可以添加到方法
    • 添加到类相当于添加访问前缀
    • 添加到方法,可以直接写全路径,若Controller类已经配置了前缀,则可以直接写后缀,框架会自己进行路径拼接
    • 默认情况下,返回结果是个String的话,会被认为是一个跳转路径,会经过视图处理器去查找对应的视图,在如今前后端分离的时代,完全没有必要去学习,知道有这么个逻辑即可
    • 除了value的路径可以传入,还可以指定请求的方法,当不指定时,不管是任何请求方法都可以匹配到这个方法中,如果指定方法则使用@RequestMapping(value = "/user", method = RequestMethod.GET)
      • 根据请求方法不同则衍生出了GetMappingPostMappingDeleteMappingPutMappingPatchMapping之类的注解,其实就是简写方便了开发
  • @ResponseBody使用该注解,相当于告诉SpringMvc这个方法要返回的内容以字节流的形式写入到响应报文主体当中,常用的是写入JSON数据格式的数据,不会再走视图处理器,基本都会使用这个注解
    • 添加到类上,表明这个类下的所有方法都会返回JSON数据(一般情况下,返回非JSON也可以)
    • 添加到方法上,表明方法的返回值会返回 JSON数据
  • @RestController相当于在类上同时使用@Controller@ResponseBody,源码如下

假设请求路径带有查询参数,http://localhost:8080/getSome?id=1&username=jackma可以通过以下方式进行数据获取

第一种,controller方法的参数名字与查询参数的key一致

会自动参数注入和类型转换

@Controller
public class MainController {
    @RequestMapping("/getSome")
    public String getSome(Integer id, String username) {
        System.out.println(id); // 这里会获取到1
        System.out.println(username); // 这里会获取到jackma
        return "";
    }
}
第二种,当查询参数的key与方法定义的参数名不一致

当不一致的时候,方法是无法获得传递进来的查询参数的,此时可以使用@RequestParam注解进行参数的绑定,此注解有一个required可以传入,默认是true,意思是只要用了这个注解的参数,请求地址就必须带过来,不传则会抛出异常,可以传入false使参数可传可不传

@RestController
public class MainController {
    // 效果与第一种方式等价
    // id是可以不传的,但是username必须传
    @RequestMapping("/getSome")
    public String getSome(@RequestParam(value = "id",required = false) Integer abcd, @RequestParam("username") String efg) {
        System.out.println(abcd);
        System.out.println(efg);
        return "123";
    }
}
第三种,方法参数想用一个实体类接收

参数名需要与参数类型的对应类成员属性名字相同,会自动注入,先新建一个类,需要有对应的setter方法才能使用

public class User {
    private Long id;
    private String username;

    public Long getId() {
        return id;
    }

    public String getUsername() {
        return username;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

映射方法形式参数改为对象类型

    @RequestMapping("/getSome")
    public String getSome(User user) {
        // id会username就会自动注入到对象实例中
        System.out.println(user.getId());
        System.out.println(user.getUsername());
        return "123";
    }

当对象中嵌套着另一个对象,只需要按照对象层级关系传递即可注入,如User里又有个Car对象,Car对象也有个id,则地址栏只需要传入car.id=xxx,Car对象中的id就会被赋值

第四种,方法参数想用一个数组或List接收

有一个前提条件是,请求参数名与数组形参相同,并且有个同名的查询参数时则会自己收集,如?id=1&id=2&username=q,则获取到的就是一个["1","2"]和["q"]

    @RequestMapping("/getSome")
    public String getSome(String[] id,String[] username) {
        System.out.println(Arrays.toString(id));
        System.out.println(Arrays.toString(username));
        return "123";
    }

如果想用List接收,则需要在形参加上@RequestParam注解,否则会抛出List的异常

    @RequestMapping("/getSome")
    public String getSome(@RequestParam List<String> id, String[] username) {
        System.out.println(Arrays.toString(id.toArray()));
        System.out.println(Arrays.toString(username));
        return "123";
    }

获取提交的JSON数据

一般情况下,JSON数据都会放到请求报文主体当中,此时需要@RequestBody注解来进行匹配转换对象,根据JSON信息反序列化成对象实例

想要不报错,需要先做两个操作

第一步,再SpringMvcConfig的配置类,添加一个@EnableWebMvc的注解,这个注解的作用可以列结尾开启了SpringMvc的核心功能,并且支持了自定义配置,当前也会有一些默认的配置

@Configuration
@ComponentScan("cn.mgl.controller")
@EnableWebMvc
public class SpringMvcConfig {
}

第二步则是添加JSON操作的相关依赖,底层需要进行对象序列化和JSON数据反序列化的操作用到

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.13.3</version>
</dependency>
第一种,使用对象实体接收JSON,修改接收类型,使用@RequestBody,若有对象嵌套则使用对应的JSON嵌套语法即可
    @PostMapping("/getSome")
    public String getSome(@RequestBody User user) {
        System.out.println(user.getId());
        System.out.println(user.getUsername());
        System.out.println(user.getCar().getId());
        return "123";
    }

接着发送Post请求,注意Content-Typeapplication/json

发送请求后,成功获取了对象的值以及嵌套对象的值

第二种,以Map的类型去接受JSON数据

类型更改为Map<String,Object>,并且嵌套对象也是以Map类型存在

    @PostMapping("/getSome")
    public String getSome(@RequestBody Map<String, Object> user) {
        System.out.println(user.get("id"));
        System.out.println(user.get("username"));
        Map car = (Map) user.get("car");
        System.out.println(car.get("id"));
        return "123";
    }
第三种,以List的类型接收JSON格式

只要JSON是数组类型,使用方式参考第二种即可,List的泛型是什么,对应的JSON数据就要是什么

使用原生Servlet写法获取请求数据

当方法有一个HttpServletRequest类型的参数时,就相当于会把Servlet里获取的对象注入,因为底层就是封装了Servlet而已,参数不需要考虑位置,底层会检测类型注入

请求参数:?id=1&username=2,那么通过下面代码就能获取对应的值

    @PostMapping("/getSome")
    public String getSome(HttpServletRequest request) {
        System.out.println(request.getParameter("id"));
        System.out.println(request.getParameter("username"));
        return "123";
    }

还可以这样获取

    @PostMapping("/getSome")
    public String getSome(HttpServletRequest request) {
        System.out.println(request.getParameter("id"));
        System.out.println(request.getParameter("username"));
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    	// 从ThreadLocal缓存中获取的
        HttpServletRequest req = requestAttributes.getRequest();
        System.out.println(req == request); // true
        return "123";
    }

接口想要返回JSON数据,只要加上@ResponseBody,如果返回的是对象,则会自动序列化

整合SSM

只要掌握上述的知识点,就可以开始开发接口服务,下面就以一张表的增删查改作为例子做演示,并且使用RESTFUL风格编写接口

所谓的SSM就是Spring+SpringMvc+Mybatis三个框架集合的简称,看下去的前提是了解SpringMybatis,这里不会赘述

第一步

安装依赖,可自行取舍

<!--        springmvc-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.3.23</version>
</dependency>
<!--        servlet-->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
</dependency>
<!--        json工具-->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.13.3</version>
</dependency>
<!--        mybatis-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.6</version>
</dependency>
<!--        mybatis spring支持-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.7</version>
</dependency>
<!-- 				jdbc -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>5.3.23</version>
</dependency>
<!--        mysql-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.30</version>
</dependency>
<!--        连接池-->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.2.14</version>
</dependency>
<!--        mybatis分页-->
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>5.3.2</version>
</dependency>

第二步,创建Spring的配置类

在这个类上添加相关注解,逐一讲解下基本作用

  1. @Configuration生命为一个配置类
  2. @MapperScan指定了Spring需要扫描的mapper包(这个注解不是Spring,而是MyBatis的)
  3. @ComponentScan指定Spring要扫描的Bean
  4. @Import相当于将传入的类对象中声明的bean复制一份到当前配置类,及分类管理又方便复用
  5. @EnableTransactionManagement表示开始Spring的事务管理,后续需要配合@Transactional进行使用
package cn.mgl.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@MapperScan("cn.mgl.dao")
@ComponentScan("cn.mgl.service")
@Import({JDBCConfig.class, MybatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}

编写一个properties文件管理jdbc的相关配置项,其中usernamepassword需要改成自己的,url中的/db也改成自己的数据库

jdbc.username=root
jdbc.password=12345678
jdbc.url=jdbc:mysql://localhost:3306/db?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
jdbc.driverClassName=com.mysql.cj.jdbc.Driver

先处理JDBCConfig这个配置类,其中的@PropertySource注解,帮助使用properties文件中的配置属性,需要注入值的字段使用@Value("[${属性key}]")的方式,主要作用是定义了两个Bean,配置了数据源事务管理器对象

package cn.mgl.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@PropertySource("classpath:jdbc.properties")
public class JDBCConfig {
    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.password}")
    private String password;

    @Value("${jdbc.driverClassName}")
    private String driverClassName;

    /**
     * 配置数据源
     *
     * @return
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setPassword(password);
        druidDataSource.setUsername(username);
        druidDataSource.setUrl(url);
        druidDataSource.setDriverClassName(driverClassName);
        return druidDataSource;
    }

    /**
     * 配置事务
     *
     * @param dataSource
     * @return
     */
    @Bean
    public PlatformTransactionManager platformTransactionManager(@Autowired DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

添加MybatisConfig类,这里不赘述Mybatis的使用吗,自行添加mybatis-config.xml

package cn.mgl.config;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

/**
* Mybatis的使用需要提供一个SqlSessionFactoryBean
*/
public class MybatisConfig {
    @Bean
    @Autowired
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource, ApplicationContext context) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setConfigLocation(context.getResource("classpath:mybatis-config.xml"));
        return sqlSessionFactoryBean;
    }
}

第三步,创建SpringMvc的配置类

注解配置的作用可以参考第二步,其中这个类需要我们继承WebMvcConfigurationSupport,这个类中已经有许多默认的实现方法,实际上就相当于在这个配置类中,去做以前通过xml文件配置的事,这里主要做了两件事

  1. 在服务端处理了前端通过Ajax请求会出现的跨域问题(浏览器的安全策略)
  2. 添加了一个拦截器,重写了addInterceptors方法,拦截器的实现后面会给出,并且会将拦截器的实现类注册成Bean来完成自动依赖注入
package cn.mgl.config;

import cn.mgl.interceptor.LoginInterceptor;
import jdk.nashorn.internal.scripts.JD;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.*;

import java.nio.charset.StandardCharsets;
import java.util.List;

@Configuration
@ComponentScan("cn.mgl.controller")
@Import(LoginInterceptor.class)
public class SpringMvcConfig extends WebMvcConfigurationSupport {

    private LoginInterceptor loginInterceptor;

    public SpringMvcConfig(LoginInterceptor loginInterceptor) {
        this.loginInterceptor = loginInterceptor;
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTION")
                .maxAge(3600);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor);
    }
}

配置类有几种方式,当子类试图继承抽象类WebMvcConfigurerAdapter时,会提示已被废弃,可以选择实现WebMvcConfigurer,或者继承WebMvcConfigurerSupport,两者的区别是,前者可以选择覆盖部分,而后者则需要重写全部方法,在SpringMvc中,如果启用了@EnableWebMvc,这个注解里会加载一个默认实现类(DelegatingWebMvcConfiguration),此时配置类仍继承为WebMvcConfigurationSupport的实现类,那么这个实现类的重写方法不会被加载

拦截器

下面这个拦截器实现HandlerInterceptor类,但其实没有任何意义,单纯为了演示如何添加一个拦截器而写,分别会在拦截前拦截后拦截完成的实际调用钩子方法

package cn.mgl.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截前");
        return true; // 返回true则表示继续执行
    }

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

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("拦截完成");
    }
}

第四步,编写一个继承ServletContainerInitializer的实现类,Spring容器会在类路径中查找实现ServletContainerInitializer 接口的类,如果发现的话,会使用该类配置进行加载

  1. 重写getRootConfigClasses方法,将带有@Component注解的类提供给Spring当做配置文件根应用上下文使用
  2. 重写getServletConfigClasses方法,将带有@Component注解的类提供给SpringMvc当做DispatcherServlet应用上下文的配置
  3. 重写getServletMappings方法,配置需要通过SpringMvc进行处理的请求路径,下文设置了"/",表示所有请求都由DispatcherServlet处理

SpringConfigSpringMvcConfig)类的编写在上文

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

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

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

}

后面就是编写ControllerServiceDao来进行业务处理,下面提供一个用户模块的增删改查写法(需要自行在MySql建表)

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '唯一主键',
  `username` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码',
  `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
  `gender` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '性别,0为男,1为女',
  `created_at` date NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 62 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

编写Dao层,这里不赘述Mybatis的使用

package cn.mgl.dao;

import cn.mgl.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface UserDao {
    User getUserById(Long id);

    int insert(User user);

    int delete(Long id);

    int update(User user);

    List<User> getAll();
}

编写对应实体User

package cn.mgl.pojo;

public class User {
    private Long id;
    private String username;

    public Long getId() {
        return id;
    }

    public String getUsername() {
        return username;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

编写相关Service

package cn.mgl.service;

import cn.mgl.dao.UserDao;
import cn.mgl.pojo.User;
import com.github.pagehelper.PageHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class UserService {
    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public User getUserById(Long id) {
        return userDao.getUserById(id);
    }

    public List<User> getAll(Integer pageNo, Integer pageSize) {
        PageHelper.startPage(pageNo, pageSize);
        return userDao.getAll();
    }

    // 在配置类中已经开启事务
    // 该配置可以在方法执行期间抛出RuntimeException或Error时进行事务的回滚
    // 没有异常则会自动提交事务
    @Transactional(rollbackFor = Exception.class)
    public int insert(User user) {
        return userDao.insert(user);
    }

    public int delete(Long id) {
        return userDao.delete(id);
    }

    public int update(User user) {
        return userDao.update(user);
    }
}

编写Controller

package cn.mgl.controller;

import cn.mgl.pojo.User;
import cn.mgl.service.UserService;
import com.github.pagehelper.PageInfo;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/users")
public class UserController {
    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public Object selectById(@PathVariable("id") Long id) {
        return userService.getUserById(id);
    }

    @PutMapping
    public Object update(@RequestBody User user) {
        int i = userService.update(user);
        return i == 1 ? "更新成功" : "更新失败";
    }

    @PostMapping
    public Object insert(@RequestBody User user) {
        userService.insert(user);
        return "新增成功";
    }

    @DeleteMapping(value = "/{id}")
    public Object delete(@PathVariable("id") Long id) {
        int i = userService.delete(id);
        return i == 1 ? "删除成功" : "删除失败";
    }

    @GetMapping
    public Object selectAll(Integer pageNo, Integer pageSize) {
        if (pageNo == null) {
            pageNo = 1;
        }
        if (pageSize == null) {
            pageSize = 10;
        }
        List<User> all = userService.getAll(pageNo, pageSize);
        PageInfo<User> tPageInfo = new PageInfo<>(all);
        System.out.println(tPageInfo.getTotal());
        Map<String, Object> result = new HashMap<>();
        result.put("result", all);
        result.put("total", tPageInfo.getTotal());
        return result;
    }
}

mybatis中的sql编写自行处理,这里不贴出,当前项目的编写,可以通过以下请求访问资源

  • get请求,访问/users?pageNo=1&pageSize=10获取分页数据,查询参数不传则默认是110
  • get请求,访问/users/1获取id1的用户数据
  • post请求,访问/users新增用户数据
  • put请求,访问/users更新用户数据
  • delete请求,访问/users/1删除id1的用户数据

一些小问题的解决方案

如果使用java打印出现中文乱码,在tomcat服务器的VM options中添加

-Dfile.encoding=UTF-8

处理浏览器的跨域问题

如果访问接口时使用的是AJAX方式请求,那么就会有浏览器独有的跨域限制,下面的代码在配置SpringMvcConfig类中已经编写好,只是取出来单独讲下。当协议,域名,端口任一不同便会被认为是跨域请求,默认情况下是不支持的,需要在WebMvcConfigurer或者WebMVCConfigurationSupport(区别在拦截器里讲了)中重写addCorsMappings方法。

  • addMapping("/**")表示所有请求
  • allowedOrigins("*")表示允许跨域的源,这里用*号代表不限制源
  • allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTION")表示允许跨域的请求类型
  • maxAge(3600)表示配置预检请求的有效时间, 单位是秒,这里的意思就是3600秒内不需要发出第二次预检请求
    @Override
    protected void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTION")
                .maxAge(3600);
    }

全局的异常处理

当某个接口的方法执行过程中抛出了异常,可以自定义一个异常的处理器用于统一解决,只需要给处理器添加ControllerAdvice或者@RestControllerAdvice,这个注解相当于@Controller+@ResponseBody的效果,这对应的方法中处理最终的返回结果,具体的异常捕获颗粒度由开发者自己控制,比如可以捕获某个具体的异常,也可以捕获异常的父类Throwable

@RestControllerAdvice
public class GlobalException {
    @ExceptionHandler(Exception.class)
    public Object exception() {
        return "Exception";
    }

    @ExceptionHandler(ArithmeticException.class)
    public Object arithmeticException() {
        return "ArithmeticException";
    }
}

举个例子,在上述代码中定了两个异常处理器,SpringMvc会根据就近原则去匹配处理器,在控制层编写一段int i = 1/0;必然会抛出ArithmeticException,由于匹配度高于Exception,最终执行的就是arithmeticException方法

处理响应体返回String时的中文乱码问题

Controller的返回值为字符串时,SpringMvc的默认响应头的Content-Type,与之相对应的表现就是中文会乱码

查看原因:WebMvcConfigurationSupport中有个addDefaultHttpMessageConverters,会添加一些默认的消息转换器,其中往List添加的第二项就是String的消息转换器

查看其构造方法,可以看到默认的就是字符编码就是ISO-8859-1,只需想办法将其改成UTF-8即可解决中文问题

此时可以选择重写WebMvcConfigurationSupportconfigureMessageConverters方法,在此处调用父类的实现方法addDefaultHttpMessageConverters,并且从其中获取String的消息转换器,并更改掉字符编码即可

public class SpringMvcConfig extends WebMvcConfigurationSupport {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        addDefaultHttpMessageConverters(converters);
        StringHttpMessageConverter httpMessageConverter = (StringHttpMessageConverter) converters.get(1);
        httpMessageConverter.setDefaultCharset(StandardCharsets.UTF_8);
    }
    // 其余配置省略...
}

再次访问中文乱码的接口,,响应头的Content-Type已更改成功,乱码问题解决

也可以选择单独在某个映射注解上添加produces属性配置,相对比较麻烦

  @GetMapping(value = "/{id}", produces = "text/html;charset=UTF-8")

完 : )