在SpringMVC场景中,并不会自动给我们返回Json字符串的也没有Json字符串的过滤器,在SpringBoot中
只要我们使用了@ResponseBody (就会利用返回值处理器里面的消息转换器进行处理)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
web场景自动引入了json场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
package com.jsxs.controller;
import com.jsxs.bean.Person;
import com.jsxs.bean.Pet;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* @Author Jsxs
* @Date 2023/7/6 10:16
* @PackageName:com.jsxs.controller
* @ClassName: ResponseTestController
* @Description: TODO
* @Version 1.0
*/
@Controller
@ResponseBody
public class ResponseTestController {
@GetMapping("/test/person")
public Person person() throws ParseException {
Person person = new Person("jsxs", 12, new SimpleDateFormat("yyyy-MM-dd").parse("2012-02-01"), new Pet("哈吉米", 2));
return person;
}
}
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
RequestResponseBodyMethodProcessor 类。
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// 使用消息转换器进行调用
// Try even with null return value. ResponseBodyAdvice could get involved.
⭐ writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
RequestResponseBodyMethodProcessor
可以处理返回值标了@ResponseBody
注解的。这里对应着15种返回值解析器
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:Person对象转为JSON。或者 JSON转为Person
0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true (不管是谁直接为true,也就是说不管是啥文件直接接受)
8 - true (不管是谁直接为true,也就是说不管是啥文件直接接受)
9 - 支持注解方式 xml处理的。
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
·AbstractGenericHttpMessageConverter类种的·
outputMessage.getBody().flush();
根据客户端接收能力不同,返回不同媒体类型的数据。
<dependency>
<groupId>com.fasterxml.jackson.dataformatgroupId>
<artifactId>jackson-dataformat-xmlartifactId>
dependency>
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
为了方便内容协商,开启基于请求参数的内容协商功能。
spring:
mvc:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
通过点开源码我们发现我们需要什么样的类型,我们只需要在路径后面添加上 format='xxx'
格式即可。前提是我们需要什么类型的时候要有依赖在 pom.xml 中。比如我们需要使用xml的,我们要有上面的那个 jackson-dataformat-xml
依赖。
获取json: http://localhost:8080/test/person?format=json
获取xml格式: http://localhost:8080/test/person?format=xml
源码中发现相比于以前的请求头的内容协商多了一个参数的请求协商。
确定客户端接收什么样的内容类型;
1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值
)
Accept头(浏览器)
。通过 contentNegotiationManager 内容协商管理器
默认使用基于请求头的策略服务器方
)为什么说引入xml的转环包就会被底层接受的原理
WebMvcConfigurationSupport 类下
917行 jackson2XmlPresent
实现多协议数据兼容。json、xml、x-guigu
0、@ResponseBody
响应数据出去 调用 RequestResponseBodyMethodProcessor
处理
1、Processor 处理方法返回值。通过 MessageConverter 处理
2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
3、内容协商找到最终的 messageConverter;
package com.jsxs.controller;
import com.jsxs.bean.Person;
import com.jsxs.bean.Pet;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* @Author Jsxs
* @Date 2023/7/6 10:16
* @PackageName:com.jsxs.controller
* @ClassName: ResponseTestController
* @Description: TODO
* @Version 1.0
*/
@Controller
@ResponseBody
public class ResponseTestController {
/**
*
* @return
* @throws ParseException
*
* @TODO: 1.浏览器请求返回xml文件。2.ajax请求返回json文件。3.硅谷app请求返回自定义文件
* 在以前一个请求完成这项工作这是不可能完成的任务,但是在现在我们有了内容协商我们可以完成这个任务。
* 步骤: 1.添加自定义的MessageConverter进入系统底层。2.系统底层就会统计出所有MessageConverter
* 3.进行内容客户端与服务器内容协商匹配。
*/
@GetMapping("/test/person")
public Person person() throws ParseException {
Person person = new Person("jsxs", 12, new SimpleDateFormat("yyyy-MM-dd").parse("2012-02-01"), new Pet("哈吉米", 2));
return person;
}
}
SpringMVC的什么功能。一个入口给容器中添加一个 WebMvcConfigurer
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
}
}
首先配置协议转换器
package com.jsxs.convert;
import com.jsxs.bean.Person;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/7/7 12:38
* @PackageName:com.jsxs.convert
* @ClassName: GguiGuMessageConverter
* @Description: TODO 自定义的消息转换器
* @Version 1.0
*/
public class GuiGuMessageConverter implements HttpMessageConverter<Person> {
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
/**
* 条件是什么
* @param clazz
* @param mediaType
* @return
*/
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(Person.class); //只有返回的类型是Person类行就能进行读写
}
/**
* 服务器要统计所有的MessageConverter 都能写哪些内容类型
*
* application/x-jsxs
* @return
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/x-jsxs");
}
@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.getAge()+";"+person.getPet();
// 写出去
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
}
package com.jsxs.config;
import com.jsxs.bean.Pet;
import com.jsxs.convert.GuiGuMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/7/3 11:13
* @PackageName:com.jsxs.config
* @ClassName: WebConfig
* @Description: TODO
* @Version 1.0
*/
@Configuration(proxyBeanMethods = false)
// 第一种方式 @Configuration + 实现WebMvcConfigurer接口 (因为JDK8允许接口的默认方法和默认实现所以我们不需要将所有方法全部重写)
// 第二种方式: @Configuration +@Bean 重新注入我们的组件
public class WebConfig /*implements WebMvcConfigurer */{
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("aaaa");
return hiddenHttpMethodFilter;
}
// @Override
// public void configurePathMatch(PathMatchConfigurer configurer) {
// UrlPathHelper helper = new UrlPathHelper();
// helper.setRemoveSemicolonContent(false);
// configurer.setUrlPathHelper(helper);
// }
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer(){
// 配置支持我们的矩阵注解
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper helper = new UrlPathHelper();
helper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(helper);
}
// 配置支持我们的自定义converter转换器
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) { //source 就是页面提交过来的值。只获得过来的值
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;
}
});
}
// 扩展 内容消息转换器
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiGuMessageConverter());
}
};
}
}
上面的内容可以通过 PostMan 进行处理。浏览器因为我们自己设置不了请求头,所以目前测试不了我们自定义的内容协商。
我们通过debug的方式进入到了我们浏览器接受的内容协议上,并查看到有两种接收方式,并在请求参数的方式上没有看到 自定义的格式,所以我们要进行自定义的操作。
因为只兼容上面两种 所以我们要进行配置内容协商功能
package com.jsxs.config;
import com.jsxs.bean.Pet;
import com.jsxs.convert.GuiGuMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;
import java.util.*;
/**
* @Author Jsxs
* @Date 2023/7/3 11:13
* @PackageName:com.jsxs.config
* @ClassName: WebConfig
* @Description: TODO
* @Version 1.0
*/
@Configuration(proxyBeanMethods = false)
// 第一种方式 @Configuration + 实现WebMvcConfigurer接口 (因为JDK8允许接口的默认方法和默认实现所以我们不需要将所有方法全部重写)
// 第二种方式: @Configuration +@Bean 重新注入我们的组件
public class WebConfig /*implements WebMvcConfigurer */{
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("aaaa");
return hiddenHttpMethodFilter;
}
// @Override
// public void configurePathMatch(PathMatchConfigurer configurer) {
// UrlPathHelper helper = new UrlPathHelper();
// helper.setRemoveSemicolonContent(false);
// configurer.setUrlPathHelper(helper);
// }
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer(){
// 配置支持我们的矩阵注解
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper helper = new UrlPathHelper();
helper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(helper);
}
// 配置支持我们的自定义converter转换器
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) { //source 就是页面提交过来的值。只获得过来的值
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;
}
});
}
// 扩展 内容消息转换器
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiGuMessageConverter());
}
// 自定义(重写)内容协商 ⭐⭐⭐
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// 要求需要为String
HashMap<String, MediaType> mediaTypeHashMap = new HashMap<>();
// 配置支持的请求参数
mediaTypeHashMap.put("json",MediaType.APPLICATION_JSON);
mediaTypeHashMap.put("xml",MediaType.APPLICATION_XML);
mediaTypeHashMap.put("jsxs",MediaType.parseMediaType("application/x-jsxs"));
// 支持解析哪些参数对应的哪些媒体类型 -》 参数内容协商支持
ParameterContentNegotiationStrategy parameterContentNegotiationStrategy = new ParameterContentNegotiationStrategy(mediaTypeHashMap);
// 支持解析哪些参数对应的哪些媒体类型 -》 请求头内容协商支持 (这里通过PostMan进行测试)
HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy();
// 真正执行
configurer.strategies(Arrays.asList(parameterContentNegotiationStrategy,headerContentNegotiationStrategy));
}
};
}
}
有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。
大家考虑,上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?【提示:参照SpringBoot官方文档web开发内容协商章节】
视图解析:SpringBoot默认不支持 JSP
,需要引入第三方模板引擎技术实现页面渲染。
1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
2、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
3、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
4、processDispatchResult
处理派发结果(页面该如何响应)
DisplatchServlet的 第1078行
render(mv, request, response)
; 进行页面渲染逻辑1、根据方法的String返回值
得到 View 对象【定义了页面的渲染逻辑】
view.render(mv.getModelInternal(), request, response); 1393行
视图解析:
文本值: ‘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)
无操作: _
设置单个值
<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}">
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes
行内写法
1. 假如要写的内容不在标签中而在行内,那么就用这个方式。(非session)
<h1>[[${xxx}]]h1>
2. 假如是取Session的值
<h1>[[$session.name.xxx}]]h1>
<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,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onionstd>
<td th:text="${prod.price}">2.41td>
<td th:text="${prod.inStock}? #{true} : #{false}">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>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
ThymeleafAutoConfiguration 类
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { }
自动配好的策略
ThymeleafProperties 类
SpringTemplateEngine
ThymeleafViewResolver
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>Successh1>
<h1 th:text="${A}">h1>
<a th:href="${baidu}">点击我去金橘社区 ${baidu}a>
<br>
<br>
<a th:href="@{baidu}">点击我去金橘社区 @{baidu}a>
<br>
<br>
<a th:href="@{/baidu}">点击我去金橘社区 @{/baidu}a>
body>
html>
package com.jsxs.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @Author Jsxs
* @Date 2023/7/7 17:30
* @PackageName:com.jsxs.controller
* @ClassName: ViewTestController
* @Description: TODO
* @Version 1.0
*/
@Controller
public class ViewTestController {
@GetMapping("/toTest")
public String toTest(Model model){
// model 会自动放入到请求域中和HttpRequest是一样的,只能接受一次请求的操作
model.addAttribute("A","a");
model.addAttribute("baidu","https://www.jsxs1.cn");
return "success"; // 假如说没有模板解析器的话,这里的路径会报黄。
}
}
# 给整个服务器添加前缀
server:
servlet:
context-path: /jsxs
1. 假如在template中再新建包的话,我们只需要在 controller 的返回值中添加上新建包路径即可 /新建包名/xxx。
2. controller 页面跳转的实质是转发;不是重定向。
3. return: 的值会默认拼接 templates/xxxx.html; return forward: return redirect 找的都是请求的路径不是页面。
4. 静态资源只要放在四大区域就行,前端调用的时候可以省略掉前面的四大区域路径只写相对路径即可。
5. 抽取公共模板(第一种)
(1). 公共页(top.html): 在标签中设置 th:fragment="AAAA"
eg: <div th:fragment="AAAA"></div>
(2). 使用公共页方: th:insert="~{公共页的HTML名字 :: AAAA}"
eg: 1. <div th:insert="~{top :: AAAA}"></div>
或 2.<div th:insert="top :: AAAA"></div>
或 3.<div th:replace="~{top :: utopbar}"></div>
或 4.<div th:include="~{top :: topbar}"></div>
假如说公共页面和被添加公共页面不再同一个包中那么就要加上路径指定在哪
eg:<div th:include="~{commons/top :: topbar}"></div>
6.抽取公共模板(第二种 ->选择器方式)
(1). 公共页(top.html): 在标签中设置 id="BBB"
(2). 使用方: <div th:replace="top :: #BBB"> (当然以上的几种方法都适用)
公共方:
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
使用公共方
<body>
...
会带上div(全部都要)
<div th:insert="~{footer :: copy}"></div>
引入的东西不会在div里面
<div th:replace="~{footer :: copy}"></div>
引入的中西会在div里面
<div th:include="~{footer :: copy}"></div>
</body>
实际效果:
假如引入的是css、js。也是一样的只不过把div改为link或script
<body>
...
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
2.replace->引入的东西会在link标签里面内嵌link标签
<link th:replace="~{}">
3.include->引入的东西会在link标签里面内嵌link标签
<link th:include="~{}">
<div th:replace="~{}"></div>
SpringMvc的拦截器配置
1.Filter: 是Servlet定义的原生组件。好处:脱离Spring也能够使用
2.Interceptor: 是Spring定义的接口。可以使用Spring的自动装配功能
第一种拦截机制
package com.jsxs.servlet;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @Author Jsxs
* @Date 2023/7/8 17:58
* @PackageName:com.jsxs.servlet
* @ClassName: MyFilter
* @Description: TODO 配置过滤器
* @Version 1.0
*/
@Slf4j
//@WebFilter(urlPatterns = {"/css/*","/imag/*"}) //设置拦截我们CSS和imag的所有文件
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初始化销毁.....");
}
}
第二种拦截机制
在这里进行拦截的操作
package com.jsxs.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 登入成功之后存在登入后的session
if (request.getSession().getAttribute("LoginUser")!=null){
return true;
}else {
request.setAttribute("msg","没有权限请先进行登入");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
在这里设定规则
/**
* 1、编写一个拦截器实现HandlerInterceptor接口
* 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
* 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
*/
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
}
}
拦截器原理:
顺序
执行所有拦截器的 preHandle
方法true(通行)
。则执行下一个拦截器的preHandlefalse(不通行)
。直接 倒序
执行所有已经执行了的拦截器的 afterCompletion;直接跳出不执行目标方法
所有拦截器都返回True。执行目标方法
倒序执行
所有拦截器的postHandle方法。afterCompletion
1. 需要指定 enctype
2. 单个文件上传 什么也不加
3. 多个文件上传 需要加上: multiple 属性
<form method="post" action="/upload" enctype="multipart/form-data">
单文件 <input type="file" name="file"><br>
多文件 <input type="file" name="file" multiple><br>
<input type="submit" value="提交">
form>
1.一定要加上参数注解 @RequestPart
/**
* MultipartFile 自动封装上传过来的文件,以后文件上传用这个类型和注解
* @param email
* @param username
* @param headerImg
* @param photos
* @return
*/
@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()){
//保存到文件服务器,OSS服务器
String originalFilename = headerImg.getOriginalFilename(); //获取上传的文件名
headerImg.transferTo(new File("H:\\cache\\"+originalFilename)); //这个方法直接传输了,内部封装了InputStrean和OutStream流(需要指定路径+文件名即可)
}
if(photos.length > 0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("H:\\cache\\"+originalFilename)); //遍历传输各个文件
}
}
}
return "main";
}
原理
MultipartAutoConfiguration 类
MultipartProperties 类
- 自动配置原理
文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties
StandardServletMultipartResolver
【文件上传解析器】isMultipart
)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求`Map
;MultiValueMap/error
处理所有错误的映射静态资源或templates目录下放 error/下的4xx,5xx页面会被自动解析
有精确的错误状态码页面就匹配精确,没有就找 4xx.htm
l;如果都没有就触发白页在自定义的4xx页面或者5xx页面的某个提示标签中可以这么写
th:text="${status}" ->在配置的错误页面中会获得返回的状态码
th:text="${message}" ->在配置的错误页面中会获得返回的信息
@ControllerAdvice+@ExceptionHandler
处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的 (第二种⭐)先定义: 编写有参构造和无参构造,并不是继承父类的方法
ErrorMvcAutoConfiguration
自动配置异常处理规则DefaultErrorAttributes
-> id:errorAttributes容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)
1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException
2、进入视图解析流程(页面渲染?)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;
- 系统默认的 异常解析器;
首先要在主类中设置需要扫描的servlet包在哪。
1. @ServletComponentScan(basePackages = "com.atguigu.admin") :指定原生Servlet组件都放在那里,默认是扫描主类所在包的子包
然后我们在servlet包中配置我们的原生servlet
2. @WebServlet(urlPatterns = "/my"):效果:直接响应,没有经过Spring的拦截器?
3. @WebFilter(urlPatterns={"/css/*","/images/*"})
4. @WebListener
扩展:DispatchServlet 如何注册进来
Tomcat-Servlet;
多个Servlet都能处理到同一层路径,精确优选原则
A: /my/ ->(假如说这个路径可以处理my/下的所有路径)
B: /my/1 -> (假如说这个路径可以处理my/1这个准确路径)
主类
package com.jsxs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan
public class SpringBootLs02Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootLs02Application.class, args);
}
}
servlet
package com.jsxs.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author Jsxs
* @Date 2023/7/8 17:24
* @PackageName:com.jsxs.servlet
* @ClassName: MyServlet
* @Description: TODO 1.添加上注解. 2.重写get和post方法
* @Version 1.0
*/
@WebServlet(urlPatterns = "/my") //标注是原生的API,并指定拦截路径
public class MyServlet extends HttpServlet {
// 请求后的真实业务
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("666666"); //拦截后展示666
}
// 请求后的真实业务
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
Filter
package com.jsxs.servlet;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @Author Jsxs
* @Date 2023/7/8 17:58
* @PackageName:com.jsxs.servlet
* @ClassName: MyFilter
* @Description: TODO 配置过滤器
* @Version 1.0
*/
@Slf4j
@WebFilter(urlPatterns = {"/css/*","/imag/*"}) //设置拦截我们CSS和imag的所有文件
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初始化销毁.....");
}
}
Listener
package com.jsxs.servlet;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* @Author Jsxs
* @Date 2023/7/8 18:05
* @PackageName:com.jsxs.servlet
* @ClassName: MyServletContextListener
* @Description: TODO
* @Version 1.0
*/
@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监听器销毁完成.....");
}
}
去掉以上方法的各个注解(但保留类),并添加一个配置类实现三个原生API。
主类
package com.jsxs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan
public class SpringBootLs02Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootLs02Application.class, args);
}
}
servlet: 将注册的组件删除
package com.jsxs.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author Jsxs
* @Date 2023/7/8 17:24
* @PackageName:com.jsxs.servlet
* @ClassName: MyServlet
* @Description: TODO 1.添加上注解. 2.重写get和post方法
* @Version 1.0
*/
//@WebServlet(urlPatterns = "/my") //标注是原生的API,并指定拦截的路径(就是不放行)
public class MyServlet extends HttpServlet {
// 请求后的真实业务
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("666666"); //拦截后展示666
}
// 请求后的真实业务
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
Filter:将注册的组件删除
package com.jsxs.servlet;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* @Author Jsxs
* @Date 2023/7/8 17:58
* @PackageName:com.jsxs.servlet
* @ClassName: MyFilter
* @Description: TODO 配置过滤器
* @Version 1.0
*/
@Slf4j
//@WebFilter(urlPatterns = {"/css/*","/imag/*"}) //设置拦截我们CSS和imag的所有文件
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初始化销毁.....");
}
}
Listener:将注册的组件删除
package com.jsxs.servlet;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* @Author Jsxs
* @Date 2023/7/8 18:05
* @PackageName:com.jsxs.servlet
* @ClassName: MyServletContextListener
* @Description: TODO
* @Version 1.0
*/
@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监听器销毁完成.....");
}
}
MyRegisterConfig 配置类实现
package com.jsxs.config;
import com.jsxs.servlet.MyFilter;
import com.jsxs.servlet.MyServlet;
import com.jsxs.servlet.MyServletContextListener;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
/**
* @Author Jsxs
* @Date 2023/7/8 18:14
* @PackageName:com.jsxs.config
* @ClassName: MyServlet
* @Description: TODO
* @Version 1.0
*/
@Configuration
public class MyRegisterConfig {
@Bean
public ServletRegistrationBean myServlet() {
MyServlet myServlet = new MyServlet(); // //这个是我们写在Servlet包中的 MyServlet类
return new ServletRegistrationBean(myServlet, "/my01", "/my02"); // 第一个参数是myServlet实列对象,第二个参数是映射的路径
}
@Bean
public FilterRegistrationBean myFilter() {
MyFilter myFilter = new MyFilter(); //这个是我们写在Servlet包中的 MyFilter类
// 1.第一种
// return new FilterRegistrationBean(myFilter,myServlet()); //第一个参数是MyFilter实列对象,第二个参数是ServletRegistrationBean对象 ->就是只拦截"/my01","/my02"这两个路径
// 2.第二种
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter); // MyFilter实列对象
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my", "/css/*")); // 拦截的路径
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener() {
MyServletContextListener mySwervletContextListener = new MyServletContextListener(); //这个是我们写在Servlet包中的 MyServletContextListener类
return new ServletListenerRegistrationBean(mySwervletContextListener); // MyServletContextListener实列对象
}
}
webServer
Tomcat
, Jetty
, or Undertow
ServletWebServerApplicationContext
容器启动寻找ServletWebServerFactory
并引导创建服务器ServletWebServerApplicationContext: 174行
private void createWebServer() {
WebServer webServer = this.webServer;
// 1.获取servletContext容器
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 2.创建服务器容器
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
// ⭐⭐3.获取Tomcate容器
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory
(Servlet 的web服务器工厂—> Servlet 的web服务器)
SpringBoot底层默认有3个的WebServer工厂;TomcatServletWebServerFactory
, JettyServletWebServerFactory
, or UndertowServletWebServerFactory
底层直接会有一个自动配置类 。ServletWebServerFactoryAutoConfiguration类
ServletWebServerFactoryAutoConfiguration
导入了ServletWebServerFactoryConfiguration
(配置类)
ServletWebServerFactoryConfiguration 配置类
根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包
),容器中就有 TomcatServletWebServerFactory
TomcatServletWebServerFactory 创建出Tomcat服务器并启动
;TomcatWebServer 的构造器拥有初始化方法initialize—this.tomcat.start();
内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)
实现 WebServerFactoryCustomizer
ServletWebServerFactory
进行绑定直接自定义 ConfigurableServletWebServerFactory (第二种
)
因为我们项目已启动ServletWebServerFactory就会引导创建服务器,所以我们直接引入子类集大成者即可。
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
@Configuration
public class AdminWebConfig implements WebMvcConfigurer
@EnableWebMvc + WebMvcConfigurer + @Configuration
—— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能
原理
WebMvcAutoConfiguration
默认的SpringMVC的自动配置功能类。静态资源、欢迎页…@EnableWebMvc
、。会 @Import(DelegatingWebMvcConfiguration.class)只保证SpringMVC最基本的使用
所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效 (也就是说手动生效了)
WebMvcAutoConfiguration
没有生效。场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jdbcartifactId>
dependency>
数据库驱动?
为什么导入JDBC场景,官方不导入驱动?
官方不知道我们接下要操作什么数据库(MySQL.Orangle)。
数据库版本和驱动版本对应
SpringBoot默认版本:<mysql.version>8.0.22mysql.version>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、重新声明版本(maven的属性的就近优先原则)
<properties>
<java.version>1.8java.version>
<mysql.version>5.1.6mysql.version>
properties>
自动配置的类
数据源的自动配置
spring.datasource
DataSource
才自动配置的 @Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration
DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
JndiDataSourceAutoConfiguration: jndi的自动配置
XADataSourceAutoConfiguration: 分布式事务相关的
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: ******
driver-class-name: com.mysql.jdbc.Driver
配置数据源成功且各项信息正确
引入了JDBC和驱动 对数据源没有进行配置
HikariDataSource: 是世界上性能最好的数据源。
Druid: 是世界上安全属性最高的数据源。
德鲁伊帮助文档
https://github.com/alibaba/druid
整合第三方技术的两种方式
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.17version>
dependency>
1. dataSource
主要作用: 链接数据库和开启监控和防火墙等功能。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
/**
*
* @return 主要用途链接数据库和配置数据库信息
*/
// @ConditionalOnMissingBean(DataSource.class) ->当数据中没有的话,才会使用默认的数据源
@Bean // 这里有数据源了所以使用我们配置的数据源
// @ConfigurationProperties("spring.datasource") // 因为德鲁伊的链接名和Hik的连接名一致,我们可以使用这个
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
// 1.链接数据库
druidDataSource.setUrl("jdbc:mysql://localhost:3306/demo1");
druidDataSource.setUsername("root");
druidDataSource.setPassword("121788");
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
// 2.打开SQL监控和防火墙
druidDataSource.setFilters("stat,wall");
System.out.println(druidDataSource.getClass());
return druidDataSource;
}
2. 配置 StatViewServle
StatViewServlet的用途包括:
提供监控信息展示的html页面
提供监控信息的JSON API
<servlet>
<servlet-name>DruidStatViewservlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>DruidStatViewservlet-name>
<url-pattern>/druid/*url-pattern>
servlet-mapping>
/**
*
* @return : 主要提供监控视图 和 JSON API的操作
*/
@Bean
public ServletRegistrationBean StatViewServlet(){
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
return registrationBean;
}
3.StatFilter
用于统计监控信息;如WEB监控
、URI监控
<filter>
<filter-name>DruidWebStatFilterfilter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilterfilter-class>
<init-param>
<param-name>exclusionsparam-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*param-value>
init-param>
filter>
<filter-mapping>
<filter-name>DruidWebStatFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
/**
*
* @return WebStatFilter用于采集web-URL关联监控的数据。
*/
@Bean
public FilterRegistrationBean WebStatFilter(){
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean<>(webStatFilter);
registrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //排除这些路径
registrationBean.setUrlPatterns(Arrays.asList("/*")); // 拦截这个路径
return registrationBean;
}
package com.jsxs.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @Author Jsxs
* @Date 2023/7/9 14:28
* @PackageName:com.jsxs.config
* @ClassName: MyDuridConfig
* @Description: TODO 德鲁伊数据源配置
* @Version 1.0
*/
@Configuration
public class MyDruidConfig {
/**
*
* @return 主要用途链接数据库和配置数据库信息
*/
// @ConditionalOnMissingBean(DataSource.class) ->当数据中没有的话,才会使用默认的数据源
@Bean // 这里有数据源了所以使用我们配置的数据源
// @ConfigurationProperties("spring.datasource") // 因为德鲁伊的链接名和Hik的连接名一致,我们可以使用这个
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
// 1.链接数据库
druidDataSource.setUrl("jdbc:mysql://localhost:3306/demo1");
druidDataSource.setUsername("root");
druidDataSource.setPassword("121788");
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
// 2.打开SQL监控和防火墙
druidDataSource.setFilters("stat,wall");
System.out.println(druidDataSource.getClass());
return druidDataSource;
}
/**
*
* @return : 主要提供监控视图 和 JSON API的操作
*/
@Bean
public ServletRegistrationBean StatViewServlet(){
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
// 1.添加监控的密码和账户
registrationBean.addInitParameter("loginUsername","1");
registrationBean.addInitParameter("loginPassword","1");
return registrationBean;
}
/**
*
* @return WebStatFilter用于采集web-URL关联监控的数据。
*/
@Bean
public FilterRegistrationBean WebStatFilter(){
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean<>(webStatFilter);
registrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //排除这些路径
registrationBean.setUrlPatterns(Arrays.asList("/*")); // 拦截这个路径
return registrationBean;
}
}
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.17version>
dependency>
@Deprecated // 标注已经过时,
我们发现德鲁伊的包中自己带了自动配置的包
并且按需导入的方式进行导入 (即屏蔽掉以前SpringBoot默认的数据源)
spring.datasource
依然存在spring.datasource.druid
DruidSpringAopConfiguration.class
, 监控SpringBean的配置;配置项:spring.datasource.druid.aop-patterns
DruidStatViewServletConfiguration.class
, 监控页的配置:spring.datasource.druid.stat-view-servlet
;默认开启DruidWebStatFilterConfiguration.class
, web监控配置;spring.datasource.druid.web-stat-filter
;默认开启DruidFilterConfiguration.class
所有Druid自己filter的配置 private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
druid:
aop-patterns: com.atguigu.admin.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: admin
resetEnable: false
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false
https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
Druid 高级配置属性列表…
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
@Mapper
就会被自动扫描进来@EnableConfigurationProperties(MybatisProperties.class) : MyBatis配置项绑定类。
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{}
@ConfigurationProperties(prefix = "mybatis") 前缀
public class MybatisProperties
MybatisX 插件。 这个插件可以实现映射跳转
application.yaml
mybatis:
config-location: classpath:mybatis/mybatis-config.xml #指定mybatis的核心文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #指定mybatis的映射文件位置
实体类
package com.jsxs.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Author Jsxs
* @Date 2023/7/17 11:01
* @PackageName:com.jsxs.bean
* @ClassName: Admin
* @Description: TODO
* @Version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin {
Integer id;
String name;
String password;
}
mapper层接口: mapper注解是为了让mybatis发现,respoity目的是为了让
package com.jsxs.mapper;
import com.jsxs.bean.Admin;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/7/17 11:01
* @PackageName:com.jsxs.mapper
* @ClassName: AdminMapper
* @Description: TODO
* @Version 1.0
*/
@Mapper
@Repository
public interface AdminMapper {
// 1.查询所有的用户
public List<Admin> allAdmin();
}
mybatis.xml映射SQL文件
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jsxs.mapper.AdminMapper">
<select id="allAdmin" resultType="com.jsxs.bean.Admin">
select *from admin;
select>
mapper>
接口
package com.jsxs.service;
import com.jsxs.bean.Admin;
import com.jsxs.mapper.AdminMapper;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/7/17 13:39
* @PackageName:com.jsxs.service
* @ClassName: AdminService
* @Description: TODO
* @Version 1.0
*/
public interface AdminService {
public List<Admin> allAdmin();
}
接口实现类
package com.jsxs.service.impl;
import com.jsxs.bean.Admin;
import com.jsxs.mapper.AdminMapper;
import com.jsxs.service.AdminService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/7/17 13:44
* @PackageName:com.jsxs.service.impl
* @ClassName: AdminServiceImpl
* @Description: TODO
* @Version 1.0
*/
@Service
public class AdminServiceImpl implements AdminService {
@Resource
AdminMapper adminMapper;
@Override
public List<Admin> allAdmin() {
return adminMapper.allAdmin();
}
}
测试实验
package com.jsxs.service.impl;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
/**
* @Author Jsxs
* @Date 2023/7/17 13:47
* @PackageName:com.jsxs.service.impl
* @ClassName: AdminServiceImplTest
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
class AdminServiceImplTest {
@Resource
AdminServiceImpl adminService;
@Test
void test(){
System.out.println(adminService.allAdmin());
}
}
Mybatis 默认是不支持我们的数据库字段的驼峰命名的。假如说数据库的字段名的间隔以下划线分割,那么实体类应该写成驼峰命名。那么如果我们没有配置mybatis使用驼峰命名那么我们就会查询到的数据为null。
第一种: 使用全局配置文件配置
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="multipleResultSetsEnabled" value="true"/>
settings>
configuration>
第二种: 使用application.yaml
mybatis:
config-location: classpath:mybatis/mybatis-config.xml #指定mybatis的核心文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #指定mybatis的映射文件位置
configuration:
multiple-result-sets-enabled: true ⭐
注意: 全局配置文件(mybatis-config)与application.yaml中只能存在一个对mybatis进行配置。
基本步骤:
如果我们使用注解开发的话,我们就可以省略掉xml映射文件而不是xml全局配置文件(mybatis-config.xml)
package com.jsxs.mapper;
import com.jsxs.bean.Admin;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/7/17 11:01
* @PackageName:com.jsxs.mapper
* @ClassName: AdminMapper
* @Description: TODO
* @Version 1.0
*/
@Mapper
@Repository
public interface AdminMapper {
// 1.查询所有的用户
public List<Admin> allAdmin();
// 2.根据id进行查找数据 ⭐⭐
@Select("select *from admin where id = #{id}")
public Admin getAdminById(Long id);
}
Service 接口实现类
package com.jsxs.service;
import com.jsxs.bean.Admin;
import com.jsxs.mapper.AdminMapper;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/7/17 13:39
* @PackageName:com.jsxs.service
* @ClassName: AdminService
* @Description: TODO
* @Version 1.0
*/
public interface AdminService {
public List<Admin> allAdmin();
public Admin getAdminById(Long id);
}
Service接口
package com.jsxs.service.impl;
import com.jsxs.bean.Admin;
import com.jsxs.mapper.AdminMapper;
import com.jsxs.service.AdminService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/7/17 13:44
* @PackageName:com.jsxs.service.impl
* @ClassName: AdminServiceImpl
* @Description: TODO
* @Version 1.0
*/
@Service
public class AdminServiceImpl implements AdminService {
@Resource
AdminMapper adminMapper;
@Override
public List<Admin> allAdmin() {
return adminMapper.allAdmin();
}
@Override
public Admin getAdminById(Long id) {
return adminMapper.getAdminById(id);
}
}
测试结果
package com.jsxs.service.impl;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
/**
* @Author Jsxs
* @Date 2023/7/17 13:47
* @PackageName:com.jsxs.service.impl
* @ClassName: AdminServiceImplTest
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
class AdminServiceImplTest {
@Resource
AdminServiceImpl adminService;
@Test
void test(){
System.out.println(adminService.allAdmin());
System.out.println(adminService.getAdminById(1L));
}
}
我们只需要各自写各自的即可,即非注解的要写 xml映射文件和全局配置文件;非注解的不需要xml映射文件但需要全局文件
package com.jsxs.mapper;
import com.jsxs.bean.Admin;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/7/17 11:01
* @PackageName:com.jsxs.mapper
* @ClassName: AdminMapper
* @Description: TODO
* @Version 1.0
*/
@Mapper
@Repository
public interface AdminMapper {
// 1.查询所有的用户
public List<Admin> allAdmin();
// 2.根据id进行查找数据
@Select("select *from admin where id = #{id}")
public Admin getAdminById(Long id);
// 3.xinz
@Insert("insert into admin(name,password) values(#{name},#{password})")
@Options(useGeneratedKeys = true,keyColumn = "id") // 指定主键是那个?
public int add(String name,String password);
}
最佳实践
mapper-location
位置即可;config-location:
核心配置文件就不需要了。核心配置文件最好直接再application.yaml中使用。Mapper接口并标注@Mapper注解
@MapperScan("com.atguigu.admin.mapper")
简化,其他的Mapper接口就可以不用标注@Mapper注解MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
mybatis plus 官网 https://baomidou.com/
建议安装 MybatisX 插件
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
帮助我们自动引入了JDBC和Mybatis的包,所以我们不需要再引入JDBC和Mybatis了
自动配置
mybatis-plus:xxx 就是对mybatis-plus的定制
底层是容器中默认的数据源
mapperLocations 自动配置好的。有默认值 (3.1.2下才有默认值)
。classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下@MapperScan("com.atguigu.admin.mapper") 批量扫描就行
优点:
Mapper继承 BaseMapper
就可以拥有crud能力。
- 数据库层继承的是: BaseMapper
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml #3.1.2版本之后有默认的值了。
实体类
为什么说:我们这里写类名为 Admin ; 然后Mybatisplus是怎么找到数据库的具体表的? 因为默认有一个注解 @TableName(“xxx”) xxx默认是写的是类名,就会通过这个类名去数据库中寻找。如果表名更改了,那么我们可以通过这个注解更改映射的关系。
package com.jsxs.bean;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Author Jsxs
* @Date 2023/7/17 11:01
* @PackageName:com.jsxs.bean
* @ClassName: Admin
* @Description: TODO
* @Version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin {
@TableField(exist = true) // 假如我们需要临时的属性,但这个属性不存在我们可以再上面添加一个注解 @TableField(exist = false)表示在表中不存在
Integer id;
String name;
String password;
}
Mapper接口-> 需要继承一个接口
package com.jsxs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jsxs.bean.Admin;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/7/17 11:01
* @PackageName:com.jsxs.mapper
* @ClassName: AdminMapper
* @Description: TODO 继承接口之后我们不需要再编写 CRUD 就自带的已经存在了
* @Version 1.0
*/
@Mapper
@Repository
public interface AdminMapper extends BaseMapper<Admin> { // 需要继承BaseMapper,因为这个父类有很多已经分装好的方法
}
测试实现
package com.jsxs.mapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
/**
* @Author Jsxs
* @Date 2023/7/18 11:03
* @PackageName:com.jsxs.mapper
* @ClassName: AdminMapperTest
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
class AdminMapperTest {
@Resource
AdminMapper adminMapper;
@Test
void test(){
System.out.println(adminMapper.selectById(1));
}
}
- 业务层接口
继承的是 IService<操作的实体类名>
; 业务层实现类继承业务层的实现类要继承ServiceImpl<要实现表的BaseMapper,操作表对应的实体类名> 且 要实现业务层的接口
实体类
package com.jsxs.bean;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Author Jsxs
* @Date 2023/7/17 11:01
* @PackageName:com.jsxs.bean
* @ClassName: Admin
* @Description: TODO
* @Version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin {
@TableField(exist = true) // 假如我们需要临时的属性,但这个属性不存在我们可以再上面添加一个注解 @TableField(exist = false)表示在表中不存在
Integer id;
String name;
String password;
}
Mapper接口
package com.jsxs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jsxs.bean.Admin;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @Author Jsxs
* @Date 2023/7/17 11:01
* @PackageName:com.jsxs.mapper
* @ClassName: AdminMapper
* @Description: TODO 继承接口之后我们不需要再编写 CRUD 就自带的已经存在了
* @Version 1.0
*/
@Mapper
@Repository
public interface AdminMapper extends BaseMapper<Admin> { // 需要继承BaseMapper,因为这个父类有很多已经分装好的方法
}
Service 接口
package com.jsxs.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jsxs.bean.Admin;
/**
* @Author Jsxs
* @Date 2023/7/18 11:26
* @PackageName:com.jsxs.service
* @ClassName: AdminService
* @Description: TODO 我们业务层的接口只需要实现IService
* @Version 1.0
*/
public interface AdminService extends IService<Admin> {
}
业务层实现类接口:
package com.jsxs.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jsxs.bean.Admin;
import com.jsxs.mapper.AdminMapper;
import com.jsxs.service.AdminService;
import org.springframework.stereotype.Service;
import java.io.Serializable;
/**
* @Author Jsxs
* @Date 2023/7/18 11:27
* @PackageName:com.jsxs.service.impl
* @ClassName: AdminServiceImpl
* @Description: TODO 业务层的实现类要继承ServiceImpl<要实现表的BaseMapper,操作表对应的实体类名> 且 要实现业务层的接口
* @Version 1.0
*/
@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper,Admin> implements AdminService {
}
测试类
package com.jsxs.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jsxs.bean.Admin;
import com.jsxs.service.AdminService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
/**
* @Author Jsxs
* @Date 2023/7/18 11:44
* @PackageName:com.jsxs.service.impl
* @ClassName: AdminServiceImplTest
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
@Slf4j
class AdminServiceImplTest {
@Resource
AdminService adminService;
@Test
void test(){
System.out.println(adminService.getById(1));
Page<Admin> page = new Page<>(1, 2); //当前页(初始页为1),一页几条
IPage<Admin> iPage = adminService.page(page, null); // Page分页的实列
log.info("全部数据为: {}",iPage.getRecords()); // 分页遍历的话,遍历这个
log.info("当前业为: {}",iPage.getCurrent());
log.info("总多少页: {}",iPage.getPages());
log.info("总多少条: {}",iPage.getTotal());
}
}
假如说查询到的数据都为0,那么就是没有配置分页插件
package com.jsxs.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author Jsxs
* @Date 2023/7/18 13:05
* @PackageName:com.jsxs.config
* @ClassName: MybatisPlusConfig
* @Description: TODO
* @Version 1.0
*/
@Configuration
public class MybatisPlusConfig {
// 分页插件
@Bean
public PaginationInterceptor paginationinterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
}
为何Controller中调用接口就能实现接口实现类里的方法?
- 如何在删除之后仍然在原来的页面 ?
我们只需要在前端的删除按钮上绑定上当前页的页码,然后在后端进行页码的重定向跳转。
前端的操作:
后端的操作
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
连接工厂是准备好的
。LettuceConnectionConfiguration、JedisConnectionConfiguration (默认配置好了客户端)自动注入了RedisTemplate
: xxxTemplate;自动注入了StringRedisTemplate
;k:v都是StringStringRedisTemplate、RedisTemplate就可以操作redis
redis环境搭建
如果我们使用window的话,我们在启动redis服务端的时候需要指定使用哪个配置 redis.windows-service.conf
redis.windows.conf
。在这里我们使用redis.windows.conf
设定了密码。
两种打开方式:
键入“cmd”切到安装目录后输出redis-server.exe redis.windows.conf,回车,就可以了。
在redis安装目录下新建文件startup.bat后,右击“编辑”,或者先用记事本建立该文件,再把扩展名改一下,文件里面写上:redis-server.exe redis.windows.conf。保存,以后再运行就直接运行这个文件,不要再直接运行redis-server.exe了,就可以了
spring:
redis:
host: 127.0.0.1
port: 6379
password: xxxxx
package com.jsxs.mapper;
import com.alibaba.fastjson2.JSON;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import javax.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
/**
* @Author Jsxs
* @Date 2023/7/18 11:03
* @PackageName:com.jsxs.mapper
* @ClassName: AdminMapperTest
* @Description: TODO
* @Version 1.0
*/
@SpringBootTest
class AdminMapperTest {
@Resource
RedisTemplate redisTemplate;
@Test
void test2(){
ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
opsForValue.set(JSON.toJSONString("hello5"),JSON.toJSONString("hello5"));
System.out.println(opsForValue.get("hello5"));
}
}
需要进行序列化处理
没有对 redisTemplate
进行序列化处理,java 代码会将 key
转化成对应的十六进制
的数值进行操作.
注意事项: 我们不管通过 Java代码/Redis客户端 进行设置值还是获取值,我们都需要对其进行JSON
(如果不是java会获取不到值)处理并且序列化
(序列化可以避免默认的16进制)
package com.jsxs.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.net.UnknownHostException;
/**
* @Author Jsxs
* @Date 2023/7/20 13:56
* @PackageName:com.jsxs.config
* @ClassName: RedisConfig
* @Description: TODO
* @Version 1.0
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 将template 泛型设置为
RedisTemplate<String, Object> template = new RedisTemplate();
// 连接工厂,不必修改
template.setConnectionFactory(redisConnectionFactory);
/*
* 序列化设置
*/
// key、hash的key 采用 String序列化方式
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// value、hash的value 采用 Jackson 序列化方式
template.setValueSerializer(RedisSerializer.json());
template.setHashValueSerializer(RedisSerializer.json());
template.afterPropertiesSet();
return template;
}
}
假如说key键值对的值是用的JOSN字符串,那么Java端获取的值就是JSON字符串。如果键值对的值用的是非JSON字符串,那么Java后端获取的值就是非JSON字符串。
总结: 以后开发全部使用JSON字符串。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
dependency>
redis:
host: 127.0.0.1
port: 6379
password: 121788
client-type: jedis #指定客户端的类型为jedis
@Test
void test3(){
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.auth("121788");
System.out.println(jedis.ping());
jedis.set("username1","123456");
System.out.println(jedis.get("username1"));
}
总结: 不管是 Letture 还是Jedis客户端,这两个客户端都需要用到序列化和 JSON 。