SpringBoot(2)

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 and BeanNameViewResolver beans.

    内容协商视图解析器和BeanName视图解析器

  • Support for serving static resources, including support for WebJars (covered later in this document)).

    静态资源(包括webjars)

  • Automatic registration of Converter, GenericConverter, and Formatter 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 type WebMvcConfigurer but without @EnableWebMvc.

不用@EnableWebMvc注解。使用@Configuration+WebMvcConfigurer自定义规则

If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations 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-annotated DelegatingWebMvcConfiguration 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.欢迎页支持

  1. 静态资源路径下index.html

    • 可以配置静态资源路径

    • 但是不可以配置静态资源的访问前缀static-path-pattern。否则导致 index.html不能被默认访问

      spring:
      #  mvc:
      #    static-path-pattern: /res/**   这个会导致welcome page功能失效
        resources:
          static-locations: [classpath:/haha/]
      
  2. 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的时候)

  1. 表单提交会带上**_method=PUT**
  2. 请求过来被HiddenHttpMethodFilter拦截
  3. 请求是否正常,并且是POST。否则不会对其进行请求方式的修改
  4. 获取到**_method**的值。
  5. 判断_method是否是PUT.DELETE.PATCH请求方式中的一种
  6. 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
  7. 过滤器链放行的时候用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

  1. pom.xml中引入spring-boot-starter-web

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  2. 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>
    
  3. 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>
    
  4. 示例:

    @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

  1. 引入xml依赖

    <dependency>
          <groupId>com.fasterxml.jackson.dataformat</groupId>
          <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>
    
  2. postman分别测试返回json和xml

5.5.视图解析与模板引擎

视图解析:Spring Boot在处理请求后跳转到某个页面的过程。

Spring Boot默认打包方式是一个jar包,JSP不支持在压缩包编译,因此SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。

5.5.1.视图解析

  1. 目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
  2. 方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
  3. 任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
  4. 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.基本语法
  1. 表达式

    表达式名字语法用途
    变量取值${…}获取请求域、session域、对象等值
    选择变量*{…}获取上下文对象值
    消息#{…}获取国际化等值
    链接@{…}生成链接
    片段表达式~{…}jsp:include 作用,引入公共页面片段
  2. 字面量

    文本值: ‘one text’ , ‘Another one!’ **,…**数字: 0 , 34 , 3.0 , 12.3 **,…**布尔值: true , false

    空值: null

    变量: one,two,… 变量不能有空格

  3. 文本操作

    字符串拼接: +

    变量替换: |The name is ${name}|

  4. 数学运算

    运算符: + , - , * , / , %

  5. 布尔运算

    运算符: and , or

    一元运算: ! , not

  6. 比较运算

    比较: > , < , >= , <= ( gt , lt , ge , le **)**等式: == , != ( eq , ne )

  7. 条件运算

    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.拦截器

  1. 编写一个拦截器实现HandlerInterceptor接口
  2. 拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
  3. 指定拦截规则【如果是拦截所有,静态资源也会被拦截】

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.拦截器原理

  1. 根据当前请求,找到**HandlerExecutionChain【**可以处理请求的handler以及handler的所有 拦截器】
  2. 先来顺序执行 所有拦截器的 preHandle方法
    • 如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
    • 如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;
  3. 如果任何一个拦截器返回false。直接跳出不执行目标方法。
  4. 所有拦截器都返回True。执行目标方法。
  5. 倒序执行所有拦截器的postHandle方法。
  6. 前面的步骤有任何异常都会直接倒序触发afterCompletion
  7. 页面成功渲染完成以后,也会倒序触发 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 {
    ……
}

例如:

  1. 最大的请求大小:maxRequestSize
  2. 最大的单个文件大小: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.默认规则

  1. 默认情况下,Spring Boot提供/error处理所有错误的映射

  2. 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据。

  3. 要对其进行自定义,添加View解析为error

  4. 要完全替换默认行为,可以实现 ErrorController并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。

  5. 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。

  1. 在容器中添加了id为errorAttributes的DefaultErrorAttributes组件

    public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
    
    }
    
  2. 在容器中添加了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);),非浏览器就返回错误数据。

  3. 容器中有组件 View->id是error;(响应默认错误页)

5.10.Web原生组件注入(Servlet、Filter、Listener)

