5.Web开发
5.1.SpringMVC自动配置概览
Spring Boot provides auto-configuration for Spring MVC that works well with most applications。(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:
-
Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans.内容协商视图解析器和BeanName视图解析器
-
Support for serving static resources, including support for WebJars (covered later in this document)).
静态资源(包括webjars)
-
Automatic registration of
Converter
,GenericConverter
, andFormatter
beans.自动注册
Converter,GenericConverter,Formatter
-
Support for
HttpMessageConverters
(covered later in this document).支持
HttpMessageConverters
(后来我们配合内容协商理解原理) -
Automatic registration of
MessageCodesResolver
(covered later in this document).自动注册
MessageCodesResolver
(国际化用) -
Static
index.html
support.静态index.html 页支持
-
Custom
Favicon
support (covered later in this document).自定义
Favicon
-
Automatic use of a
ConfigurableWebBindingInitializer
bean (covered later in this document).自动使用 ConfigurableWebBindingInitializer`,(DataBinder负责将请求数据绑定到JavaBean上)
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own
@Configuration
class of typeWebMvcConfigurer
but without@EnableWebMvc
.不用@EnableWebMvc注解。使用@Configuration+WebMvcConfigurer自定义规则
If you want to provide custom instances of
RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
, orExceptionHandlerExceptionResolver
, and still keep the Spring Boot MVC customizations, you can declare a bean of typeWebMvcRegistrations
and use it to provide custom instances of those components.声明WebMvcRegistrations改变默认底层组件
If you want to take complete control of Spring MVC, you can add your own
@Configuration
annotated with@EnableWebMvc
, or alternatively add your own@Configuration
-annotatedDelegatingWebMvcConfiguration
as described in the Javadoc of@EnableWebMvc
.使用@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC
5.2.简单功能分析
5.2.1.静态资源访问
5.2.1.1.静态资源目录
只要静态资源放在类路径下: called /static
(or /public
or /resources
or /META-INF/resources
)
访问 : 当前项目根路径(类路径)/ + 静态资源名
例如:http://localhost:8080/resources/1.jpg
原理: 静态映射/**,即只要写资源名就可以了。
请求进来,先去找Controller看能不能处理。不能处理的所有请求都交给静态资源处理器。静态资源也找不到则响应404页面
5.2.1.2.改变默认的静态资源路径
spring:
mvc:
# 1.改变默认的静态资源路径,即请求静态资源必须加/res的路径前缀;示例:http://localhost:8080/res/5.jpg
static-path-pattern: /res/**
web:
resources:
# 2.修改静态资源的存放路径;设置以后只有该路径下的静态资源才能访问,默认配置全部失效;可采用数组设置多个。
static-locations: [ classpath:/static_resources/ ]
# static-locations: classpath:/static_resources/
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
5.2.1.3.webjar
自动映射 /webjars/**
https://www.webjars.org/
js,css等资源将其打包成jar包,通过maven引入
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>
请求路径为:http://localhost:8080/webjars/jquery/3.6.0/jquery.js
5.2.2.欢迎页支持
-
静态资源路径下index.html
-
可以配置静态资源路径
-
但是不可以配置静态资源的访问前缀static-path-pattern。否则导致 index.html不能被默认访问
spring: # mvc: # static-path-pattern: /res/** 这个会导致welcome page功能失效 resources: static-locations: [classpath:/haha/]
-
-
controller能处理/index
5.2.3.自定义 Favicon
favicon.ico 放在静态资源目录下即可。
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致 Favicon 功能失效
5.2.4.静态资源配置原理
SpringBoot启动默认加载xxxAutoConfiguration 类(自动配置类)
加载SpringMVC功能的自动配置类WebMvcAutoConfiguration,生效
补充:WebMvcAutoConfigurationAdapter有一个有参构造器
有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties; 获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory,spring的容器
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath
//ServletRegistrationBean 给应用注册Servlet、Filter....
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
}
WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addResourceHandlers资源处理的默认规则,因为这是配置类,容器启动时调用这些方法
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//resourceProperties:相当于拿到配置文件下,spring.resources下的所有配置信息
//add-mappings:开启静态资源映射规则,默认是true。如果设置为false就不会进行静态资源的配置
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
spring:
# mvc:
# static-path-pattern: /res/**
resources:
add-mappings: false 禁用所有静态资源规则
5.3.请求参数处理
5.3.1.请求映射
5.3.1.1.rest使用与原理
Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
-
以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
-
现在:/user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
-
核心Filter;HiddenHttpMethodFilter
-
用法: 表单method=post,隐藏域 _method=put
-
SpringBoot中手动开启;SpringBoot中默认是不开启REST风格的,需要在配置文件中开启
spring: mvc: hiddenmethod: filter: enabled: true #开启页面表单的Rest功能
@Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { return new OrderedHiddenHttpMethodFilter(); }
-
示例:
@RequestMapping(value = "/user", method = RequestMethod.GET)
@ResponseBody
public String getUser() {
return "GET-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.POST)
@ResponseBody
public String saveUser() {
return "POST-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.PUT)
@ResponseBody
public String putUser() {
return "PUT-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
@ResponseBody
public String deleteUser() {
return "DELETE-张三";
}
REST原理(表单提交要使用REST的时候)
- 表单提交会带上**_method=PUT**
- 请求过来被HiddenHttpMethodFilter拦截
- 请求是否正常,并且是POST。否则不会对其进行请求方式的修改
- 获取到**_method**的值。
- 判断_method是否是PUT.DELETE.PATCH请求方式中的一种
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
5.3.1.2.请求映射原理
SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet --> doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
//HandlerMapping:处理器映射。/xxx->>xxxx
HandlerMapping mapping = (HandlerMapping)var2.next();
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。
-
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
-
SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
-
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 HandlerMapping
-
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
5.3.2.普通参数与基本注解
5.3.2.1.注解
@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
@RequestMapping(value = "car/{id}/owner/{username}", method = RequestMethod.GET)
@ResponseBody
public Map<String, Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String, String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String, String> requestHeader,
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> Inters,
@RequestParam Map<String, String> params,
@RequestBody String content) {
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("name", name);
map.put("pv", pv);
map.put("userAgent", userAgent);
map.put("requestHeader", requestHeader);
map.put("age", age);
map.put("Inters", Inters);
map.put("params", params);
map.put("content", content);
return map;
}
@PostMapping(value = "save")
@ResponseBody
public Map postMethod(@RequestBody String context) {
Map<String, Object> map = new HashMap<>();
map.put("context", context);
return map;
}
5.3.2.2.Servlet API
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
略,参考SpringMVC
5.3.2.3.复杂参数
Map、**Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、**Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
5.3.2.3.自定义对象参数
可以自动类型转换与格式化,可以级联封装。
5.4.数据响应
5.4.1.响应JSON
jackson.jar+@ResponseBody
-
pom.xml中引入spring-boot-starter-web
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
spring-boot-starter-web引入spring-boot-starter-json
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> <version>2.4.10</version> <scope>compile</scope> </dependency>
-
spring-boot-starter-json中引入jackson作为底层JSON处理工具
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.11.4</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jdk8</artifactId> <version>2.11.4</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>2.11.4</version> <scope>compile</scope> </dependency>
-
示例:
@RequestMapping("/test/person") @ResponseBody public Person getPerson() { Person person = new Person(); person.setAge(11); person.setUserName("zhangsan"); person.setBrith(new Date()); return person; }
5.4.2.响应XML
-
引入xml依赖
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>
-
postman分别测试返回json和xml
5.5.视图解析与模板引擎
视图解析:Spring Boot在处理请求后跳转到某个页面的过程。
Spring Boot默认打包方式是一个jar包,JSP不支持在压缩包编译,因此SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。
5.5.1.视图解析
- 目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
- 方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
- 任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
- processDispatchResult 处理派发结果(页面改如何响应)
-
render(mv, request, response); 进行页面渲染逻辑
根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
-
所有的视图解析器尝试是否能根据当前返回值得到View对象
-
得到了 redirect:/main.html --> Thymeleaf new RedirectView()
-
ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
-
view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
-
RedirectView 如何渲染【重定向到一个页面】
-
获取目标url地址
-
response.sendRedirect(encodedURL);
-
-
-
视图解析:
- 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
- 返回值以 redirect: 开始: new RedirectView() --》 render就是重定向
- 返回值是普通字符串: new ThymeleafView()—>
Spring Boot引入的spring-boot-starter-web默认配置了视图解析器ThymeleafViewResolve,前后缀为ThymeleafProperties里的默认值,无需用户配置。
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = "classpath:/templates/";
private String suffix = ".html";
private String mode = "HTML";
……
}
5.5.2.模板引擎-Thymeleaf
5.5.2.1.thymeleaf简介
Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.
现代化、服务端Java模板引擎
5.5.2.2.基本语法
-
表达式
表达式名字 语法 用途 变量取值 ${…} 获取请求域、session域、对象等值 选择变量 *{…} 获取上下文对象值 消息 #{…} 获取国际化等值 链接 @{…} 生成链接 片段表达式 ~{…} jsp:include 作用,引入公共页面片段 -
字面量
文本值: ‘one text’ , ‘Another one!’ **,…**数字: 0 , 34 , 3.0 , 12.3 **,…**布尔值: true , false
空值: null
变量: one,two,… 变量不能有空格
-
文本操作
字符串拼接: +
变量替换: |The name is ${name}|
-
数学运算
运算符: + , - , * , / , %
-
布尔运算
运算符: and , or
一元运算: ! , not
-
比较运算
比较: > , < , >= , <= ( gt , lt , ge , le **)**等式: == , != ( eq , ne )
-
条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
5.5.2.3.设置属性值-th:attr
设置单个值
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
</fieldset>
</form>
设置多个值
<img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
以上两个的代替写法 th:xxxx
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">
所有h5兼容的标签写法
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes
5.5.2.4.迭代
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
5.5.2.5.条件运算
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
<p th:case="*">User is some other thing</p>
</div>
5.5.2.6.属性优先级
5.5.3.thymeleaf使用
5.5.3.1.引入Starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
5.5.3.2.自动配置thymeleaf
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ThymeleafProperties.class})
@ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class})
@AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class})
public class ThymeleafAutoConfiguration {
……
}
自动配好的策略
-
所有thymeleaf的配置值都在 ThymeleafProperties
public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html";
-
配置好了 SpringTemplateEngine(模板引擎)
-
Thymeleaf的视图解析器ThymeleafViewResolver已经自动配好了,前后缀就是在ThymeleafProperties里的默认值。无需用户配置。
-
我们只需要直接开发页面
5.5.3.3.页面开发
@RequestMapping("/success")
public ModelAndView success() {
ModelAndView modelAndView = new ModelAndView("success");
modelAndView.addObject("msg", "Stonebridge");
return modelAndView;
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${msg}"></h1>
<h2>
<a th:href="${link}">去百度</a> <br/>
</h2>
</body>
</html>
5.5.3.4.设置访问路径
server:
servlet:
context-path: /world
ContextPath must start with ‘/’ and not end with ‘/’
5.5.4.构建后台管理系统
5.5.4.1.项目创建
thymeleaf、web-starter、devtools、lombok
5.5.4.2.静态资源处理
自动配置好,我们只需要把所有静态资源放到 static 文件夹下
5.5.4.3.路径构建
th:action="@{/login}"
5.5.4.4.模板抽取
th:insert/replace/include
5.5.4.5.页面跳转
防止用户登录后重新刷新页面导致重新提交表单到@PostMapping("/login"),因此登录后跳转到@GetMapping("/main.html"),防止重复刷新。
/**
* 来登录页
* @return
*/
@GetMapping(value = {"/","/login"})
public String loginPage(){
return "login";
}
@PostMapping("/login")
public String main(User user, HttpSession session, Model model){ //RedirectAttributes
if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
//把登陆成功的用户保存起来
session.setAttribute("loginUser",user);
//登录成功重定向到main.html; 重定向防止表单重复提交
return "redirect:/main.html";
}else {
model.addAttribute("msg","账号密码错误");
//回到登录页面
return "login";
}
}
/**
* 去main页面
* @return
*/
@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
log.info("当前方法是:{}","mainPage");
//是否登录。 拦截器,过滤器
Object loginUser = session.getAttribute("loginUser");
if(loginUser != null){
return "main";
}else {
//回到登录页面
model.addAttribute("msg","请重新登录");
return "login";
}
}
5.5.4.6.数据渲染
@GetMapping("/dynamic_table")
public String dynamic_table(Model model){
//表格内容的遍历
List<User> users = Arrays.asList(new User("zhangsan", "123456"),
new User("lisi", "123444"),
new User("haha", "aaaaa"),
new User("hehe ", "aaddd"));
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,stats:${users}">
<td th:text="${stats.count}">Trident</td>
<td th:text="${user.userName}">Internet</td>
<td >[[${user.password}]]</td>
</tr>
</tbody>
</table>
5.6.拦截器
- 编写一个拦截器实现HandlerInterceptor接口
- 拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
- 指定拦截规则【如果是拦截所有,静态资源也会被拦截】
5.6.1.编写拦截器
在preHandle中被拦截后不要使用重定向到初始化页面,因为携带信息无法传递到页面。应该采用转发的方式。
/**
* 登录检查
* 1、配置好拦截器要拦截哪些请求
* 2、把这些配置放在容器中
*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行之前
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("preHandle拦截的请求路径是{}", requestURI);
//登录检查逻辑
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if (loginUser != null) {
//放行
return true;
}
//拦截住。未登录。跳转到登录页
request.setAttribute("msg", "请先登录");
//被拦截后不要使用重定向到登录页面。因为msg信息无法传递到页面。应该采用转发的方式。
// re.sendRedirect("/");
request.getRequestDispatcher("login").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 {
log.info("postHandle执行{}", 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 {
log.info("afterCompletion执行异常{}", ex);
}
}
5.6.2.配置拦截器
指定拦截规则【如果是拦截所有,静态资源也会被拦截】
/**
* 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/**"); //放行的请求
}
}
5.6.3.拦截器原理
- 根据当前请求,找到**HandlerExecutionChain【**可以处理请求的handler以及handler的所有 拦截器】
- 先来顺序执行 所有拦截器的 preHandle方法
- 如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
- 如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;
- 如果任何一个拦截器返回false。直接跳出不执行目标方法。
- 所有拦截器都返回True。执行目标方法。
- 倒序执行所有拦截器的postHandle方法。
- 前面的步骤有任何异常都会直接倒序触发afterCompletion
- 页面成功渲染完成以后,也会倒序触发 afterCompletion
5.7.文件上传
文件上传要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data” SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息
5.7.1.页面表单
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">名字</label>
<input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
//单文件
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input type="file" name="headerImg" id="exampleInputFile">
</div>
//多文件
<div class="form-group">
<label for="exampleInputFile">生活照</label>
<input type="file" name="photos" multiple>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
5.7.2.文件上传代码
/**
* 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));
}
if(photos.length > 0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("H:\\cache\\"+originalFilename));
}
}
}
return "main";
}
5.7.3.自动配置原理
文件上传的配置都是在MultipartAutoConfiguration完成自动配置的,其相关的属性都是都在MultipartProperties类进行配置。例如文件的上传大小配置了默认值,与文件上传相关的所有属性都是以spring.servlet.multipart为前缀的。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class})
@ConditionalOnProperty(prefix = "spring.servlet.multipart",name = {"enabled"},matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties({MultipartProperties.class})
public class MultipartAutoConfiguration {
……
}
例如:
- 最大的请求大小:maxRequestSize
- 最大的单个文件大小:maxFileSize
spring:
servlet:
multipart:
max-file-size: 2MB
max-request-size: 100MB
5.8.文件下载
使用ResponseEntity实现下载文件的功能
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws
IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/images/1552.jpg");
//创建输入流s
InputStream is = new FileInputStream(realPath);
//创建字节数组
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要<下载方式>以及<下载文件>的名字,只有filename的值可以自定义。
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
//bytes:响应体
//headers:响应头
//statusCode:状态码
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
5.9.异常处理
5.9.1.默认规则
-
默认情况下,Spring Boot提供/error处理所有错误的映射
-
对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据。
-
要对其进行自定义,添加
View
解析为error
-
要完全替换默认行为,可以实现
ErrorController
并注册该类型的Bean定义,或添加ErrorAttributes类型的组件
以使用现有机制但替换其内容。 -
error/下的4xx,5xx页面会被自动解析;
-
4xx错误
<section class="error-wrapper text-center"> <h1><img alt="" src="images/404-error.png"></h1> <h2 th:text="${status}">page not found</h2> <h3 th:text="${message}">We Couldn’t Find This Page</h3> //跳转到首页 <a class="back-btn" th:href="@{/main.html}"> Back To Home</a> </section>
-
5xx错误
<section class="error-wrapper text-center"> <h1><img alt="" src="images/500-error.png"></h1> <h2>OOOPS!!!</h2> //异常信息 <h3 th:text="${message}">Something went wrong.</h3> //错误详情 <p class="nrml-txt" th:text="${trace}">Why not try refreshing you page? Or you can <a href="#">contact our support</a> if the problem persists.</p> //状态码 <a class="back-btn" href="index.html" th:text="${status}"> Back To Home</a> </section>
-
5.9.2.定制错误处理逻辑
ErrorMvcAutoConfiguration自动配置异常处理规则
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@AutoConfigureBefore({WebMvcAutoConfiguration.class})
@EnableConfigurationProperties({ServerProperties.class, WebMvcProperties.class})
public class ErrorMvcAutoConfiguration {
private final ServerProperties serverProperties;
@Bean
@ConditionalOnMissingBean(value = {ErrorAttributes.class},search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
@Bean
@ConditionalOnMissingBean(value = {ErrorController.class},search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(), (List)errorViewResolvers.orderedStream().collect(Collectors.toList()));
}
}
异常有关的属性配置来自ServerProperties和WebMvcProperties。
-
在容器中添加了id为errorAttributes的DefaultErrorAttributes组件
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered { }
-
在容器中添加了id为basicErrorController的BasicErrorController组件
@Controller @RequestMapping({"${server.error.path:${error.path:/error}}"}) public class BasicErrorController extends AbstractErrorController { @RequestMapping( produces = {"text/html"} ) @RequestMapping(produces = {"text/html"}) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = this.getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); return modelAndView != null ? modelAndView : new ModelAndView("error", model); } @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = this.getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity(status); } else { Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity(body, status); } } }
这个Controller中,如果配置了server.error.path就使用该值,没有陪的默认值是error.path,如果也没有配就匹配/error路径。如果是浏览器就显示error页面(页面响应 new ModelAndView(“error”, model);),非浏览器就返回错误数据。
-
容器中有组件 View->id是error;(响应默认错误页)
5.10.Web原生组件注入(Servlet、Filter、Listener)
5.10.1.使用Servlet API
-
在com.stonebridge.boot05webadmin.servlet包下定义Servlet、Filter、Listener
-
Servlet
效果:直接响应,没有经过Spring的拦截器
@WebServlet(urlPatterns = "/myServlet") public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("66666666"); } }
-
Filter
@Slf4j @WebFilter(urlPatterns = {"/myServlet"}) public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { log.info("MyFilter初始化完成"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { log.info("MyFilter过滤"); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { log.info("MyFilter销毁"); } }
-
Listener
@Slf4j @WebListener public class MySwervletContextListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { log.info("MySwervletContextListener监听项目初始化完成"); } @Override public void contextDestroyed(ServletContextEvent sce) { log.info("MySwervletContextListener监听项目销毁"); } }
-
-
在Spring Boot的主启动类中使用@ServletComponentScan扫描Servlet、Filter、Listener组件所在的包
@ServletComponentScan(basePackages = "com.stonebridge.boot05webadmin")//指定原生Servlet组件都放在那里 @SpringBootApplication public class Boot05WebAdminApplication { public static void main(String[] args) { SpringApplication.run(Boot05WebAdminApplication.class, args); } }
-
启动效果
说明:
加入MyServlet后此时有两个Servlet处理请求
-
MyServlet -->处理:/my
-
DispatcherServlet -->处理:/
-
容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。
-
通过 ServletRegistrationBean<DispatcherServlet> 把 DispatcherServlet 配置进来。
-
默认映射的是 / 路径。
-
5.10.2.使用RegistrationBean将Servlet API注入容器
@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();
// return new FilterRegistrationBean(myFilter,myServlet());
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);
}
}
6.数据访问
6.1.数据源的自动配置-HikariDataSource
Spring Boot默认的数据源是HikariDataSource
6.1.1.导入JDBC开发场景
数据库开发的场景会导入很多的相关数据库的自动配置类(XXXAutoConfiguration),例如数据源(dataSource),以及相关配置属性绑定的properties类。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
分析依赖发现缺少数据库驱动
为什么导入JDBC场景,官方不导入驱动?
因为官方不知道我们接下要操作什么数据库,如果要操作数据库,导入对应的依赖即可。
数据库版本和驱动版本对应
默认版本(版本仲裁):
<mysql.version>8.0.26</mysql.version>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</exclusion>
</exclusions>
</dependency>
想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、重新声明版本(maven的属性的就近优先原则)
<properties>
<java.version>1.8</java.version>
<mysql.version>5.1.49</mysql.version>
</properties>
6.1.2.分析自动配置
自动配置的类
6.1.2.1.数据源的自动配置类
DataSourceAutoConfiguration : 数据源的自动配置类
-
数据源相关的配置:spring.datasource。与数据源相关的属性都都在DataSourceProperties配置映射。
容器中没有io.r2dbc.spi.ConnectionFactory(响应式编程)就启动该配置
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class}) @ConditionalOnMissingBean(type = {"io.r2dbc.spi.ConnectionFactory"}) @EnableConfigurationProperties({DataSourceProperties.class}) @Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class}) public class DataSourceAutoConfiguration { public DataSourceAutoConfiguration() { } }
@ConfigurationProperties(prefix = "spring.datasource") public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean { private ClassLoader classLoader; private String name; private boolean generateUniqueName = true; private Class<? extends DataSource> type; private String driverClassName; private String url; private String username; private String password; private String jndiName; private DataSourceInitializationMode initializationMode; …… }
-
数据库连接池的配置,当容器中没有DataSource组件才自动配置的,即用户未配置DataSource。底层默认数据源HikariDataSource
@Configuration(proxyBeanMethods = false) @Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class}) @ConditionalOnMissingBean({DataSource.class, XADataSource.class}) @Import({Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class}) protected static class PooledDataSourceConfiguration { protected PooledDataSourceConfiguration() { } }
-
默认在容器中注册hikari数据源
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({HikariDataSource.class}) @ConditionalOnMissingBean({DataSource.class}) @ConditionalOnProperty(name = {"spring.datasource.type"},havingValue = "com.zaxxer.hikari.HikariDataSource",matchIfMissing = true) static class Hikari { Hikari() {} @Bean @ConfigurationProperties(prefix = "spring.datasource.hikari") HikariDataSource dataSource(DataSourceProperties properties) { HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(properties, HikariDataSource.class); if (StringUtils.hasText(properties.getName())) { dataSource.setPoolName(properties.getName()); } return dataSource; } }
将数据源属性的配置DataSourceProperties传递给createDataSource方法。通过spring.datasource.type设置数据源的类型。
6.1.2.2.数据源事务管理器的自动配置
DataSourceTransactionManagerAutoConfiguration: 数据源事务管理器的自动配置
6.1.2.3.JdbcTemplate的自动配置
JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
-
可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
@Configuration( proxyBeanMethods = false) @ConditionalOnClass({DataSource.class, JdbcTemplate.class}) @ConditionalOnSingleCandidate(DataSource.class) @AutoConfigureAfter({DataSourceAutoConfiguration.class}) @EnableConfigurationProperties({JdbcProperties.class}) @Import({JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class}) public class JdbcTemplateAutoConfiguration { public JdbcTemplateAutoConfiguration() { } }
-
@Bean@Primary JdbcTemplate;容器中有这个组件
@Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean({JdbcOperations.class}) class JdbcTemplateConfiguration { JdbcTemplateConfiguration() { } @Bean @Primary JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); Template template = properties.getTemplate(); jdbcTemplate.setFetchSize(template.getFetchSize()); jdbcTemplate.setMaxRows(template.getMaxRows()); if (template.getQueryTimeout() != null) { jdbcTemplate.setQueryTimeout((int)template.getQueryTimeout().getSeconds()); } return jdbcTemplate; } }
6.1.2.4.jndi的自动配置
JndiDataSourceAutoConfiguration: jndi的自动配置
6.1.2.5.分布式事务相关的
XADataSourceAutoConfiguration: 分布式事务相关的
6.1.3.配置DataSource
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
# 默认的数据库类型时hikari
type: com.zaxxer.hikari.HikariDataSource
# 驱动
driver-class-name: com.mysql.jdbc.Driver
6.1.4.使用JdbcTemplate进行测试
@SpringBootTest
class JdbcDemoApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT * FROM user");
System.out.println(list.size());
for (int i = 0; i < list.size(); i++) {
Map<String, Object> map = list.get(i);
System.out.println(map.get("username"));
}
}
}
6.2.配置Druid数据源
整合第三方技术的两种方式
- 自定义
- 找starter
6.2.1.druid官方github地址
https://github.com/alibaba/druid
6.2.2.自定义方式
6.2.2.1.引入数据源
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
6.2.2.2.创建数据源
-
Spring的原生方法创建数据源
<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" /> </bean>
-
Spring Boot配置数据源
默认的自动配置是判断容器中没有数据源才会自动配置,此时配置了druid数据源,就不会配置默认的HikariDataSource数据源
@Configuration public class MyDataSourceConfig { //默认的自动配置是判断容器中没有数据源才会自动配置(@ConditionalOnMissingBean(DataSource.class)) @Bean @ConfigurationProperties("spring.datasource") public DataSource dataSource() throws SQLException { DruidDataSource dataSource = new DruidDataSource(); return dataSource; } }
6.2.2.3.配置数据库属性
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
# 驱动
driver-class-name: com.mysql.jdbc.Driver
6.2.2.4.使用JdbcTemplate对象测试
@Slf4j
@Controller
public class MainController {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
DataSource dataSource;
@RequestMapping(value = "testDb")
@ResponseBody
public List<Map<String, Object>> testDb() {
log.info("数据源类型:" + dataSource.getClass());
List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT * FROM user");
System.out.println(list.size());
for (int i = 0; i < list.size(); i++) {
Map<String, Object> map = list.get(i);
System.out.println(map.get("username"));
}
return list;
}
}
6.2.3.开启Druid的监控
6.2.3.1.开启Druid的监控
@Configuration
public class MyDataSourceConfig {
//默认的自动配置是判断容器中没有数据源才会自动配置(@ConditionalOnMissingBean(DataSource.class))
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
//加入监控功能
dataSource.setFilters("stat,wall");
return dataSource;
}
/**
* 配置 druid的监控页功能
*
* @return
*/
@Bean
public ServletRegistrationBean statViewServlet() {
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
registrationBean.addInitParameter("loginUsername", "admin");
registrationBean.addInitParameter("loginPassword", "123456");
return registrationBean;
}
/**
* WebStatFilter 用于采集web-jdbc关联监控的数据。
*/
@Bean
public FilterRegistrationBean webStatFilter() {
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
//设置拦截路径,相当于统计每个请求查了数据库的信息
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
//将静态资源的使用排除在外
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
6.2.3.2.配置druid的监控页功能
配置druid的监控页功能(statViewServlet())就可以查看所有的监控页面。
使用配置形式
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
# 默认的数据库类型时hikari
type: com.zaxxer.hikari.HikariDataSource
# 驱动
driver-class-name: com.mysql.jdbc.Driver
filters: stat,wall
max-active: 12
6.2.3.3.打开监控统计功能
打开监控统计功能
//加入监控统计功能
dataSource.setFilters("stat");
6.2.3.4.WebStatFilter
WebStatFilter用于采集web-jdbc关联监控的数据
/**
* WebStatFilter 用于采集web-jdbc关联监控的数据。
*/
@Bean
public FilterRegistrationBean webStatFilter() {
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
//设置拦截路径,相当于统计每个请求查了数据库的信息
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
//将静态资源的使用排除在外
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
6.2.3.4.开启防火墙
dataSource.setFilters("stat,wall");
6.2.4.使用官方starter方式(推荐)
6.2.4.1.引入druid-starter
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
//1.标注为配置类
@Configuration
//2.系统引入DruidDataSource包
@ConditionalOnClass({DruidDataSource.class})
//3.在DataSourceAutoConfiguration自动配置之前配置,因为DataSourceAutoConfiguration中如果没有配置数据源就自动配置Hikari数据源
@AutoConfigureBefore({DataSourceAutoConfiguration.class})
//4.Druid的所有的属性配置在DruidStatProperties(spring.datasource.druid)和DataSourceProperties(spring.datasource)
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
public DruidDataSourceAutoConfigure() {
}
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
LOGGER.info("Init DruidDataSource");
return new DruidDataSourceWrapper();
}
}
6.2.4.2.分析自动配置
-
扩展配置项 spring.datasource.druid。与DruidStatProperties映射
-
DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
-
DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
例如:allow、deny、loginUsername、loginPassword、resetEnable
-
DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
功能同:WebStatFilter 用于采集web-jdbc关联监控的数据。
-
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";
6.2.4.3.配置示例
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
# 数据库驱动类型
driver-class-name: com.mysql.jdbc.Driver
# 与druid有关的全部配置
druid:
aop-patterns: com.atguigu.admin.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true # 开启配置监控页功能
login-username: admin # 设置监控页面的登录账户
login-password: password # 设置监控页面的登录密码
resetEnable: false # 是否有重置按钮
# 以及allow、deny、
web-stat-filter: # 监控web
enabled: true # 开启监控web的功能
urlPattern: # 监控哪些属性
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' # 排除哪些请求
filter: # 对filter进行详细配置
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000 # 设置慢查询的时间限制
logSlowSql: true # 是否记录慢查询
enabled: true # 开启stat
wall:
enabled: true # 开启wall防火墙
config: # 开启wall防火墙
drop-table-allow: false
6.2.4.4.启动并测试
@Slf4j
@Controller
public class MainController {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
DataSource dataSource;
@RequestMapping(value = "testDb")
@ResponseBody
public List<Map<String, Object>> testDb() {
System.out.println("数据源类型:" + dataSource.getClass());
List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT * FROM user");
System.out.println(list.size());
for (int i = 0; i < list.size(); i++) {
Map<String, Object> map = list.get(i);
System.out.println(map.get("username"));
}
return list;
}
}
配置的禁止重置,重置时不会成功
SpringBoot配置示例
https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
6.3.整合MyBatis操作
https://github.com/mybatis
starter
SpringBoot官方的Starter:spring-boot-starter-*
*第三方的: -spring-boot-starter
引入mybatis
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
6.3.1.MyBatis的自动配置
以前单独使用mybatis。需要全局配置文件,根据全局配置文件需要配置SqlSessionFactory,根据SqlSessionFactory获得SqlSession。通过SqlSession找到Mapper接口操作数据库。
// 1.标记为配置类
@Configuration
// 2.导了mybatis的starter后就有SqlSessionFactory、SqlSessionFactoryBean
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
// 3.项目中有且只有一个数据源DataSource
@ConditionalOnSingleCandidate(DataSource.class)
// 4.mybatis配置项的绑定项MybatisProperties
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final TypeHandler[] typeHandlers;
private final LanguageDriver[] languageDrivers;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
//5.自动配置好了SqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
……
}
//6.自动配置了 SqlSessionTemplate 组合了SqlSession
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
// 7.找到标注了@Mapper的接口。只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来
@Configuration
@Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
public MapperScannerRegistrarNotFoundConfiguration() {
}
public void afterPropertiesSet() {
MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
}
@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
private String configLocation;
private String[] mapperLocations;
private String typeAliasesPackage;
private Class<?> typeAliasesSuperType;
private String typeHandlersPackage;
private boolean checkConfigLocation = false;
private ExecutorType executorType;
private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
private Properties configurationProperties;
}
可以application.yaml修改配置文件中mybatis的所有属性
6.3.2.配置模式
6.3.2.1.创建项目
6.3.2.2.创建数据库表映射的Bean
@Data
public class User {
private Integer id;
private String name;
private String age;
private String email;
}
6.3.2.3.创建对应的Mapper
编写mapper接口,必须@Mapper注解或者使用在主启动类加扫描注解。
@Mapper
public interface UserMapper {
public User getUser(Integer id);
}
6.3.2.4.配置Mapper对应的xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.stonebridge.druiddemo.mapper.UserMapper">
<!--配置查询所有-->
<select id="getUser" resultType="com.stonebridge.druiddemo.bean.User">
SELECT *
FROM user
WHERE id = #{id}
</select>
</mapper>
6.3.2.5.配置MyBatis属性
在Spring Boot的application.yaml中配置MyBatis属性
-
通过config-location配置mybatis的全局配置文件的位置(不推荐)
-
Mybatis的全局配置文件可以使用mybatis.configuration属性进行配置(推荐)
例如:通过配置map-underscore-to-camel-case为true是开启驼峰映射
-
在mybatis.mapper-locations属性指向mapper映射文件所在的位置
mybatis:
#config-location: classpath:mybatis/mybatis-config.xml # 指定全局配置文件
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
map-underscore-to-camel-case: true # 开启驼峰映射
6.3.2.6.创建Service
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public User getUserById(Integer id) {
return userMapper.getUser(id);
}
}
6.3.2.7.项目结构
6.3.2.8.测试
@SpringBootTest
class SpringbootSsmApplicationTests {
@Autowired
UserService userService;
@Test
void testGetUser() {
User user = userService.getUserById(1);
System.out.println(user);
}
}
6.3.3.注解模式
6.3.3.1.创建项目
6.3.3.2.引入场景启动器starter
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
6.3.3.3.创建Bean
@Data
public class City {
private Long id;
private String name;
private String state;
private String country;
}
6.3.3.4.创建Mapper接口
编写Mapper接口并标注@Mapper注解
@Mapper
public interface CityMapper {
@Select("SELECT id, name, state, country FROM city WHERE id = #{id}")
City findById(long id);
}
6.3.3.5.启动并测试
@Service
public class CityService {
@Autowired
CityMapper cityMapper;
public City getById(Long id) {
return cityMapper.findById(id);
}
}
@Slf4j
@Controller
public class MainController {
@Autowired
CityService cityService;
@ResponseBody
@RequestMapping("city")
public City getById(@RequestParam("id") Long id) {
return cityService.getById(id);
}
}
6.3.4.混合模式
6.3.4.1.创建Mapper接口
在Mapper接口中定义注解模式的方法和配置模式的方法。编写Mapper接口并标注@Mapper注解
@Mapper
public interface CityMapper {
void insert(City city);
// @Insert("INSERT INTO city (name, state, country) VALUES(#{name}, #{state}, #{country})")
// @Options(useGeneratedKeys = true, keyProperty = "id")
// void insert(City city);
@Select("SELECT id, name, state, country FROM city WHERE id = #{id}")
City findById(long id);
}
6.3.4.2.配置Mapper.xml文件
因为有配置模式,需要添加mapper.xml文件。将sql以及配置添加其中
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.stonebridge.mybatistest.mapper.CityMapper">
<!-- void insert(City city);-->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO city (`name`, `state`, `country`)
VALUES (#{name}, #{state}, #{country})
</insert>
</mapper>
6.3.4.3.指定mapper.xml文件的位置
.在Spring Boot的总配置文件中指定mapper.xml文件的位置
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
6.3.5.最佳实战
- 引入mybatis-starter
- 配置application.yaml中,指定Mapper-location位置即可(混合模式需要添加、注解模式不需要,建议添加mapper.xml文件,用于写负责sql)
- 编写Mapper接口并标注@Mapper注解
- 简单方法直接注解方式配置sql
- 复杂方法编写mapper.xml进行绑定映射
- @MapperScan(“com.stonebridge.admin.mapper”) 简化,其他的接口就可以不用标注@Mapper注解
6.3.6.注意事项
6.3.6.1.数据库地址配置时区
SpringBoot版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区
jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
或在MySQL数据库端配置时区解决此问题
6.3.6.2.数据库驱动类过时
驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
原因:
升级后的mysql驱动类,Driver位置由com.mysql.jdbc.Driver 变为com.mysql.cj.jdbc.Driver
解决方式:
将数据配置文件里spring.datasource.driver-class-name=com.mysql.jdbc.Driver修改为如下
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
6.4.整合MyBatis-Plus
6.4.1.什么是MyBatis-Plus
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
建议安装MybatisX 插件
6.4.2.整合MyBatis-Plus
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
自动配置
-
MybatisPlusAutoConfiguration配置类,该配置类的所有相关的属性都绑定于MybatisPlusProperties。mybatis-plus:xxx 就是对mybatis-plus的定制
Configuration @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) @ConditionalOnSingleCandidate(DataSource.class) @EnableConfigurationProperties({MybatisPlusProperties.class}) @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class}) public class MybatisPlusAutoConfiguration implements InitializingBean { }
@ConfigurationProperties(prefix = "mybatis-plus") public class MybatisPlusProperties { }
-
SqlSessionFactory 自动配置好。底层是容器中默认的数据源。
@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } …… }
- 配置SqlSessionFactory的时候的属性都来自于绑定于MybatisPlusProperties的。但是有一些默认值。例如mapperLocations 自动配置好的。有默认值。classpath:/mapper/**/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。建议以后sql映射文件,放在classpath:mapper下*
- 容器中也自动配置好了SqlSessionTemplate
- @Mapper 标注的接口也会被自动扫描;建议直接 @MapperScan(“com.atguigu.admin.mapper”) 批量扫描就行
优点:
- 只需要我们的Mapper继承 BaseMapper就可以拥有crud能力
6.4.3.快速入门
6.4.3.1.创建项目
6.4.3.2.引入依赖
Spring Boot官方项目中没有Mybatis Plus的启动器,只能创建项目都自行引入;
可以引入Druid的数据源
<dependencies>
<!--mybatis启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--数据库依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis plus启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
</dependencies>
6.4.3.3.创建JavaBean
创建JavaBean与数据库表对应,使用@TableId注解标注主键
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private String email;
}
6.4.3.4.创建Mapper接口
使用@Mapper注解将其扫描进Spring容器,只要实现BaseMapper接口,就会生成很多默认方法。
@Mapper
public interface UserMapper extends BaseMapper<User> {
User getUser(Integer id);
}
6.4.3.5.创建Mapper映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.stonebridge.mapper.UserMapper">
<!--配置查询所有-->
<select id="getUser" resultType="com.stonebridge.domain.User">
SELECT *
FROM tb_user
WHERE id = #{id}
</select>
</mapper>
6.4.3.6.配置application.yaml
- 配置数据库连接信息
- 通过mybatis-plus.mapper-locations配置Mapper映射文件的位置,如果没有整合要在mybatis中配置,否则会报:org.apache.ibatis.binding.BindingException: Invalid bound statem
- 在mybatis-plus.configuration开启自动映射驼峰式命名规则
spring:
datasource:
url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
mybatis-plus:
mapper-locations: classpath:mappers/*.xml
configuration:
map-underscore-to-camel-case: true
6.4.3.7.service层
创建service层,调用mapper层方法
@Service
public class UserService {
UserMapper userMapper;
@Autowired
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User getUserById(Integer id) {
return userMapper.getUser(id);
}
public List<User> selectList() {
return userMapper.selectList(null);
}
}
6.4.3.8.测试
-
测试mapper中自定义的方法
@SpringBootTest class SpringbootSsmApplicationTests { @Autowired private UserService userService; @Test void testGetUser() { User user = userService.getUserById(1); System.out.println(user); } }
-
测试MyBatis Plus中的方法
@SpringBootTest class SpringbootSsmApplicationTests { @Autowired private UserService userService; @Test void testSelectList() { List<User> list = userService.selectList(); for (User user : list) { System.out.println(user); } } }
6.5.事务控制
6.5.1.Spring框架中的事务
-
事务管理器的对象:事务管理器TransactionManager接口,接口有很多实现类,构成一个技术体系。
例如:使用jdbc或mybatis访问数据库,使用的事务管理器:org.springframework.jdbc.datasource.DataSourceTransactionManager
-
声明式事务:在xml配置文件或者使用注解说明事务控制的内容
事务控制:隔离级别、传播行为、超时时间、回滚和不回滚异常、隔离级别、传播行为
-
事务处理方式:
- Spring框架中的@Transactional
- aspectj框架可以在xml配置文件中声明事务控制的内容
6.5.2.Spring Boot中事务配置步骤
- 业务方法加@Transactional,加入注解后,方法就开启了事务控制。
- 在主启动类上@EnableTransactionManagement注解(非必须,最好加上)
6.5.3.具体实现
6.5.3.1.主启动类加@EnableTransactionManagement
@SpringBootApplication
@EnableTransactionManagement
public class SpringbootSsmApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSsmApplication.class, args);
}
}
6.5.3.2.测试MyBatis中定义的方法中开启事务
-
在mapper.xml中定义
<mapper namespace="com.stonebridge.mapper.UserMapper"> <update id="updateUserById"> UPDATE tb_user SET name =#{name},user_name=#{user_name} WHERE id = #{id} </update> </mapper>
-
在mapper接口定义方法
@Mapper public interface UserMapper extends BaseMapper<User> { void updateUserById(@Param("id") Integer id, @Param("name") String name, @Param("user_name") String user_name); }
-
在service方法中使用该方法
@Service public class UserService { UserMapper userMapper; @Autowired public void setUserMapper(UserMapper userMapper) { this.userMapper = userMapper; } @Transactional public void updateUser(Integer id) { userMapper.updateUserById(id, "trump", "Trump"); } }
-
测试
@SpringBootTest class SpringbootSsmApplicationTests { @Autowired private UserService userService; @Test void testUpdateUser() { userService.updateUser(6); } }
执行正常执行更新
在service层增加异常
@Transactional public void updateUser(Integer id) { userMapper.updateUserById(id, "trump", "Trump"); System.out.println(1/0); }
@Test void testUpdateUser() { userService.updateUser(5); }
此时将回滚事务,数据不会被提交。
6.5.3.3.测试MyBatis Plus中的定义的方法中开启事务
-
在service方法中调用方法,@Transactional开启事务管理
@Transactional public void saveUser() { User user = new User(); user.setUserName("Stonebridge"); user.setPassword("123456"); user.setName("stonebride"); user.setAge(18); user.setEmail("stonebridge@gamil.com"); Integer row = userMapper.insert(user); System.out.println(1 / 0); System.out.println("影响条数:" + row); }
-
测试
@SpringBootTest class SpringbootSsmApplicationTests { @Autowired private UserService userService; @Test void testSaveUser() { userService.saveUser(); } }
此时将回滚事务,数据不会被提交。
6.6.Redis
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
6.6.1.Redis自动配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Spring Boot中Redis的场景启动器默认选中lettuce作为底层连接工厂,该客户端基于netty
自动配置:
-
RedisAutoConfiguration 自动配置类。
RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { …… }
-
连接工厂是准备好的。LettuceConnectionConfiguration(默认)、JedisConnectionConfiguration
-
引入LettuceConnectionConfiguration
@Configuration(proxyBeanMethods = false) @ConditionalOnClass(RedisClient.class) @ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", matchIfMissing = true) class LettuceConnectionConfiguration extends RedisConnectionConfiguration { LettuceConnectionConfiguration(RedisProperties properties, ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider, ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) { super(properties, sentinelConfigurationProvider, clusterConfigurationProvider); } }
当已经引入了RedisClient依赖时,spring.redis.client-type无论是否设置lettuce都会加载LettuceConnectionConfiguration连接工具
-
引入JedisConnectionConfiguration
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class }) @ConditionalOnMissingBean(RedisConnectionFactory.class) @ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "jedis", matchIfMissing = true) class JedisConnectionConfiguration extends RedisConnectionConfiguration { }
当已经引入了GenericObjectPool、JedisConnection、Jedis依赖时,spring.redis.client-type无论是否设置lettuce都会加载LettuceConnectionConfiguration连接工具
-
-
自动注入了RedisTemplate<Object, Object> : xxxTemplate;k:v可以是Object的
-
自动注入了StringRedisTemplate;k:v都是String
-
底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis
redis环境搭建
1、阿里云按量付费redis。经典网络
2、申请redis的公网连接地址
3、修改白名单 允许0.0.0.0/0 访问
6.6.2.RedisTemplate与Lettuce
-
引入redis的场景启动器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
在yaml中配置链接属性
spring: redis: #url: redis://stonebridge:Stonebridge123456@r-bp18q0hwvo1fxi5vhbpd.redis.rds.aliyuncs.com:6379 host: r-bp18q0hwvo1fxi5vhbpd.redis.rds.aliyuncs.com port: 6379 password: stonebridge:Stonebridge123456 #client-type: jedis client-type: lettuce lettuce: pool: max-active: 8 min-idle: 5
-
测试
@Slf4j @SpringBootTest class MybatisTestApplicationTests { @Autowired StringRedisTemplate redisTemplate; @Autowired RedisConnectionFactory redisConnectionFactory; @Test void testRedis() { ValueOperations<String, String> operations = redisTemplate.opsForValue(); operations.set("hello", "world"); String key = operations.get("hello"); System.out.println(key); //class org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory System.out.println(redisConnectionFactory.getClass()); } }
6.6.3.切换至Jedis
-
引入jedis依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 导入jedis--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
-
修改client-type: jedis
声明底层连接工厂是jedis。jedis和Lettuce都是给容器添加RedisConnectionFactory。
spring: redis: #url: redis://stonebridge:Stonebridge123456@r-bp18q0hwvo1fxi5vhbpd.redis.rds.aliyuncs.com:6379 host: 192.168.174.132 port: 6379 password: client-type: jedis
-
测试
@Slf4j @SpringBootTest class MybatisTestApplicationTests { @Autowired StringRedisTemplate redisTemplate; @Autowired RedisConnectionFactory redisConnectionFactory; @Test void testRedis() { ValueOperations<String, String> operations = redisTemplate.opsForValue(); operations.set("hello", "world"); String key = operations.get("hello"); System.out.println(key); System.out.println(redisConnectionFactory.getClass()); } }
7.单元测试
7.1.JUnit5 的变化
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
- JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
- JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。
注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
JUnit 5’s Vintage Engine Removed from spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
7.1.1.现在版本
-
引入test的启动器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
-
测试代码
@SpringBootTest class Boot05WebAdminApplicationTests { @Test void contextLoads() { } }
-
示例
需要组件自动注入使用@Autowired自动注入即可
其中@Test为org.junit.jupiter.api.Test;
@Slf4j @SpringBootTest class MybatisTestApplicationTests { @Autowired UserMapper userMapper; @Autowired StringRedisTemplate redisTemplate; @Autowired RedisConnectionFactory redisConnectionFactory; @Test void testRedis() { ValueOperations<String, String> operations = redisTemplate.opsForValue(); operations.set("hello", "world"); String key = operations.get("hello"); System.out.println(key); System.out.println(redisConnectionFactory.getClass()); } @Test public void TestMybatisPlus() { User user = userMapper.selectById(4); log.info("查询到的User信息:{}", user); //查询到的User信息:User(id=4, name=Sandy, age=21, email=test4@baomidou.com, username=null, password=null) } }
-
注意点
SpringBoot整合Junit以后。
- 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
- Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚
7.1.2.以前版本
@SpringBootTest + @RunWith(SpringTest.class)
7.2.JUnit5常用注解
Unit5的注解与JUnit4的注解有所变化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
-
**@Test 😗*表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
-
**@ParameterizedTest 😗*表示方法是参数化测试,下方会有详细介绍
-
**@RepeatedTest 😗*表示方法可重复执行,下方会有详细介绍
-
**@DisplayName 😗*为测试类或者测试方法设置展示名称
-
**@BeforeEach 😗*表示在每个单元测试之前执行
-
**@AfterEach 😗*表示在每个单元测试之后执行
-
**@BeforeAll 😗*表示在所有单元测试之前执行
-
**@AfterAll 😗*表示在所有单元测试之后执行
-
**@Tag 😗*表示单元测试类别,类似于JUnit4中的@Categories
-
**@Disabled 😗*表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
-
**@Timeout 😗*表示测试方法运行如果超过了指定时间将会返回错误
-
**@ExtendWith 😗*为测试类或测试方法提供扩展类引用。功能类似于Junit4的@Runwith。如果测试时需要启动Spring Boot的测试驱动进行测试,即需要获取Spring容器中的组件,在Junit5中就需要使用@SpringBootTest,@SpringBootTest底层就是通过。@ExtendWith实现的。
-
@SpringBootTest:如果在测试类使用该注解,该类下的所有测试方法启动就会加载Spring的整个容器,Spring容器中的组件也可以被使用。
/**
* @BootstrapWith(SpringBootTestContextBootstrapper.class)
* @ExtendWith(SpringExtension.class)
*/
@SpringBootTest
@DisplayName("junit5功能测试类")
public class Junit5Test {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
@DisplayName("测试DisplayName的方法")
void testDisplayName() {
System.out.println("测试DisplayName注解");
//org.springframework.jdbc.core.JdbcTemplate@36f6e521
System.out.println(jdbcTemplate);
}
@Test
@RepeatedTest(5)
void test3() {
System.out.println(5);
}
@BeforeEach
void testBeforeEach() {
System.out.println("测试就要开始了……");
}
@AfterEach
void testAfterEach() {
System.out.println("测试就要开始了……");
}
// @BeforeAll需要加为static修改
@BeforeAll
static void testBeforeAll() {
System.out.println("所有测试就要开始了...");
}
// @AfterAll需要加为static修改
@AfterAll
static void testAfterAll() {
System.out.println("所有测试以及结束了...");
}
/**
* 规定方法超时时间。超出时间测试出异常
*
* @throws InterruptedException
*/
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
@Test
void testTimeout() throws InterruptedException {
Thread.sleep(501);
}
}
7.3.断言(assertions)
断言就是断定某些事情一定发生,如果没有发生就是可以断定出了问题。
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
检查业务逻辑返回的数据是否合理。
所有的测试运行结束以后,会有一个详细的测试报告;
7.3.1.简单断言
用来对单个值进行简单的验证。如:
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
//@SpringBootTest
public class Junit5Assertions {
/**
* 断言:前面断言失败,后面的代码都不会执行
*/
@Test
@DisplayName("测试简单断言")
void testSimpleAssertions() {
int result = cal(3, 5);
//相等
Assertions.assertEquals(8, result, "业务逻辑计算失败");
Object obj1 = new Object();
Object obj2 = new Object();
Assertions.assertSame(obj1, obj2, "两个对象不一样");
}
int cal(int i, int j) {
return i + j;
}
}
7.3.2.数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
@Test
@DisplayName("array assertion")
void array() {
Assertions.assertArrayEquals(new int[]{1, 2}, new int[]{1, 2}, "数组内容不相等");
}
7.3.3.组合断言
assertAll方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
@Test
@DisplayName("组合断言")
void all() {
/**
* 所有断言全部需要成功
*/
Assertions.assertAll("test",
() -> Assertions.assertTrue(true && true, "结果不为true"),
() -> Assertions.assertEquals(1, 2, "结果不是1"));
System.out.println("=====");
}
7.3.4.异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用**@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows()** ,配合函数式编程就可以进行使用。
@DisplayName("异常断言")
@Test
void testException() {
//断定业务逻辑一定出现异常
Assertions.assertThrows(ArithmeticException.class, () -> {
int i = 10 / 2;
}, "业务逻辑居然正常运行?");
}
7.3.5.超时断言
Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间
@DisplayName("异常断言")
@Test
void testException() {
//断定业务逻辑一定出现异常
Assertions.assertThrows(ArithmeticException.class, () -> {
int i = 10 / 2;
}, "业务逻辑居然正常运行?");
}
7.3.6.快速失败
通过 fail 方法直接使得测试失败
@DisplayName("快速失败")
@Test
void testFail() {
//xxxxx
if (1 == 2) {
Assertions.fail("测试失败");
}
}
7.4.前置条件(assumptions)
JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
/**
* 测试前置条件
*/
@DisplayName("测试前置条件")
@Test
void testassumptions(){
Assumptions.assumeTrue(false,"结果不是true");
System.out.println("111111");
}
/**
* 测试前置条件
*/
@DisplayName("测试前置条件")
@Test
void testassumptions(){
Assumptions.assumeFalse(false,"结果不是true");
System.out.println("111111");
}
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
7.5.嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
@DisplayName("嵌套测试")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
8.指标监控
8.1.SpringBoot Actuator
8.1.1.简介
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
8.1.2.x与2.x的不同
8.1.3.如何使用
https://docs.spring.io/spring-boot/docs/2.4.10/reference/html/production-ready-features.html#production-ready
-
引入场景
-
访问 http://localhost:8080/actuator/**
-
暴露所有监控信息为HTTP
management: endpoints: enabled-by-default: true #暴露所有端点信息 web: exposure: include: '*' #以web方式暴露
-
测试
http://localhost:8080/actuator/beans
http://localhost:8080/actuator/configprops
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/metrics/jvm.gc.pause
http://localhost:8080/actuator/endpointName/detailPath(重点)
actuator后面的叫做Endpoint(监控端点)
8.1.4.可视化
https://github.com/codecentric/spring-boot-admin
8.2.Actuator Endpoint
8.2.1.最常使用的端点
ID | 描述 |
---|---|
auditevents | 暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件 。 |
beans | 显示应用程序中所有Spring Bean的完整列表。 |
caches | 暴露可用的缓存。 |
conditions | 显示自动配置的所有条件信息,包括匹配或不匹配的原因。 |
configprops | 显示所有@ConfigurationProperties 。 |
env | 暴露Spring的属性ConfigurableEnvironment |
flyway | 显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway 组件。 |
health | 显示应用程序运行状况信息。 |
httptrace | 显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository 组件。 |
info | 显示应用程序信息。 |
integrationgraph | 显示Spring integrationgraph 。需要依赖spring-integration-core 。 |
loggers | 显示和修改应用程序中日志的配置。 |
liquibase | 显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase 组件。 |
metrics | 显示当前应用程序的“指标”信息。 |
mappings | 显示所有@RequestMapping 路径列表。 |
scheduledtasks | 显示应用程序中的计划任务。 |
sessions | 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 |
shutdown | 使应用程序正常关闭。默认禁用。 |
startup | 显示由ApplicationStartup 收集的启动步骤数据。需要使用SpringApplication 进行配置BufferingApplicationStartup 。 |
threaddump | 执行线程转储。 |
如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:
ID | 描述 |
---|---|
heapdump | 返回hprof 堆转储文件。 |
jolokia | 通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core 。 |
logfile | 返回日志文件的内容(如果已设置logging.file.name 或logging.file.path 属性)。支持使用HTTPRange 标头来检索部分日志文件的内容。 |
prometheus | 以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus 。 |
最常用的Endpoint
-
Health:监控状况
-
Metrics:运行时指标
http://localhost:8080/actuator/metrics
-
Loggers:日志记录
8.2.2.Health Endpoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:
-
health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
-
很多的健康检查默认已经自动配置好了,比如:数据库、redis等
-
可以很容易的添加自定义的健康检查机制
示例:
# management 是所有actuator
# management.endpoint.端点名.xxx 对某个端点具体配置
# 例如: management.endpoint.health.show-details 开启健康信息的详细配置
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
endpoint:
health:
show-details: always
http://localhost:8080/actuator/health
// 20210912132411
// http://localhost:8080/actuator/health
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "MySQL",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 253827739648,
"free": 66736623616,
"threshold": 10485760,
"exists": true
}
},
"ping": {
"status": "UP"
},
"redis": {
"status": "UP",
"details": {
"version": "6.2.5"
}
}
}
}
8.2.3.Metrics Endpoint
提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;
-
通过Metrics对接多种监控系统
-
简化核心Metrics开发
-
添加自定义Metrics或者扩展已有Metrics
// 20210912132244
// http://localhost:8080/actuator/metrics
{
"names": [
"http.server.requests",
"jvm.buffer.count",
"jvm.buffer.memory.used",
"jvm.buffer.total.capacity",
"jvm.classes.loaded",
"jvm.classes.unloaded",
"jvm.gc.live.data.size",
"jvm.gc.max.data.size",
"jvm.gc.memory.allocated",
"jvm.gc.memory.promoted",
"jvm.memory.committed",
"jvm.memory.max",
"jvm.memory.used",
"jvm.threads.daemon",
"jvm.threads.live",
"jvm.threads.peak",
"jvm.threads.states",
"logback.events",
"process.cpu.usage",
"process.start.time",
"process.uptime",
"system.cpu.count",
"system.cpu.usage",
"tomcat.sessions.active.current",
"tomcat.sessions.active.max",
"tomcat.sessions.alive.max",
"tomcat.sessions.created",
"tomcat.sessions.expired",
"tomcat.sessions.rejected"
]
}
8.2.4.管理Endpoints
8.2.4.1.开启与禁用Endpoints
-
默认所有的Endpoint除过shutdown都是开启的。
-
需要开启或者禁用某个Endpoint。配置模式为 management.endpoint.<endpointName>.enabled = true
management: endpoint: beans: enabled: true
-
或者禁用所有的Endpoint然后手动开启指定的Endpoint
management: endpoints: enabled-by-default: false endpoint: beans: enabled: true health: enabled: true
8.2.4.2.暴露Endpoints
支持的暴露方式
-
HTTP:默认只暴露health和info Endpoint
-
JMX:默认暴露所有Endpoint
-
除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
ID | JMX | Web |
---|---|---|
auditevents | Yes | No |
beans | Yes | No |
caches | Yes | No |
conditions | Yes | No |
configprops | Yes | No |
env | Yes | No |
flyway | Yes | No |
health | Yes | Yes |
heapdump | N/A | No |
httptrace | Yes | No |
info | Yes | Yes |
integrationgraph | Yes | No |
jolokia | N/A | No |
logfile | N/A | No |
loggers | Yes | No |
liquibase | Yes | No |
metrics | Yes | No |
mappings | Yes | No |
prometheus | N/A | No |
scheduledtasks | Yes | No |
sessions | Yes | No |
shutdown | Yes | No |
startup | Yes | No |
threaddump | Yes | No |
8.3.定制 Endpoint
8.3.1.定制 Health 信息
如果当前项目需要引入更多信息,需要自己手动添加组件,其组件名称就是MyHealthIndicator截去
@Component
public class MyComHealthIndicator extends AbstractHealthIndicator {
/**
* 真实的检查方法
*
* @param builder
* @throws Exception
*/
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
//mongodb。 获取连接进行测试
Map<String, Object> map = new HashMap<>();
// 检查完成
if (1 == 2) {
// builder.up(); //健康
builder.status(Status.UP);
map.put("count", 1);
map.put("ms", 100);
} else {
// builder.down();
builder.status(Status.OUT_OF_SERVICE);
map.put("err", "连接超时");
map.put("ms", 3000);
}
builder.withDetail("code", 100)
.withDetails(map);
}
}
开启监控
management:
health:
enabled: true
show-details: always #总是显示详细信息。可显示每个模块的状态信息
返回结果:
"myCom": {
"status": "OUT_OF_SERVICE",
"details": {
"code": 100,
"err": "连接超时",
"ms": 3000
}
}
8.3.2.定制info信息
常用两种方式
8.3.2.1.编写配置文件
info:
appName: boot-admin
version: 2.0.1
mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
mavenProjectVersion: @project.version@