目录
1、SpringMVC 自动配置概览
2、简单功能分析
2.1、静态资源访问
2.1.1、静态资源目录?
2.1.2、静态资源访问前缀
2.1.3、webjar
2.2、欢迎页支持
2.3、自定义 Favicon
3、请求参数处理
3.1、请求映射
3.1.1、rest 的使用
3.2、普通参数与基本注解
3.2.1、注解
3.2.2、自定义对象参数自动装配
4、数据响应与内容协商
?4.1、内容协商
4.1.1、引入依赖
4.1.2 开启浏览器参数方式内容协商功能
5、视图解析与模板引擎
5.1、模板引擎 - Thymeleaf
5.1.1、简介
5.1.2、基本语法
????????① 表达式
????????② 字面量
????????③ 文本操作
????????④ 数学运算
????????⑤ 布尔运算
????????⑥ 比较运算
????????⑦ 条件运算
????????⑧ 特殊操作
5.1.3、设置属性值? th:attr
5.2、Thymeleaf 的使用
5.2.1、引入 Starter 依赖
5.2.2、在页面引入 Thymeleaf 名称空间
6、拦截器
6.1、HandlerInterceptor 接口
6.2、配置拦截器
7、文件上传
7.1、表单
7.2、代码
8、异常处理
8.1、默认规则
9、Web 原生组件注入(Servlet、Filter、Listener)
9.1、使用 ServletAPI
9.1.1、Servlet
9.1.2、Filter
9.1.3、Listener
9.2、使用RegistrationBean
1、SpringMVC 自动配置概览
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置) The auto-configuration adds the following features on top of Spring’s defaults: 1、Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans. 内容协商视图解析器和BeanName视图解析器
2、Support for serving static resources, including support for WebJars (covered later in this document)). 静态资源(包括webjars)
3、Automatic registration of Converter, GenericConverter, and Formatter beans. 自动注册 Converter,GenericConverter,Formatter
4、Support for HttpMessageConverters (covered later in this document). 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
5、Automatic registration of MessageCodesResolver (covered later in this document). 自动注册 MessageCodesResolver (国际化用)
6、Static index.html support. 静态index.html 页支持
7、Custom Favicon support (covered later in this document). 自定义 Favicon
8、Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document). 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)
2、简单功能分析
2.1、静态资源访问
2.1.1、静态资源目录?
只要静态资源放在类路径下: 包名为 /static (或 /public 或 /resources 或 /META-INF/resources)
访问 : 当前项目根路径/ + 静态资源名
?例如图片放在一下路径:
?则访问路径为?http://localhost:8080/1.jpg、http://localhost:8080/2.jpg?……?http://localhost:8080/5.jpg
原理: 静态映射/** 类似SpringMVC,请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
改变默认的静态资源路径:?
spring:
mvc:
static-path-pattern: /res/** #设置访问静态资源的前缀
web:
resources:
static-locations: [classpath:/haha/] #设置默认的静态资源路径(数组)
设置后,访问静态资源必须加上前缀 /res,且必须在设置的多个静态资源路径中存在该资源
例如?http://localhost:8080/res/2.jpg
2.1.2、静态资源访问前缀
默认情况下无前缀
在 application.yaml 中设置前缀:
spring:
mvc:
static-path-pattern: /res/** #设置访问静态资源的前缀
则访问路径为:当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
2.1.3、webjar
官网:WebJars - Web Libraries in Jars
作用:自动映射 /webjars/**
依赖:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径
2.2、欢迎页支持
● 静态资源路径下 ?index.html ? ○ 可以配置静态资源路径 ? ○ 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问,需要完整的路径,例如:http://localhost:8080/res/index.html ● controller能处理/index
2.3、自定义 Favicon
将需要的图片命名为 favicon.ico 放在静态资源目录下即可。
注:访问静态资源的前缀会影响 favicon.ico 的显示
效果如下:
3、请求参数处理
3.1、请求映射
3.1.1、rest 的使用
● @xxxMapping; ● Rest风格支持(使用HTTP请求方式动词来表示对资源的操作) ? ○ 以前:/getUser ? 获取用户 ? ? /deleteUser 删除用户 ? ?/editUser ?修改用户 ? ? ? /saveUser 保存用户 ? ○ rest风格: /user ? ?GET-获取用户 ? ?DELETE-删除用户 ? ? PUT-修改用户 ? ? ?POST-保存用户 ? ○ 核心Filter;HiddenHttpMethodFilter ? ? ????????■ 用法: 表单method=post,隐藏域 _method=put,SpringBoot 中的 application.yml 手动开启
spring:
mvc:
hiddenmethod:
filter:
enabled: true
<form action="/user" method="get">
<input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="delete"/>
<input name="_m" type="hidden" value="delete"/>
<input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT"/>
<input value="REST-PUT 提交" type="submit"/>
</form>
? ?○ 扩展:把_method 这个名字换成我们自己喜欢的:自定义 filter
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
Rest原理(表单提交要使用REST的时候) ● 表单提交会带上_method=PUT ● 请求过来被HiddenHttpMethodFilter拦截 ? ○ 请求是否正常,并且是POST ? ? ■ 获取到_method的值。 ? ? ■ 兼容以下请;PUT、DELETE、PATCH ? ? ■ 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。 ? ? ■ 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
3.2、普通参数与基本注解
3.2.1、注解
@PathVariable(路径变量)、@RequestHeader(获取请求头)、@RequestParam(获取请求参数)、@CookieValue(获取cookie值)、@RequestBody(获取请求体[POST])
@RestController
public class ParameterTestController {
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name, //获取路径变量
@PathVariable Map<String,String> pv, //获取所有路径变量
@RequestHeader("User-Agent") String userAgent, //获取特定请求头
@RequestHeader Map<String,String> header, //获取所有请求头信息
@RequestParam("age") Integer age, //获取请求参数
@RequestParam("inters") List<String> inters, //获取同名多值的请求参数(复选框)
@RequestParam Map<String,String> params, //获取所有请求参数,同名多值的请求参数只显示一个
@CookieValue("Idea-c7c6cd52") String _ga, //获取特定的cookie值
@CookieValue("Idea-c7c6cd52") Cookie cookie){ //获取特定的cookie值
Map<String,Object> map = new HashMap<>();
//map.put("id",id);
//map.put("name",name);
//map.put("pv",pv);
//map.put("userAgent",userAgent);
//map.put("headers",header);
map.put("age",age);
map.put("inters",inters);
map.put("params",params);
map.put("_ga",_ga);
System.out.println(cookie.getName()+"===>"+cookie.getValue());
return map;
}
@PostMapping("/save")
public Map postMethod(@RequestBody String content){ //获取请求体
Map<String,Object> map = new HashMap<>();
map.put("content",content);
return map;
}
}
@RequestAttribute(获取request域的属性)?
@Controller
public class RequestController {
@ResponseBody
@GetMapping("/success")
//获取request域属性的两种方法
public Map success(@RequestAttribute("msg") String msg,
@RequestAttribute("code") Integer code,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");
Map<String, Object> map = new HashMap<>();
map.put("request_msg", msg1);
map.put("annotation_msg", msg);
return map;
}
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("msg", "成功啦");
request.setAttribute("code", 200);
return "forward:/success";
}
}
@MatrixVariable(矩阵变量)
页面开发,cookie禁用了,session里面的内容怎么使用; cookie:session.set(a,b)---> jsessionid ---> cookie ----> 每次发请求携带。 url重写:/abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递.
SpringBoot默认是禁用矩阵变量的功能 手动开启:对于路径的处理,是UrlPathHelper进行解析。removeSemicoloncontent(移除分号内容)为 false 才支持矩阵变量
手动开启方法①?
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 设置不移除 ; 分号后面的内容
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
手动开启方法②
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
}
@MatrixVariable(矩阵变量)使用举例
// /cars/sell;low=34;brand=byd,audi,yd --> {"path":"sell","low":34,"brand":["byd","audi","yd"]}
// SpringBoot默认是禁用矩阵变量的功能
// 手动开启:对于路径的处理。UrlPathHelper进行解析。removeSemicoloncontent(移除分号内容)支持矩阵变量的
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
Map<String,Object> map = new HashMap<>();
map.put("low", low);
map.put("brand", brand);
map.put("path", path);
return map;
}
// /boss/1;age=20/2;age=10 --> {"bossAge":20,"empAge":10}
// pathVar:设置是第几个参数的
@GetMapping("/boss/{bossId}/{empId}")
public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
Map<String,Object> map = new HashMap<>();
map.put("bossAge",bossAge);
map.put("empAge",empAge);
return map;
}
3.2.2、自定义对象参数自动装配
可以自动类型转换与格式化,可以级联封装。
@Data
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
@Data
public class Pet {
private String name;
private String age;
}
表单如下?
<form action="/saveuser" method="post">
姓名: <input name="userName" value="zhangsan"/> <br/>
年龄: <input name="age" value="18"/> <br/>
生日: <input name="birth" value="2019/12/10"/> <br/>
<!-- 宠物姓名:<input name="pet.name" value="阿猫"/><br/>-->
<!-- 宠物年龄:<input name="pet.age" value="5"/>-->
宠物: <input name="pet" value="啊猫,3"/>
<input type="submit" value="保存"/>
</form>
控制器:
// 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
@PostMapping("/saveuser")
public Person saveuser(Person person){
return person;
}
输出:
{"userName":"zhangsan","age":18,"birth":"2019-12-09T16:00:00.000+00:00","pet":{"name":"阿猫","age":"5"}}
自定义Converter来自动装配
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
// 使用@MatrixVariable时设置不移除分后后的内容
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
// 自定义自动装配的格式
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)) {
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
表单:
<form action="/saveuser" method="post">
姓名: <input name="userName" value="zhangsan"/> <br/>
年龄: <input name="age" value="18"/> <br/>
生日: <input name="birth" value="2019/12/10"/> <br/>
<!--宠物姓名:<input name="pet.name" value="阿猫"/><br/>
宠物年龄:<input name="pet.age" value="5"/>-->
宠物: <input name="pet" value="啊猫,3"/>
<input type="submit" value="保存"/>
</form>
4、数据响应与内容协商
?4.1、内容协商
根据客户端接收能力不同,返回不同媒体类型的数据。
4.1.1、引入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
4.1.2 开启浏览器参数方式内容协商功能
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
在 application.yaml 中开启该功能?
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商
然后在请求中添加 format 参数,例如?http://localhost:8080/test/person?format=json
5、视图解析与模板引擎
视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。
5.1、模板引擎 - Thymeleaf
5.1.1、简介
Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.
现代化、服务端Java模板引擎
5.1.2、基本语法
① 表达式
表达式名字 | 语法 | 用途 | 变量取值 | ${...} | 获取请求域、session域、对象等值 | 选择变量 | *{...} | 获取上下文对象值 | 消息 | #{...} | 取国际化等值 | 链接 | @{...} | 生成链接 | 片段表达式 | ~{...} | jsp:include 作用,引入公共页面片段 |
② 字面量
文本值: 'one text' , 'Another one!' ,… 数字: 0 , 34 , 3.0 , 12.3 ,… 布尔值: true , false 空值: null 变量: one,two,.... 变量不能有空格
③ 文本操作
字符串拼接: + 变量替换: |The name is ${name}|
④ 数学运算
运算符: + , - , * , / , %
⑤ 布尔运算
运算符: and , or 一元运算: ! , not
⑥ 比较运算
比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )
⑦ 条件运算
If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue)
⑧ 特殊操作
无操作: _
5.1.3、设置属性值? th:attr
设置单个值
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
</fieldset>
</form>
设置多个值
<img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
以上两个的代替写法 th:xxxx
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">
5.2、Thymeleaf 的使用
5.2.1、引入 Starter 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
5.2.2、在页面引入 Thymeleaf 名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
6、拦截器
6.1、HandlerInterceptor 接口
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
// 目标方法执行之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("拦截的请求路径是{}", requestURI);
// 登录检查逻辑
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if(loginUser != null){
// 已登录,放行
return true;
}
// 未登录,跳转到登录界面
request.setAttribute("msg", "请先登录");
request.getRequestDispatcher("/").forward(request, response);
return false;
}
// 目标方法执行完成以后
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle执行{}", modelAndView);
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
// 页面渲染之后
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行异常{}", ex);
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
6.2、配置拦截器
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()) //添加拦截器
.addPathPatterns("/**") //设置拦截的请求, /** 表示拦截所有请求,包括静态资源
.excludePathPatterns("/", "/login", "/css/**", "/js/**", "/fonts/**", "/images/**"); //设置放行的请求
}
}
7、文件上传
7.1、表单
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">名字</label>
<input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input type="file" name="headerImg" id="exampleInputFile">
</div>
<div class="form-group">
<label for="exampleInputFile">生活照</label>
<input type="file" name="photos" multiple>
</div>
<div class="checkbox">
<label>
<input type="checkbox"> Check me out
</label>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
7.2、代码
// MultipartFile 自动封装上传过来的文件
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
//log.info("上传的信息:email={},username={},headerImg={},photos={}",
// email, username, headerImg.getSize(), photos.length);
if(!headerImg.isEmpty()){
// 保存到文件服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("C:\\Users\\zhang\\Desktop\\1\\" + originalFilename));
}
if(photos.length > 0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("C:\\Users\\zhang\\Desktop\\1\\" + originalFilename));
}
}
}
return "main";
}
8、异常处理
8.1、默认规则
-
默认情况下,Spring Boot提供/error处理所有错误的映射 -
对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据 -
要对其进行自定义,添加View解析为error -
要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。 -
error/下的4xx,5xx页面会被自动解析
<section class="error-wrapper text-center">
<h1><img alt="" src="images/500-error.png"></h1>
<h2>OOOPS!!!</h2>
<h3 th:text="${message}">Something went wrong.</h3>
<p class="nrml-txt" th:text="${trace}">Why not try refreshing you page? Or you can <a href="#">contact our support</a> if the problem persists.</p>
<a class="back-btn" href="index.html" th:text="${status}"> Back To Home</a>
</section>
9、Web 原生组件注入(Servlet、Filter、Listener)
9.1、使用 ServletAPI
注:使用时均需要在主程序类添加? @ServletComponentScan? 注解
9.1.1、Servlet
创建自己的 Servlet?
@WebServlet(urlPatterns = "/my") //效果:直接响应,没有经过Spring的拦截器
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("6666");
}
}
在主程序类添加 @ServletComponentScan 来扫描自己的 Servlet
@ServletComponentScan(basePackages = "com.zyj.boot05webadmin") //若没设置扫描的包,默认为主程序所在的包下的所有
@SpringBootApplication
public class Boot05WebAdminApplication {
public static void main(String[] args) {
SpringApplication.run(Boot05WebAdminApplication.class, args);
}
}
9.1.2、Filter
@Slf4j
@WebFilter(urlPatterns = {"/css/*"})
public class MyFilter implements Filter { // 注意实现的是javax.servlet.Filter
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("MyFilter工作(过滤)");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
log.info("MyFilter销毁");
}
}
9.1.3、Listener
@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("MyServletContextListener监听到初始化完成");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("MyServletContextListener监听到销毁");
}
}
9.2、使用RegistrationBean
注意要保证依赖的组件始终是单实例?,所以需要设置?@Configuration(proxyBeanMethods = true)
@Configuration(proxyBeanMethods = true) //保证依赖的组件始终是单实例
public class MyRegistConfig {
@Bean
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet, "/my", "/my02");//设置拦截的请求
}
@Bean
public FilterRegistrationBean myFilter(){
MyFilter myFilter = new MyFilter();
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my", "/css/*"));
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener(){
MyServletContextListener myServletContextListener = new MyServletContextListener();
return new ServletListenerRegistrationBean(myServletContextListener);
}
}
|