SpringMVC
- SpringMVC 技术与 Servlet 技术功能相同,均属于Web层开发技术
- SpringMVC 是一种基于Java实现的MVC模型的轻量级Web框架
- 相较于 Servlet 使用简单,开发便捷,灵活性强。
由于目前绝大多数项目都是使用Springboot框架开发,所以本文只讲述一些重要概念,而不去实现。
案例
第一步:导坐标
<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>
第二步:创建SpringMVC控制器类
等同于Servlet功能
@Controller //声明这个类是一个Bean 1
public class userControler {
@RequestMapping("/save") //设置当前操作的访问路径 2
@ResponseBody //设置当前操作的返回值类型3
public String save(){
System.out.println("user save...");
return "{'info':'springmvc'}";
}
}
第三步:创建SpringMVC的配置类
@Configuration
@ComponentScan("com.protectark.mysqlt")
public class SpringMvcConfig {
}
第四步:定义一个servlet容器启动的配置类,在里面加载spring的配置
public class servletInConfig extends AbstractDispatcherServletInitializer {
//加载SpringMVC容器配置
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext act = new AnnotationConfigWebApplicationContext();
//注册配置
act.register(SpringMvcConfig.class);
return act;
}
//设置那些请求归属springMVC处理
@Override
protected String[] getServletMappings() {
//设置所有请求归SpringMVC管理
return new String[]{"/"};
}
//加载spring容器配置
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
注解说明
在以上案例中,出现了一些新的注解
| 注解 | 类型 | 位置 | 作用 |
|---|---|---|---|
| @Controller | 类注解 | SpringMVC控制器类上 | 设定SpringMvc的核心控制器bean |
| @RequestMapping(地址) | 方法注解 | SpringMVC控制器类中方法上 | 设置当前控制器方法请求访问路径 |
| @ResponseBody | 方法注解 | SpringMVC控制器方法定义上方 | 设置当前控制器方法响应内容为当前返回值,无需解析 |
工作流程说明
启动服务器初始化过程
- 服务器启动,执行ServletContainersInitConfig类,初始化web容器
- 执行createServletApplicationContext方法,创建了webApplicationcontext对象
- 加载SpringMvcConfig
- 执行@ComponentScan加载对应的bean
- 加载UserController,每个@RequestMapping的名称对应一个具体的方法
- 执行getServletMappings方法,定义所有的请求都通过springMVC
单次请求过程
- 发送请求localhost/ save
- web容器发现所有请求都经过springMVC,将请求交给SpringMVC处理
- 解析请求路径/save
- 由/save匹配执行对应的方法save( )
- 执行save()
- 检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方
简化配置类
Spring提供了一个AbstractDispatcherServletInitializer的子类: AbstractAnnotationConfigDispatcherServletInitializer
public class servletInConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
//Spring配置路径
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{springConfig.class};
}
//SpringMVC配置路径
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{springMvcConfig.class};
}
//拦截路径
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
请求与响应
解决路径冲突
在团队多人开发,如何解决请求路径冲突
设置模块名作为请求路径的前缀 两种方法:
- 修改每个方法的路径
@RuquestMapping如:"/user/save""/book/save" - 修改类的请求路径 "/user" "/save" "/book" "/save"
| 注解 | 类型 | 位置 | 作用 |
|---|---|---|---|
| @RequestMapping(value) | 方法注解 类注解 | SpringMVC控制器类中方法上 | 设置当前控制器方法请求访问路径,如果设置在类上统一设置当前控制器方法请求访问路径前缀 |
示例:
@Controller
@RequestMapping("/user")
public class userControler {
@RequestMapping("/save")
@ResponseBody
public String save(){
return "{'info':'springmvc'}";
}
}
- value(默认):请求访问你路径,或访问路径前缀
解决中文乱码
关于发送的中文乱码,可以在“servlet容器启动的配置类” servletInConfig中覆盖父类的方法: 为web容器添加过滤器并指定字符集,Spring-web包中提供了专用字符过滤器
//乱码处理
@Override
protected Filter[] getServletFilters() {
//获取字符过滤器
CharacterEncodingFilter filter = new CharacterEncodingFilter();
//设定字符集
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
请求参数与形参不同
例如,定义的方法中形参名为username和age,但是实际传参为name,age
@Controller
public class userControler {
@RequestMapping("/save")
@ResponseBody
public String save(String username, int age){
System.out.println(username + age);
return "{'info':'springmvc'}";
}
}
测试之后就会发现
username为null
此时我们可以使用一个注解,用来声明参数名: @RequestParam("name")
@Controller
public class userControler {
@RequestMapping("/save")
@ResponseBody
public String save(@RequestParam("name") String username,
@RequestParam("age") int age){
System.out.println(username + age);
return "{'info':'springmvc'}";
}
}
传递参数为实体类
如果我们需要的参数过多,比如在新增用户时需要大量的数据,这样我们一般会制作一个实体类,来进行接收。
@Controller
public class userControler {
@RequestMapping("/save")
@ResponseBody
public String save(User user){
return "{'info':'springmvc'}";
}
}
public class User{
private String username;
private int age;
//等其他属性
//重写getter,setter,toString方法
}
这样请求时,如果请求的属性名与实体类中的属性名完全一致,则会自动放入user中。
传递JSON数据
传递JSON数据的时候,我们的数据并不是在请求头的参数中,而是在请求体中。
所以我们在接收的时候也要添加注解,来声明这是请求体中的数据
下面就来实际写一下:
首先应该导入JSON数据转化相关坐标
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
然后告诉SpringMVC开启JSON转化数据
只需要再SpringMvcConfig中加入@EnableWebMvc注释,开启JSON转化数据
@Configuration
@ComponentScan("com.protectark.mysqlt")
@EnableWebMvc
public class SpringMvcConfig {
}
由于JSON请求再请求体当中所以,再Controller方法中 对于形参的修饰不能使用@RequestParam 要使用@RequestBody
@RequestMapping("/inputuser")
@ResponseBody
public String init(@RequestBody User user){
System.out.println("input user ==>"+user);
return "{'info':'springMVC'}";
}
到此完成。
| 注解 | 类型 | 位置 | 作用 |
|---|---|---|---|
@EnableWebMvc | 配置类注解 | SpringMVC配置类定义上方 | 开启SpringMVC多项辅助功能 |
@RequestBody | 形参注解 | Controller方法形参前 | 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次 |
RequestBody与RequestParam对比
区别
- @RequestParam用于接收ur1地址传参,表单传参【application/x-www-form-urlencoded)
- @RequestBody用于接收json数据【application/json】
应用
后期开发中,发送json格式数据为主,@RequestBody应用较广
如果发送非json格式数据,选用@RequestParam接收请求参数
后面还有一个@PathVariable 属于形参注解,绑定路径参数与形参的关系
@RequestMapping("/users/{id}")
@ResponseBody
public String init(@PathVariable Integer id){
System.out.println("input id ==>"+id);
return "{'info':'springMVC'}";
}
日期类型参数传递
SpringMVC可以直接接收String类型的参数,并自动转为日期类型,如果需要指定格式可以使用注解
@DateTimeFormat
@RequestMapping("/inputuser")
@ResponseBody
public String init(@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date date){
System.out.println("input date ==>"+date);
return "{'info':'springMVC'}";
}
| 注解 | 类型 | 位置 | 作用 |
|---|---|---|---|
@DateTimeFormat | 形参注解 | SpringMVC控制器方法形参前 | 设定日期时间型数据格式 |
属性:pattern:日期时间格式字符串
响应
如果要响应json数据,则只需要返回实体类即可
@RequestMapping("/select")
@ResponseBody
public User select(){
User user = new User();
return user;
}
SpringMVC会自动将实体类转化为json数据
| 注解 | 类型 | 位置 | 作用 |
|---|---|---|---|
@ResponseBody | 方法注解 | SpringMVC控制器方法上 | 设置当前控制器方法响应内容为当前返回值,无需解析 |
REST 风格
概念
REST(Representational state Transfer),表现形式状态转换
- 传统风格资源描述形式
- http://localhost/user/getById?id=1
- http://localhost/user/saveUser
- REST风格描述形式
- http://localhost/user/1
- http://localhost/user
优点:书写简化,隐藏资源访问方式,无法通过地址得知对资源是哪种请求方式。
按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
- http://localhost/users 查询全部用户信息 GET(查询)
- http://localhost/users/1 查询指定用户信息 GET(查询)
- http://localhost/users 添加用户信息 POST(新增/保存)
- http://localhost/users 修改用户信息 PUT(修改/更新)
- http://localhost/users/1 删除用户信息 DELETE(删除)
这种方式属于约定方式,称为REST风格。根据这种风格对资源访问的方式称为RESTful
描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts...
RESTful 快速开发
由于REST风格都使用JSON进行通信,所以每一个方法都需要带有@ResponseBody,所以我们可以将这个注解放到控制器类上,又因为我们所有的控制器类都有@Controller和@ResponseBody注解,所以SpringMVC就将这两个合二为一:@RestController
又因为我们使用的请求方式,绝大多数就四种,所以@RequestMapping注解也可以根据请求方式衍生出
- @PostMapping
- @GetMapping
- @PutMapping
- @DeleteMapping
使用这种方式,我们的Controller就由
@Controller
public class userControler {
@RequestMapping(value = "/users/save",method = RequestMethod.Get)
@ResponseBody
public String save(@Requestbody User user){
return "{'info':'springmvc'}";
}
@RequestMapping(value = "/users/delete",method = RequestMethod.Delete)
@ResponseBody
public String save(@Requestbody User user){
return "{'info':'springmvc'}";
}
}
变为了
@RestController
@RequestMapping("/users")
public class userControler {
@GetMapping()
public String save(@Requestbody User user){
return "{'info':'springmvc'}";
}
@DeleteMapping()
public String save(@Requestbody User user){
return "{'info':'springmvc'}";
}
}
统一结果类
设置统一结果类有以下好处:
一、提高可维护性
- 集中管理返回结果,方便统一修改和扩展。
- 清晰定义错误处理逻辑,便于维护和调试。
二、增强稳定性和可靠性
- 规范异常信息,避免前端解析错误。
- 实现全局异常处理,防止系统崩溃。
三、提升团队协作效率
- 为前后端提供明确规范,减少沟通成本。
- 可复用性高,减少代码冗余。
四、便于监控和统计
- 统一日志记录格式,方便分析和监控。
- 利于数据统计分析,发现问题和瓶颈。
一般我们的统一结果类由三部分组成,分别是code、message、data
/**
* 后端统一返回结果
* @param <T>
*/
@Data
public class Result<T> implements Serializable {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
public static <T> Result<T> success() {
Result<T> result = new Result<T>();
result.code = 1;
return result;
}
public static <T> Result<T> success(T object) {
Result<T> result = new Result<T>();
result.data = object;
result.code = 1;
return result;
}
public static <T> Result<T> error(String msg) {
Result result = new Result();
result.msg = msg;
result.code = 0;
return result;
}
}
对于 code 我们一般将提前定义好的状态码写道一个类中,该类用于存放所有用到的状态码常量。
public class Code {
public static final Integer SAVE_OK = 200;
/* 等等 */
}
异常处理器
基本概念
开发中不可避免会出现异常现象。
出现异常现象的常见位置与常见诱因如下:
- 框架内部抛出的异常:因使用不合规导致
- 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
- 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
- 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
- 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)
我们可以发现,所有的层级都会抛出异常,所以我们采取的做法是,全部上抛,统一处理。 所有异常均抛到表现层进行处理
Spring提供的异常处理器:
- 集中统一处理项目中出现的异常
//处理REST风格的Controller
@RestControllerAdvice
public class ProjectExceptionAdvice {
// 拦截异常的种类
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
return new Result(666,null);
}
}
注意:
由于我们拦截器属于
SpringMVC技术,对SpringMVC进行加强,所以要吧拦截器放到能被SpringMvcConfig识别到的路径下,一般放到controller包下前面我们也说了,要返回统一结果类,所以我们要修改异常处理器,使其返回结果类。
项目中异常处理
项目异常分类
- 业务异常(BusinessException)
- 规范的用户行为产生的异常
- 不规范的用户行为操作产生的异常
- 系统异常(SystemException)
- 项目运行过程中可预计且无法避免的异常
- 其他异常(Exception)
- 编程人员未预期到的异常
项目异常处理方案
- 业务异常(BusinessException)
- 发送对应消息传递给用户,提醒规范操作
- 系统异常(SystemException)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给运维人员,提醒维护
- 记录日志
- 其他异常(Exception)
- 发送固定消息传递给用户,安抚用户
- 发送特定消息给编程人员,提醒维护(纳入预期范围内)
- 记录日志
定义异常
依据上面的分类,可以定义出两种异常,系统异常与业务异常
我们创建新的包exception,用于存放自定义异常
/**
* 自定义系统异常
*/
public class SystemException extends RuntimeException{
//给异常编号
private Integer code;
/* 此处省略getter&setter */
//重写所有的构造方法
public SystemException(Integer code) {
this.code = code;
}
public SystemException(String message, Integer code) {
super(message);
this.code = code;
}
public SystemException(String message, Throwable cause, Integer code) {
super(message, cause);
this.code = code;
}
public SystemException(Throwable cause, Integer code) {
super(cause);
this.code = code;
}
public SystemException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Integer code) {
super(message, cause, enableSuppression, writableStackTrace);
this.code = code;
}
}
同理创造出BusinessException
抛出自定义异常
到业务层中可能出现异常的位置,将代码包装,转化为抛出异常
public User getById(Integer id){
User user = userMapper.getById(id);
if (user == null) {
//此处code值我们定义到Code常量中。
throw new SystemException(Code.SYSTEM_NULL,"结果为空");
}
return user;
}
处理自定义异常
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex){
/* 记录日志 */
/* 发送消息给运维 */
return new Result(ex.getCode,ex.getMessage);
}
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
return new Result(666,null);
}
}
拦截器
拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVc中动态拦截控制器方法的执行 作用:
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行 拦截器的作用主要包括:
- 权限与访问控制:验证用户身份及权限,限制对特定资源的访问,保障系统安全。
- 日志与性能监控:记录请求信息,分析性能瓶颈,为优化提供数据支持。
- 数据处理:对请求数据预处理和响应数据后处理,确保数据规范、安全。
- 防止重复提交与验证:防止表单重复提交,验证表单数据合法性。
- 系统集成:控制系统间交互,执行统一业务逻辑,提高复用性和可维护性。
与过滤器区别
- 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
- 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVc的访问进行增强
- 设计目的不同:拦截器侧重于业务逻辑前后处理,与业务关联性强;过滤器主要对请求和响应进行一般性过滤和处理。
- 执行顺序:拦截器按在 Spring MVC 中的注册或注解顺序执行特定方法;过滤器由 Web 容器的部署描述符或注解指定顺序执行doFilter方法。
使用
首先创建一个新的包interceptor,这个包可以放到controller包下,因为拦截器就是对表现层做增强的。
创建拦截器类,并实现 HandlerInterceptor 接口
/**
* 该拦截器要作为bean被SpringMVC配置类识别
*/
@Component
public class Interceptor implements HandlerInterceptor {
/* 控制器执行前 */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 此处true为全部放行
return true;
}
/* 控制器执行后 */
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
/* Post执行之后 */
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
创建SpringMVC配置类
@Configuration
public class SpringMvcConfig extends WebMvcConfigurationSupport {
@Autowired
private Interceptor interceptor;
/* /user 拦截路径 */
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/users");
}
}
配置多个拦截器
根据 Interceptor 创建一个 Interceptor2 的拦截器 修改 SpringMvcConfig
@Configuration
public class SpringMvcConfig extends WebMvcConfigurationSupport {
@Autowired
private Interceptor interceptor;
@Autowired
private Interceptor2 interceptor2;
/* /user 拦截路径 */
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/users");
registry.addInterceptor(interceptor2).addPathPatterns("/users");
}
}
