Springboot提供SpringMVC的自动配置,大多场景我们都无需自定义配置
Springboot根据条件自动装配各种Java Bean到容器中,替换掉xml读取方式
内容协商视图解析器ContentNegotiatingViewResolver
和BeanName视图解析器 BeanNameViewResolver
支持静态资源(包括webjars)
自动注册 Converter,GenericConverter,Formatter
支持 HttpMessageConverters
(后来我们配合内容协商理解原理)
静态index.html 页支持
自定义 Favicon
小图标
自动使用 ConfigurableWebBindingInitializer
,DataBinder负责将请求数据绑定到JavaBean上
定制化
@Configuration
+ WebMvcConfigurer
自定义规则WebMvcRegistrations
改变默认底层组件@EnableWebMvc + @Configuration+DelegatingWebMvcConfiguration
全面接管SpringMVC1.1 静态资源目录:
只要静态资源放在类路径下:
/static
(or/public
or/resources
or/META-INF/resources
访问路径 : 当前项目根路径/ + 静态资源名(localhost:8080/temp.jpg)
原理: 静态映射是/**,即拦截所有请求。
当请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
1.2 静态资源访问前缀:
默认无前缀,/**
spring:
mvc:
static-path-pattern: /resource/**
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找(localhost:8080/resource/temp.jpg)
1.3 webjar
可用jar方式添加css,js等资源文件
https://www.webjars.org/
例如,添加jquery依赖
<dependency>
<groupId>org.webjarsgroupId>
<artifactId>jqueryartifactId>
<version>3.5.1version>
dependency>
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径
静态资源路径static下 index.html
controller能处理/index
SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
SpringMVC功能的自动配置类autoconfigure—>web—>servlet—>WebMvcAutoConfiguration
,注解条件生效
给容器中配置的内容:WebMvcAutoConfigurationAdapter(配置类)
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
...
}
有参构造器所有参数的值都会从容器中确定
...
public class WebMvcAutoConfiguration {
...
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
...
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
ServletContext servletContext = getServletContext();
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (servletContext != null) {
registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
}
});
}
...
}
...
}
spring:
resources:
add-mappings: false #禁用所有静态资源规则
静态资源规则:
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
/**
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
* /resources/, /static/, /public/].
*/
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
...
}
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
Rest原理(表单提交要使用REST的时候)
表单提交会带上\_method=PUT
请求过来被HiddenHttpMethodFilter
拦截
\_method
的值Rest使用客户端工具
a> 如PostMan可直接发送put、delete等方式请求(前后端分离)
<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 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>
@GetMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "GET-张三";
}
@PostMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "POST-张三";
}
@PutMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
return "PUT-张三";
}
@DeleteMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-张三";
}
原理分析:
org.springframework.web.servlet.DispatcherServlet
-> doDispatch()
开始getHandler()
**方法中HandlerMapping:处理器映射 /xxx->>xxxx,有5个请求映射RequestMappingHandlerMapping
**保存了所有@RequestMapping
和handler
的映射规则
RequestMappingHandlerMapping
为例:所有的请求映射都在HandlerMapping中:
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
SpringBoot自动配置了默认的 RequestMappingHandlerMapping,与@RequestMapping(“”)映射
请求进来,挨个尝试所有的HandlerMapping(5个)看是否有请求信息。
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
@ResponseBody响应报文返回值可以是String,Map,实体对象类型
@PathVariable
rest风格路径变量@GetMapping("/car/{id}/owner/{username}")
public Map<String, Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String username,
@PathVariable Map<String,String> pv){
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("username", username);
map.put("自动封装map", pv);
return map;
}
测试路径:http://localhost:8080/car/3/owner/wz
结果:{“自动封装map”:{“id”:“3”,“username”:“wz”},“id”:3,“username”:“wz”}
@RequestHeader
获取请求头信息@GetMapping("/header")
public Map<String, Object> getHeaders(@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> mp){
Map<String,Object> map = new HashMap<>();
map.put("userAgent", userAgent);
map.put("AllHeaders", mp);
return map;
}
@RequestParam
获取请求参数(指问号后的参数,url?username=wz&inters=basketball&inters=football)@RequestMapping("/Hello")
public Map<String, Object> Hello(@RequestParam("username") String name,
@RequestParam("inters") List<String> inters
@RequestParam Map<String,String> params){}
@CookieValue
获取Cookie值@GetMapping("/Cookie")
public Map<String, Object> getCookies(@CookieValue("_ga") String _ga,
@CookieValue("_ga") Cookie cookie){}
@RequestBody
获取请求体[POST]@PostMapping("/save")
public Map<String, Object> getResponseBody(@RequestBody String content){
Map<String, Object> map = new HashMap<>();
map.put("content", content);
return map;
}
结果:{"content":"username=123&email=1231"}
@RequestAttribute
获取request域中保存的键值对@RequestMapping("/goto")
public String gotoPage(Model model,
HttpServletRequest request){
model.addAttribute("bf","fkd");
request.setAttribute("gf", "wz");
return "forward:/success";
}
@ResponseBody
@RequestMapping("/success")
public Map<String, Object> success(@RequestAttribute("gf") String gf,
HttpServletRequest request){
Object bf = request.getAttribute("bf");
Map<String, Object> map = new HashMap<>();
map.put("gf", gf);
map.put("bf", bf);
return map;
}
@MatrixVariable
矩阵变量
/cars/sell;low=34;brand=byd,audi,yd
;/boss/1;age=20/2;age=22
页面开发,cookie被禁用了,session中的内容如何使用?
1.没禁用时session.set(a,b)—>jsessionId保存在—>cookie—>每次发请求都携带,服务器根据每次cookie中携带的jsessionId找到session对象,调用set方法
2.以矩阵变量的形式带上jsessionId,url重写:/abc;jsessionid=xxxx,把cookie值使用矩阵变量的方式传递
a> 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);
}
}
b> 以http://localhost:8080/cars/sell;low=34;brand=byd,audi,yd访问路径为例
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable(value = "low",pathVar = "path") Integer low,
@MatrixVariable(value = "brand",pathVar = "path") 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;
}
结果:{“path”:“sell”,“low”:34,“brand”:[“byd”,“audi”,“yd”]}
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
原理分析:
执行核心处理目标方法:invocableMethod.invokeAndHandle(webRequest,mavContainer)
执行完invokeForRequest(webRequest, mavContainer, providedArgs)后,执行自定义的目标方法controller
获取目标方法中所有参数值
遍历找到支持该参数的解析器
调用ServletRequestMethodArgumentResolver来处理以上的参数
Map
map.put()
Model model
model.addAttribute()
**HttpServletRequest request
** request.setAttribute()
三者可以给request域中放数据,用HttpServletRequest request.getAttribute()
或者@RequestAttribute
获取
ServletResponse:response响应
@RequestMapping("/goto")
public String gotoPage(Map<String, Object> map,
Model model,
HttpServletRequest request,
HttpServletResponse response){
model.addAttribute("bf","fkd");
request.setAttribute("gf", "wz");
map.put("msg", "love");
//添加cookie
response.addCookie(new Cookie("k1", "v1"));
return "forward:/success";
}
@ResponseBody
@RequestMapping("/success")
public Map<String, Object> success(@RequestAttribute("gf") String gf,
HttpServletRequest request){
Object bf = request.getAttribute("bf");
Object msg = request.getAttribute("msg");
Map<String, Object> map = new HashMap<>();
map.put("msg", msg);
map.put("gf", gf);
map.put("bf", bf);
return map;
}
原理分析:Map
与Model model
解析
执行目标方法invokeForRequest(webRequest, mavContainer, providedArgs)获取请求
解析参数,getMethodArgumentValues(request, mavContainer, providedArgs)
遍历解析器,Map
参数用MapMethodProcessor
处理,Model model
用ModelMethodProcessor
处理;
invokeForRequest中方法doInvoke(args)执行目标方法的内容
处理返回结果handleReturnValue,传入mavContainer,包含模型(数据)和视图(跳转地址)
目标方法执行完成后,会将所有的数据都放在ModelAndViewContainer,包含要去的地址View和Model数据
可以自动绑定属性和级联封装
定义实体类对象pojo:
/**
* 姓名:
* 年龄:
* 生日:
* 宠物姓名:
* 宠物年龄:
*/
@Data
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
@Data
public class Pet {
private String name;
private Integer age;
}
控制器方法:
@RestController
public class ParameterTestController {
/**
* 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
* @param person
* @return
*/
@PostMapping("/saveuser")
public Person saveuser(Person person){
return person;
}
}
输出json对象:{“userName”:“wz”,“age”:23,“birth”:“1998-12-21T16:00:00.000+00:00”,“pet”:{“name”:“dog”,“age”:2}}
封装过程用到ServletModelAttributeMethodProcessor
这个参数处理器支持
判断是否是简单类型,否
创建空Person对象attribute = createAttribute(name, parameter, binderFactory, webRequest);
利用binderFactory创建一个web数据绑定器:WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder 利用它里面的 **Converters (124个)**将请求数据转成指定的数据类型。再次封装到JavaBean中(即attribute)。
bindRequestParameters(binder, webRequest);执行该步后完成将请求参数绑定到javaBean中
得到所有请求参数的key-value对,封装成PropertyValue
底层利用反射进行赋值
在过程当中,用到GenericConversionService:在设置每一个值的时候,找它里面的所有converter哪个可以将这个数据类型(request带来参数的字符串)转换到指定的类型
未来我们可以给WebDataBinder里面放自己的Converter,自定义类型封装规则,默认浏览器读到的都是String
需求:将字符串“猫猫,3”
转换成Pet
对象
//WebMvcConfigurer定制化SpringMVC的功能
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
//规则:"猫猫,3"->Pet
//字符串非空
if (!StringUtils.hasText(source)){
String[] str = source.split(",");
Pet pet = new Pet();
pet.setName(str[0]);
pet.setAge(Integer.parseInt(str[1]));
return pet;
}
return null;
}
});
}
}
这要从DispatcherServlet
开始:
HandlerMapping
中找到能处理请求的Handler
(Controller.method())。HandlerAdapter
,用的最多的是RequestMappingHandlerAdapter
HandlerAdapter
@RequestMapping
public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = null;
...
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
//本节重点
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
HandlerAdapter
接口实现类RequestMappingHandlerAdapter
(主要用来处理@RequestMapping
)invocableMethod
中<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
--------------------------------------------------------
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jsonartifactId>
<version>2.6.7version>
<scope>compilescope>
dependency>
@Controller
public class ResponseTestController {
@ResponseBody //利用返回值处理器里面的消息转换器进行处理
@GetMapping(value = "/test/person")
public Person getPerson(){
Person person = new Person();
person.setAge(28);
person.setBirth(new Date());
person.setUserName("zhangsan");
return person;
}
}
原理:返回值解析器ReturnValueHandler(15个)
调用returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest)
返回值处理器,来处理返回值
返回值处理器判断是否支持这种类型返回值supportsReturnType
返回值处理器调用handleReturnValue
处理
调用RequestResponseBodyMethodProcessor处理器,可以处理返回值标了@ResponseBody 的注解
利用消息转换器MessageConverters
,进行writeWithMessageConverters
处理,将数据写为json
- 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,Json数据
- SpringMVC会挨个遍历所有容器底层的 `HttpMessageConverter` ,看谁能处理?
- 得到`MappingJackson2HttpMessageConverter`可以将对象写为json
- 利用`MappingJackson2HttpMessageConverter`将对象转为json再写出去。
SpringMVC支持返回值:
- ModelAndView
- Model
- View
- ResponseEntity
- ResponseBodyEmitter
- StreamingResponseBody
- HttpEntity
- HttpHeaders
- Callable
- DeferredResult
- ListenableFuture
- CompletionStage
- WebAsyncTask
- 有 @ModelAttribute 且为对象类型的
- @ResponseBody 注解 ---> RequestResponseBodyMethodProcessor
HttpMessageConverter
: 看是否支持将此 Class
类型的对象,转为MediaType
类型的数据。例子:
Person
对象转为JSON,或者 JSON转为Person
,这将用到MappingJackson2HttpMessageConverter
默认MessageConverter:
0 - 只支持Byte类型的
1 - String,支持字符集utf-8
2 - String,支持byte[]数组类型,支持字符集ISO-8859-1
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true , ==任何类型都支持(导入jackson才有)==
8 - true,任何类型都支持
9 - 支持注解方式xml处理的
根据客户端(比如浏览器)接收能力不同,返回不同媒体类型的数据(json,xml…)
举例:返回xml数据类型
<dependency>
<groupId>com.fasterxml.jackson.dataformatgroupId>
<artifactId>jackson-dataformat-xmlartifactId>
dependency>
可用Postman软件分别测试返回json和xml:
只需要改变请求头中Accept字段(application/json、application/xml)
Http协议中规定的,Accept字段告诉服务器本客户端可以接收的数据类型
判断当前响应头中是否已经有确定的媒体类型MediaType
。即要返回的数据类型。
获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)
a> contentNegotiationManager
内容协商管理器 默认使用基于请求头的策略
HeaderContentNegotiationStrategy
确定客户端可以接收的内容类型MessageConverter
,看谁支持操作这个对象(Person)找到支持操作Person的converter,把converter支持的媒体类型统计出来
客户端需要application/xml,服务端能够产出10种MediaType
进行内容协商的最佳匹配媒体类型
调用支持将对象转为最佳匹配媒体类型的converter(即将Person类型转化为xml类型的converter)进行转化
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
然后,浏览器地址输入带format参数的URL:
这样,后端会根据参数format的值,返回对应json或xml格式的数据。
原理:
此时由Spring容器注入了一个
ParameterContentNegotiationStrategy
到内容协商管理器中,参数策略优先确定,获取参数format的值
@ResponseBody
响应数据出去 调用 RequestResponseBodyMethodProcessor
处理MessageConverter
处理MessageConverter
合起来可以支持各种媒体类型数据的操作(读、写)messageConverter
1、浏览器发请求直接返回 xml [application/xml] jacksonXmlConverter
2、如果是ajax请求返回 json [application/json] jacksonJsonConverter
3、如果硅谷app发请求,返回自定义协议数据 [appliaction/x-guigu] xxxxConverter
属性值1;属性值2;
1、添加自定义的MessageConverter进系统底层
2、系统底层就会统计出所有MessageConverter能操作哪些类型
3、客户端内容协商 [guigu--->guigu]
WebMvcConfigurer
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiguMessageConverter());
}
}
/**
* 自定义的Converter
*/
public class GuiguMessageConverter implements HttpMessageConverter<Person> {
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(Person.class);
}
/**
* 服务器要统计所有MessageConverter都能写出哪些内容类型
*
* application/x-guigu
* @return
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-guigu");
}
@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//自定义协议数据的写出
String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();
//写出去
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
}
/person
(请求头Accept:application/x-guigu
),将返回自定义协议数据的写出。基于自定义请求参数的自定义内容协商功能
需求:在地址栏输入http://localhost:8080/person?format=atguigu
返回数据,跟http://localhost:8080/person
且请求头参数Accept:application/x-guigu
的返回自定义协议数据的一致
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//Map mediaTypes
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
//自定义媒体类型
mediaTypes.put("atguigu",MediaType.parseMediaType("application/x-guigu"));
//指定支持解析哪些参数对应的哪些媒体类型
ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
//还需添加请求头处理策略,否则accept:application/json、application/xml则会失效
HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterStrategy, headeStrategy));
}
}
日后开发要注意,有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效
视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染
thymeleaf:服务端Java模板引擎,在页面没有经过服务端渲染或者无法获取数据的情况下,会保持标签的默认使用
视图解析原理流程:
目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer
里面,其中包括数据和视图地址
a>: 方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
。
任何目标方法执行完成以后都会返回ModelAndView
(数据和视图地址)。
processDispatchResult()
处理派发结果(页面改如何响应)
a> render(mv, request, response);
进行页面渲染逻辑
根据方法的`String`返回值得到 `View` 对象【==定义了页面的渲染逻辑==】
(1) 遍历所有的视图解析器尝试是否能根据当前返回值内容协商得到View
对象
(2) ContentNegotiationViewResolver
里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象
(3) 得到了redirect:/mapMain
--> 调用ThymeleafViewController解析器 new RedirectView()
(4) view.render(mv.getModelInternal(), request, response);
视图对象view调用自定义的render进行页面渲染工作
RedirectView
如何渲染【重定向到一个页面】
response.sendRedirect(encodedURL);
视图解析:
forward:
开始: new InternalResourceView(forwardUrl);
--> 转发request.getRequestDispatcher(path).forward(request, response);
redirect:
开始: new RedirectView()
--> render就是重定向new ThymeleafView()
表达式名字 | 语法 | 用途 |
---|---|---|
变量取值 | ${…} | 获取请求域request{user.name}、session域${session.user.name}、对象等值 |
选择变量 | *{…} | 获取上下文对象值 |
消息 | #{…} | 获取国际化等值 |
链接 | @{…} | 生成链接 |
片段表达式 | ~{…} | 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)
特殊操作
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onionstd>
<td th:text="${prod.price}">2.41td>
<td th:text="${prod.inStock}? #{true} : #{false}">yestd>
tr>
<tr th:each="prod,status : ${prods}">
<td th:text="${prod.name}">Onionstd>
<td th:text="${prod.price}">2.41td>
<td th:text="${status.count}">yestd>
tr>
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">viewa>
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administratorp>
<p th:case="#{roles.manager}">User is a managerp>
<p th:case="*">User is some other thingp>
div>
thymeleaf内联写法:
<p>Hello, [[${session.user.name}]]p>
thymeleaf只会针对绝对路径自动拼接项目路径,相对路径不会做拼接操作
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {
...
}
自动配好的策略
public static final String DEFAULT_PREFIX = "classpath:/templates/";//模板放置处
public static final String DEFAULT_SUFFIX = ".html";//文件的后缀名
@RequestMapping("/hello1")
public String Hello(Model model){
model.addAttribute("msg", "wzz");
model.addAttribute("location","http://wwww.baidu.com");
return "success";
}
/templates/success.html
:DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1 th:text="${msg}">h1>
<h2>
<a th:href="${location}">百度a>
<a th:href="@{/Hello}">Helloa>
h2>
body>
html>
/Hello:设置的是绝对路径,即 http://localhost:8080/Hello
server:
servlet:
context-path: /app
这个设置后,URL要插入/app
, 如http://localhost:8080/app/hello.html
。
使用IDEA的Spring Initializr:
导入thymeleaf、web-starter、devtools、lombok依赖
已经自动配置了处理器,我们只需要把所有静态资源放到 static 文件夹下
/static
放置 css,js等静态资源/templates/login.html
登录页/templates/main.html
主页th:action="@{/login}”
抽取左侧和上方导航栏common.html
th:fragment="xxx"声明公共部分
引用:
th:include; th:replace; th:insert
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="commonheader">
<link href="css/style.css" th:href="@{css/style.css}" rel="stylesheet">
head>
<body>
<div th:fragment="leftmenu" class="left-side sticky-left-side"...>div>
<div th:fragment="headermenu" class="header-section".../>
<div th:fragment="commonscript">
<script th:src="@{js/jquery-1.10.2.min.js}">script>
<script th:src="@{js/jquery-ui-1.9.2.custom.min.js}">script>
<script th:src="@{js/jquery-migrate-1.2.1.min.js}">script>
<script th:src="@{js/bootstrap.min.js}">script>
<script th:src="@{js/modernizr.min.js}">script>
<script th:src="@{js/jquery.nicescroll.js}">script>
<script th:src="@{js/scripts.js}">script>
div>
body>
html>
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<meta name="description" content="">
<meta name="author" content="ThemeBucket">
<link rel="shortcut icon" href="#" type="image/png">
<title>Basic Tabletitle>
<link th:replace="table/common :: commonheader">
head>
<body class="sticky-header">
<section>
<div th:replace="table/common :: leftmenu">div>
<div class="main-content".../>
section>
<div th:replace="table/common :: commonscript">div>
body>
html>
官方文档 - Template Layout
@Controller
public class IndexController {
/**
* 登录页
* @return
*/
@RequestMapping(value = {"/","/login"})
public String loginPage(){
return "login";
}
@PostMapping("/login")
public String main(User user, HttpSession session, Model model){
if (StringUtils.hasText(user.getUserName()) && user.getPassword().equals("123456")){
//把登录成功的用户保存起来
session.setAttribute("loginUser", user);
//登录成功重定向到mapMain,重定向防止表单重复提交
return "redirect:/mapMain";
}else{
//存入错误信息
model.addAttribute("msg", "账号或密码错误");
return "/login";
}
}
@GetMapping("mapMain")
public String mainPage(HttpSession session,Model model){
//是否登录? 拦截器、过滤器
Object loginUser = session.getAttribute("loginUser");
if (loginUser != null){
return "main";
}else {
model.addAttribute("msg", "请重新登录");
return "/login";
}
}
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private String userName;
private String password;
}
遍历输出表格数据
@GetMapping("/dynamic_table")
public String dynamic_table(Model model){
List<User> users = Arrays.asList(new User("fkd", "123456"),
new User("wzz", "123456"),
new User("lzj", "123456"),
new User("WL", "123456"),
new User("yjw", "123456"));
model.addAttribute("users",users);
return "table/dynamic_table";
}
<table class="display table table-bordered" id="hidden-table-info">
<thead>
<tr>
<th>#th>
<th>用户名th>
<th>密码th>
tr>
thead>
<tbody>
<tr class="gradeX" th:each="user,status: ${users}">
<td th:text="${status.count}">td>
<td>[[${user.userName}]]td>
<td>[[${user.password}]]td>
tr>
tbody>
table>
登录检查与静态资源放行,要实现
HandlerInterceptor
接口
HandlerInterceptor
接口@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 在目标方法执行之前
* @param request
* @param response
* @param handler
* @return true:放行 false:拦截
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
log.info("preHandle拦截的请求路径是{}", uri);
//登录检查逻辑
HttpSession session = request.getSession();
Object user = session.getAttribute("loginUser");
if (user != null){
//放行
return true;
}
request.setAttribute("msg","请重新登录");
//未登录,拦截住,跳转到登录页面
//response.sendRedirect("/");
request.getRequestDispatcher("/").forward(request,response);
return false;
}
/**
* 目标方法执行之后
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
/**
* 页面渲染完成以后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
拦截器注册到容器中(实现WebMvcConfigurer
的addInterceptors()
),
并制定拦截规则(注意,如果是拦截所有,静态资源也会被拦截)
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器注册到容器中.拦截所有请求.放行的请求
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");
}
}
拦截器同filter执行顺序差不多,顺序进来,倒序出去
根据当前请求,找到HandlerExecutionChain
(可以处理请求的handler以及handler的所有拦截器链)
先按顺序执行所有拦截器的 preHandle()
方法。
如果当前拦截器preHandle()
返回为true
。则执行下一个拦截器的preHandle()
如果当前拦截器返回为false
。直接从当前拦截器倒序执行所有已经执行了的拦截器的 afterCompletion();
。
如果任何一个拦截器返回false
,直接跳出不执行目标方法。
所有拦截器都返回true
,才执行目标方法。
倒序执行所有拦截器的postHandle()
方法。
(都执行)前面的步骤有任何异常都会直接从当前拦截器倒序触发 afterCompletion()
;页面成功渲染完成以后,也会倒序触发 afterCompletion()
。
单文件:input type=“file” name=“headerImg” id=“exampleInputFile”>
多文件:input type=“file” name=“photos” multiple>
表单提交路径:form role=“form” th:action=“@{/upload}” method=“post” enctype=“multipart/form-data”>
/static/form/form_layouts.html
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱label>
<input type="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="username">
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>
/**
* MultipartFile 自动封装上传过来的文件
* @param username
* @param email
* @return
*/
@PostMapping("/upload")
public String upload(@RequestParam("username") String username,
@RequestParam("email") String email,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
//保存文件到文件服务器,OSS服务器
if (!headerImg.isEmpty()){
String filename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("D:\\cache\\header\\"+filename));
}
if (photos.length > 0){
for(MultipartFile photo: photos){
String filename = photo.getOriginalFilename();
photo.transferTo(new File("D:\\cache\\photos\\"+filename));
}
}
return "main";
}
spring:
servlet:
multipart:
max-request-size: 32MB
max-file-size: 4MB
文件上传自动配置类—MultipartAutoConfiguration
—MultipartProperties
StandardServletMultipartResolver
multipartResolver.isMultipart
),并封装(调用resolveMultipart
方法,返回MultipartHttpServletRequest
类型)文件上传请求requestPartMethodArgumentResolver
来解析请求中的文件内容封装成MultipartFileMultiValueMap FileCopyUtils
。内部使用FileCopyUtiles实现文件流的拷贝Spring Boot官方文档 - Error Handling
默认规则:
默认情况下,Spring Boot提供/error
处理所有错误的映射
{
"timestamp": "2020-11-22T05:53:28.416+00:00",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/asadada"
}
要对其进行自定义,添加View
解析为error
要完全替换默认行为,可以实现 ErrorController
并注册该类型的Bean定义,或添加ErrorAttributes类型的组件
以使用现有机制但替换其内容。
/templates/error/
或/public/error/
下的4xx,5xx页面会被自动解析;
@ControllerAdvice+@ExceptionHandler
处理全局异常;底层是 ExceptionHandlerExceptionResolver支持的@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 定制要处理的异常逻辑
* @param e
* @return
*/
@ExceptionHandler({ArithmeticException.class,NullPointerException.class})
public String handleException(Exception e){
log.info("异常是{}",e);
return "login";
}
}
@ResponseStatus
+自定义异常 ;
ResponseStatusExceptionResolver
,把responseStatus注解的信息底层调用 response.sendError(statusCode, resolvedReason)
,tomcat发送的/error
package com.zju.admin.exception;
/**
* 自定义异常处理
*/
@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "用户数量太多")
public class UserTooManyException extends RuntimeException{
public UserTooManyException() {
}
public UserTooManyException(String message) {
super(message);
}
}
@GetMapping("/dynamic_table")
public String dynamic_table(Model model){
List<User> users = Arrays.asList(new User("fkd", "123456"),
new User("wzz", "123456"),
new User("lzj", "123456"),
new User("WL", "123456"),
new User("yjw", "123456"));
if (users.size()>3){
//抛出自定义异常
throw new UserTooManyException();
}
model.addAttribute("users",users);
return "table/dynamic_table";
}
Spring框架底层的异常,如参数类型转换异常;由DefaultHandlerExceptionResolver 处理框架底层的异常。
response.sendError(HttpServletResponse.SC_BAD_REQUEST/*400*/, ex.getMessage());
HandlerExceptionResolver
处理异常;可以作为默认的全局异常处理规则@Order(value= Ordered.HIGHEST_PRECEDENCE) //优先级,数字越小优先级越高
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
try {
response.sendError(511,"我喜欢的错误");
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
ErrorMvcAutoConfiguration 自动配置异常处理规则:
容器中的组件:类型:DefaultErrorAttributes
; id:errorAttributes
(自定义错误信息内容)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
DefaultErrorAttributes
:定义错误页面中可以包含数据(异常明细,堆栈信息等)。容器中的组件:类型:BasicErrorController
;id:basicErrorController
(自定义json+白页,适配响应)
处理默认 /error
路径的请求,页面响应 new ModelAndView("error", model);
容器中有组件 View
->id是error;(响应默认错误页)
容器中放组件 BeanNameViewResolver
(视图解析器);按照返回的视图名作为组件的id去容器中找View
对象。
容器中的组件:类型:DefaultErrorViewResolver
;id:conventionErrorViewResolver
(自定义错误页面访问路径)
如果想要返回页面,就会找error视图(StaticView
默认是一个白页)
1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException
2、进入视图解析流程(页面渲染?)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;
遍历所有的 handlerExceptionResolvers,看谁能处理当前异常**【HandlerExceptionResolver处理器异常解析器】**
系统默认的异常解析器;
DefaultErrorAttributes先来处理异常。把异常信息保存到request域,并且返回null;
默认没有任何人能处理异常,所以异常会被抛出
web原生组件注入:Servlet、Filter、Listener
官方文档 - Servlets, Filters, and listeners
在主启动类添加注解@ServletComponentScan("com.zju.admin")
来发现注册的组件
@WebServlet(urlPatterns = “/my”):效果:直接响应,没有经过Spring拦截器?
根据精确优先原则,DispatcherServlet处理"/“请求,MyServlet处理”/my"请求,更精确,所以由原生的servlet(Tomcat处理),而只有由DispatcherServlet(Spring)处理的请求才会经过spring的拦截器。效率不同。
@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("66666");
}
}
@Slf4j
@WebFilter(urlPatterns={"/css/*","/images/*"}) //servlet写法是/*
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter初始化完成");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("MyFilter工作");
chain.doFilter(request,response);
}
@Override
public void destroy() {
log.info("MyFilter销毁");
}
}
@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("MySwervletContextListener监听到项目初始化完成");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("MySwervletContextListener监听到项目销毁");
}
}
ServletRegistrationBean
, FilterRegistrationBean
, and ServletListenerRegistrationBean
@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(){
MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
return new ServletListenerRegistrationBean(mySwervletContextListener);
}
}
DispatcherServlet
默认映射的是 /
路径,可以通过在配置文件修改spring.mvc.servlet.path=/mvc
默认支持的WebServer
Tomcat
, Jetty
, or Undertow
ServletWebServerApplicationContext
容器启动寻找ServletWebServerFactory
并引导创建服务器Spring Boot默认使用Tomcat服务器,若需更改其他服务器,则修改工程pom.xml:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jettyartifactId>
dependency>
修改配置文件: server.xxx
实现WebServerFactoryCustomizer
ServletWebServerFactory
进行绑定直接自定义 ConfigurableServletWebServerFactory
xxxConfiguration
+ @Bean
替换、增加容器中默认组件,视图解析器
WebMvcConfigurer
即可定制化web功能 + @Bean
给容器中再扩展一些组件@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry) {...}
}s
xxxxxCustomizer
@EnableWebMvc
+ WebMvcConfigurer
— @Bean
可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能(高级功能,初学者退避三舍)。
原理:
WebMvcAutoConfiguration
默认的SpringMVC的自动配置功能类,如静态资源、欢迎页等。@EnableWebMvc
,会@Import(DelegatingWebMvcConfiguration.class)
。DelegatingWebMvcConfiguration
的作用,只保证SpringMVC最基本的使用
WebMvcConfigurer
拿过来,所有功能的定制都是这些WebMvcConfigurer
合起来一起生效。RequestMappingHandlerMapping
,这些组件依赖的组件都是从容器中获取如。public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
。WebMvcAutoConfiguration
里面的配置要能生效必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
。场景starter – **xxxxAutoConfiguration
– 导入xxx组件(@Bean) – 绑定xxxProperties
**-- 绑定配置文件项