5.10.1.使用Servlet API

  1. 在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监听项目销毁");
          }
      }
      
  2. 在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);
        }
    }
    
  3. 启动效果

说明:

加入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

配置项列表https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

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属性

  1. 通过config-location配置mybatis的全局配置文件的位置(不推荐)

  2. Mybatis的全局配置文件可以使用mybatis.configuration属性进行配置(推荐)

    例如:通过配置map-underscore-to-camel-case为true是开启驼峰映射

  3. 在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 的基础上只做增强不做改变,为简化开发、提高效率而生。

mybatis plus 官网

建议安装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()));
        }
        ……
    }
    
    1. 配置SqlSessionFactory的时候的属性都来自于绑定于MybatisPlusProperties的。但是有一些默认值。例如mapperLocations 自动配置好的。有默认值。classpath:/mapper/**/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。建议以后sql映射文件,放在classpath:mapper下*
    2. 容器中也自动配置好了SqlSessionTemplate
    3. @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
  1. 配置数据库连接信息
  2. 通过mybatis-plus.mapper-locations配置Mapper映射文件的位置,如果没有整合要在mybatis中配置,否则会报:org.apache.ibatis.binding.BindingException: Invalid bound statem
  3. 在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.测试
  1. 测试mapper中自定义的方法

    @SpringBootTest
    class SpringbootSsmApplicationTests {
        @Autowired
        private UserService userService;
    
        @Test
        void testGetUser() {
            User user = userService.getUserById(1);
            System.out.println(user);
        }
    }
    
  2. 测试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框架中的事务

  1. 事务管理器的对象:事务管理器TransactionManager接口,接口有很多实现类,构成一个技术体系。

    例如:使用jdbc或mybatis访问数据库,使用的事务管理器:org.springframework.jdbc.datasource.DataSourceTransactionManager

  2. 声明式事务:在xml配置文件或者使用注解说明事务控制的内容

    事务控制:隔离级别、传播行为、超时时间、回滚和不回滚异常、隔离级别、传播行为

  3. 事务处理方式:

    1. Spring框架中的@Transactional
    2. aspectj框架可以在xml配置文件中声明事务控制的内容

6.5.2.Spring Boot中事务配置步骤

  1. 业务方法加@Transactional,加入注解后,方法就开启了事务控制。
  2. 在主启动类上@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中定义的方法中开启事务
  1. 在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>
    
  2. 在mapper接口定义方法

    @Mapper
    public interface UserMapper extends BaseMapper<User> {
        void updateUserById(@Param("id") Integer id, @Param("name") String name, @Param("user_name") String user_name);
    }
    
  3. 在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");
        }
    }
    
  4. 测试

    @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中的定义的方法中开启事务
  1. 在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);
    }
    
  2. 测试

    @SpringBootTest
    class SpringbootSsmApplicationTests {
    
        @Autowired
        private UserService userService;
    
    
        @Test
        void testSaveUser() {
            userService.saveUser();
        }
    }
    

    此时将回滚事务,数据不会被提交。

6.6.Redis

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(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

    1. 引入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连接工具

    2. 引入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

  1. 引入redis的场景启动器

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 在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
    
  3. 测试

    @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

  1. 引入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>
    
  2. 修改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
    
  3. 测试

    @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.现在版本

  1. 引入test的启动器

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    
  2. 测试代码

    @SpringBootTest
    class Boot05WebAdminApplicationTests {
        @Test
        void contextLoads() {
    
        }
    }
    
  3. 示例

    需要组件自动注入使用@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)
        }
    }
    
  4. 注意点

    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");
}

09215326908

/**
 * 测试前置条件
 */
@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.namelogging.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:默认只暴露healthinfo Endpoint

  • JMX:默认暴露所有Endpoint

  • 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则

IDJMXWeb
auditeventsYesNo
beansYesNo
cachesYesNo
conditionsYesNo
configpropsYesNo
envYesNo
flywayYesNo
healthYesYes
heapdumpN/ANo
httptraceYesNo
infoYesYes
integrationgraphYesNo
jolokiaN/ANo
logfileN/ANo
loggersYesNo
liquibaseYesNo
metricsYesNo
mappingsYesNo
prometheusN/ANo
scheduledtasksYesNo
sessionsYesNo
shutdownYesNo
startupYesNo
threaddumpYesNo

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@
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值