SpringMVC是Spring家族中的重要一员,基于MVC的开发模式用于优化控制器
M(Model)
:模型层, 包含了实体类(Entity)
,业务逻辑层(Service)
,数据访问层(DAO)
V(View)
:视图层,用户能看到的所有均为视图层C(Controller)
:控制器,用来接收客户端的请求,并返回响应到客户端(Servlet)
SpringMVC
具有轻量级,易上手,功能强的优势,是现代JavaWeb
应用不可缺少的一部分,看这篇文章前需要掌握Servlet
和Spring
使用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代码和注解式开发
有两点注意事项
- 使用注解式开发需要单独引入对应的servlet类,注意是javax的包
- 把原本
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
地址映射,相当于在Servlet
的mapping
映射添加地址,既可以添加到类,也可以添加到方法
-
- 添加到类相当于添加访问前缀
- 添加到方法,可以直接写全路径,若
Controller
类已经配置了前缀,则可以直接写后缀,框架会自己进行路径拼接 - 默认情况下,返回结果是个
String
的话,会被认为是一个跳转路径,会经过视图处理器去查找对应的视图,在如今前后端分离的时代,完全没有必要去学习,知道有这么个逻辑即可 - 除了
value
的路径可以传入,还可以指定请求的方法,当不指定时,不管是任何请求方法都可以匹配到这个方法中,如果指定方法则使用@RequestMapping(value = "/user", method = RequestMethod.GET)
-
-
- 根据请求方法不同则衍生出了
GetMapping
,PostMapping
,DeleteMapping
,PutMapping
,PatchMapping
之类的注解,其实就是简写方便了开发
- 根据请求方法不同则衍生出了
-
@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-Type
为application/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
三个框架集合的简称,看下去的前提是了解Spring
和Mybatis
,这里不会赘述
第一步
安装依赖,可自行取舍
<!-- 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的配置类
在这个类上添加相关注解,逐一讲解下基本作用
@Configuration
生命为一个配置类@MapperScan
指定了Spring
需要扫描的mapper
包(这个注解不是Spring
,而是MyBatis
的)@ComponentScan
指定Spring
要扫描的Bean
包@Import
相当于将传入的类对象中声明的bean
复制一份到当前配置类,及分类管理又方便复用@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
的相关配置项,其中username
和password
需要改成自己的,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
文件配置的事,这里主要做了两件事
- 在服务端处理了前端通过
Ajax
请求会出现的跨域问题(浏览器的安全策略) - 添加了一个拦截器,重写了
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
接口的类,如果发现的话,会使用该类配置进行加载
- 重写
getRootConfigClasses
方法,将带有@Component
注解的类提供给Spring
当做配置文件根应用上下文使用 - 重写
getServletConfigClasses
方法,将带有@Component
注解的类提供给SpringMvc
当做DispatcherServlet
应用上下文的配置 - 重写
getServletMappings
方法,配置需要通过SpringMvc
进行处理的请求路径,下文设置了"/"
,表示所有请求都由DispatcherServlet
处理
(SpringConfig
和SpringMvcConfig
)类的编写在上文
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[]{"/"};
}
}
后面就是编写Controller
,Service
,Dao
来进行业务处理,下面提供一个用户模块的增删改查写法(需要自行在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
获取分页数据,查询参数不传则默认是1
和10
get请求
,访问/users/1
获取id
为1
的用户数据post请求
,访问/users
新增用户数据put请求
,访问/users
更新用户数据delete请求
,访问/users/1
删除id
为1
的用户数据
一些小问题的解决方案
如果使用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
即可解决中文问题
此时可以选择重写WebMvcConfigurationSupport
的configureMessageConverters
方法,在此处调用父类的实现方法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")
完 : )