1. handler方法分析
/**
* TODO: 一个controller的方法是控制层的一个处理器,我们称为handler
* TODO: handler需要使用@RequestMapping/@GetMapping系列,声明路径,在HandlerMapping中注册,供DS查找!
* TODO: handler作用总结:
* 1.接收请求参数(param,json,pathVariable,共享域等)
* 2.调用业务逻辑
* 3.响应前端数据(页面(不讲解模版页面跳转),json,转发和重定向等)
* TODO: handler如何处理呢
* 1.接收参数: handler(形参列表: 主要的作用就是用来接收参数)
* 2.调用业务: { 方法体 可以向后调用业务方法 service.xx() }
* 3.响应数据: return 返回结果,可以快速响应前端数据
*/
@GetMapping
public Object handler(简化请求参数接收){
调用业务方法
返回的结果 (页面跳转,返回数据(json))
return 简化响应前端数据;
}
总结:
通过handler的形参列表接收请求数据
通过handler的return关键字响应前端数据(视图或JSON)
2. 页面跳转控制
configureViewResolvers 则是用于配置更复杂的视图解析器,例如定义不同后缀的视图,或者添加自定义的视图解析器。
addViewControllers 主要用于简单的视图跳转,对于简单的页面导航,不需要业务逻辑的情况,可以直接使用这个方法进行配置。因为该方法不需要配置Controller.
2.1 configureViewResolvers视图解析器返回模板视图页面
什么是返回模板视图页面?
采用轻度的前后端分离项目时, 前端代码未完成与后端解耦合, 前端的代码(如视图)仍存在于整体项目中
目标 : 用户访问localhost:8080/jsp/index时,返回给用户本地webapp-WEB-INF-view下的home.jsp页面.
- pom文件添加jsp依赖
<!-- jsp需要依赖! jstl-->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>
- 创建等会要返回的home.jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<!-- 可以获取共享域的数据,动态展示! jsp== 后台vue -->
${msg}
</body>
</html>
- 新建一个MvcConfig类
//本类中要重写handlerMapping,handlerAdapter两个方法,配置一个json转化器, 通过一个注解@EnableWebMvc实现
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.sunsplanter.controller") // 进行controller扫描
public class SpringMvcConfig implements WebMvcConfigurer {
//配置一个jsp对应的视图解析器
public void configureViewResolvers(ViewResolverRegistry registry) {
//稍后的handler中如果想给前端返回 /WEB-INF/views/index .jsp
//Resigter可以帮忙添加前后缀
registry.jsp("/WEB-INF/views/",".jsp");
}
}
- 编写jspController类
/**
* 跳转到提交文件页面
*
* 如果要返回jsp页面!
* 1.方法返回值改成字符串类型
* 2.返回逻辑视图名即可
* <property name="prefix" value="/WEB-INF/views/"/>
* + 逻辑视图名 +
* <property name="suffix" value=".jsp"/>
*/
//访问localhost:8080/jsp/index时,返回给用户本地webapp-WEB-INF-view下的home.jsp页面.
@GetMapping("index")
public String jumpJsp(Model model){
System.out.println("FileController.jumpJsp");
return "home";
}
2.2 addViewControllers视图控制器返回模板视图页面
对于上例, HTML代码不变, Controller方法删除, 仅将MvcConfig中的方法更换为:
// 跳转视图页面控制器
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("home");
}
同样可以实现进入index页面则自动返回一个视图.
2.3 跳转和重定向
重定向与跳转的区别:
s
1.请求的次数的不同 : 重定向总共请求了两次服务器;转发则是用户请求一次可能经过N个JSP页面由返回到用户浏览器中,是一次请求多次处理的过程;
2.跳转过程中链接的变化不同 : ,重定向在跳转中请求了两次服务器并且是两次不同的链接地址,在浏览器的地址栏可以看到两次是有变化的;转发在跳转过程中浏览器请求了一次服务器,服务器经过了n个JSP页面并没有改变请求的链接地址,因为用户只请求了一次,所以在整个跳转过程中链接地址是没有改变的,在浏览器的地址栏就可以看到
3.目的不同 : 重定向只是简单的让用户访问一个新的链接,而转发是服务器要得到用户的请求内容并需要进行一部分处理的,所以两者目的之不同的
- 不管是重定向(redirect)还是转发(forward),其handler都要设置String类型
- 语法为 return 关键字: /路径,不需要额外注解
- @ResponseBody注解和转发/重定向是互斥的。@ResponseBody注解的类会快速返回,不走视图解析器,也不会再转发/重定向。
- 注意:如果是项目下的资源,转发和重定向都一样都是项目下路径!都不需要添加项目根路径!
@RequestMapping("/redirect-demo")
public String redirectDemo() {
// 重定向到 /demo 路径
return "redirect:/demo";
}
@RequestMapping("/forward-demo")
public String forwardDemo() {
// 转发到 /demo 路径
return "forward:/demo";
}
3.返回JSON数据
- 如上章接收JSON数据一样,涉及JSON必须要添加依赖以及在config类中配置JSON转换器(@EnableWebMvc)。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
- 如上章接收JSON数据一样(@RequestBody),使用@ResponseBody注解返回JSON数据,该注解的作用在于将返回的对象序列化为 JSON 或 XML 格式的数据,并发送给客户端。
- 需注意的是ResponseBody不经过视图解析器,且与转发/重定向互斥。
@GetMapping("/accounts/{id}")
@ResponseBody
public Object handle() {
// ...
return obj;
}
测试方法:
@Controller
@RequestMapping(value = "/user/detail", method = RequestMethod.POST)
@ResponseBody
public User getUser(@RequestBody User userParam) {
System.out.println("userParam = " + userParam);
User user = new User();
user.setAge(18);
user.setName("John");
//返回的对象,会使用jackson的序列化工具,转成json返回给前端!
return user;
}
合并两条注解:
@Controller+@ResponseBody = @RestController
3.configureDefaultServletHandling返回静态资源
什么是静态资源?
资源本身已经是可以直接拿到浏览器上使用的程度了,不需要在服务器端做任何运算、处理。典型的静态资源包括:
- 纯HTML文件/图片
- CSS文件
- JavaScript文件
如何让外部能够访问静态资源?
- DispatcherServlet 的 url-pattern 配置的是“/”,表示整个 Web 应用范围内所有请求都由 SpringMVC 来处理
- 而对 SpringMVC 来说,若想某个url被后台处理,必须将这个urll用 @RequestMapping 绑定到某个handler上,才能找到处理请求的方法,否则handlermapping(秘书)无法知道找哪个handler(打工人)处理这个链接。
- 因此,思路是显然的,那就是想办法让系统知道,如果请求的是url是xxx/xxx/car.jpg , 那就从静态资源中直接提取而不是找handler(静态资源不可能存进handler里)。
- 准备好静态资源并编译:
2. 在config类中开启静态资源处理:
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.sunsplanter.controller") //
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法!
public class SpringMvcConfig implements WebMvcConfigurer {
//开启静态资源处理
//处理的顺序是DispatcherServlet --> handlerMapping -->没找到就去找静态资源
//平时第二步走完就结束,该方法多走一步为了找到静态资源
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
访问localhost:8080/images/car.jpg , 可以正常显示.
addFormatters 数据格式化
- Formatter 和 Converter<S,T> 都是数据转换接口,将一种数据类型转换为另一种数据类型。
- Formatter只能将 String 类型转为其他数据数据类型。
这点在Web 应用适用更广。因为 Web 请求的所有参数都是 String,我们需要把 String 转为 Integer ,Long,Date 等
.1 接口原型
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Formatter是一个组合接口,没有自己的方法。内容来自 Printer和 Parser
Printer:将 T 类型转为 String,格式化输出
Parser:将 String 类型转为期望的 T 对象。
.2 Spring已经实现的Formatter
前述已经提到, Formatter是一个接口, 而Spring已经帮我们实现了常用的几个类
- DateFormatter : String 和 Date 之间的解析与格式化
- NumberFormat :String 和 Number 之间的解析与格式化
- InetAddressFormatter :String 和 InetAddress 之间的解析与格式化
- PercentStyleFormatter :String 和 Number 之间的解析与格式化,带货币符号
- Formatter也是 Spring 的扩展点,我们处理特殊格式的请求数据时,如果以上四种对象不能满足, 我们可以自己实现合适的 Formatter,重写 Print 和 Parse 方法, 将请求的 String 数据转为我们的某个对象,使用这个对象更方便我们的后续编码。
.3 Formatter生效的时间节点
当Controller接收到前端的参数时, 会先寻找已注册的 XXXFormatter 进行类型转换。
具体到接下来的例子,
- Controller层指定接收的参数为DeviceInfo类型
- Spring寻找所有已经注册的XXXFormatter,发现 "public class DeviceFormatter implements Formatter " ,即DeviceFormatter是为DeviceInfo类型进行类型转换的,
- 就调用该类中的parse方法 , 将传入的东西(String字符串)转换成DeviceInfo对象并赋值进去,今后可以直接使用deviceInfo,里面的属性已被正确赋值
- 等到Controller层的return语句时, 会再次调用该类的Print方法转化为字符串.
.4 自定义Formatter尝试
需求: 接收前端传来的字符串 “1111;2222;333,NF;4;561”,并在代码中用 DeviceInfo 存储参数值
.4.1 实体类
import lombok.Data;
@Data
public class DeviceInfo {
private String item1;
private String item2;
private String item3;
private String item4;
private String item5;
}
.4.2 自定义Formatter
import com.sunsplanter.sf2.pojo.DeviceInfo;
import org.springframework.format.Formatter;
import org.springframework.util.StringUtils;
import java.text.ParseException;
import java.util.Locale;
import java.util.StringJoiner;
public class DeviceFormatter implements Formatter<DeviceInfo> {
//重写parse方法将 String 数据转为 DeviceInfo
//Locale对象可以限定日期, 语言, 数字等格式, 为了重写parse方法必须要带上这个参数, 但本次不需要使用
@Override
public DeviceInfo parse(String text, Locale locale) throws ParseException {
DeviceInfo deviceInfo = null;
//如果传入字符串即不为NULL也不未空("")
if (StringUtils.hasLength(text)) {
//每次遇到";"就分割字符串并存入数组
String[] items = text.split(";");
deviceInfo = new DeviceInfo();
deviceInfo.setItem1(items[0]);
deviceInfo.setItem2(items[1]);
deviceInfo.setItem3(items[2]);
deviceInfo.setItem4(items[3]);
deviceInfo.setItem5(items[4]);
}
return deviceInfo;
}
//将 DeviceInfo 转为 String
//将 DeviceInfo 对象的属性 item1 和 item2 以 # 为分隔符拼接成一个字符串
@Override
public String print(DeviceInfo object, Locale locale) {
StringJoiner joiner = new StringJoiner("#");
joiner.add(object.getItem1()).add(object.getItem2());
return joiner.toString();
}
}
.4.3 在MVCConfig类中注册该Formatter
import com.sunsplanter.sf2.formatter.DeviceFormatter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MVCConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/welcome").setViewName("index");
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DeviceFormatter());
}
}