124.【SpringBoot 源码刨析C】

SpringBoot源码刨析C

  • (三)、SpringBoot核心功能
    • 2.Web
      • 4.数据响应与内容协商
        • (1).响应JSON
          • (1.1)jackson.jar+@ResponseBody
            • (1.1.1)、返回值解析器
            • (1.1.2)、返回值解析器原理
          • (1.2).SpringMVC到底支持哪些返回值
          • (1.3)/HTTPMessageConverter原理
            • (1.3.1)、MessageConverter规范
            • (1.3.2)、默认的MessageConverter
        • (2).内容协商 (MessageConverter)
          • (2.1)、引入XML文件
          • (2.2)、postman分别测试返回json和xml
          • (2.3)、基于请求参数的内容协商 ⭐⭐
          • (2.4)、内容协商原理
          • (2.5)、自定义 MessageConverter
          • (2.6)、运用参数的方式请求自定义内容协商
        • 5.视图解析与模板引擎
          • (1).视图解析
            • (1.1)、视图解析原理流程
          • (2).Thymeleaf基本语法
            • (2.1)、表达式
            • (2.2)、字面量
            • (2.3)、文本操作
            • (2.4)、数学运算
            • (2.5)、布尔运算
            • (2.6)、比较运算
            • (2.7)、条件运算
            • (2.8)、特殊操作
            • (2.9)、设置属性值-th:attr
            • (2.10)、迭代
            • (2.11)、条件运算
          • (3).Thymeleaf的使用
            • (3.1)、引入依赖
            • (3.2)、自动配置好了thymeleaf
            • (3.2)、页面开发
          • (4).后台管理系统总结
            • (4.1)、举列子(公共方)
            • (4.2)、配置拦截器
            • (4.3)、文件上传(表单)
        • 6.异常处理
          • (1).错误处理
            • (5.1)、默认规则
            • (5.2)、定制错误处理逻辑 (==三种方法 ==)
            • (5.3)、异常处理自动配置原理
            • (5.4)、异常处理步骤流程
        • 7.Web原生组件注入(Servlet、Filter、Listener)
          • (1).使用Servlet API (第一种方式)
          • (2).使用RegistrationBean (第二种方式)
        • 8.嵌入式Servlet容器
          • (1).切换嵌入式Servlet容器
          • (2).定制Servlet容器
        • 9.定制化原理
          • (1).定制化的常见方式
          • (2).原理分析套路
    • 3.数据访问
      • 1.SQL
        • (1).数据源的自动配置-HikariDataSource
          • (1.1) 、导入JDBC场景
          • (1.2) 、分析自动配置JDBC引入了啥
          • (1.3) 、修改配置项
      • 2.使用Druid数据源
        • (1).druid官方github地址
        • (2).自定义方式 (使用德鲁伊数据源)
          • (2.1)、创建数据源
          • (2.2)、Spring时代配置德鲁伊数据源
          • (2.3)、 配置类SpringBoot时代
        • (3).使用官方starter方式
          • (3.1)、引入druid-starter
          • (3.2)、分析自动配置
          • (3.3)、示列
      • 3.Mybatis 操作数据库
        • (1).原始(Spring) - 配置模式
        • (2).非注解的配置模式
        • (3).纯注解的配置模式
        • (4).非注解和纯注解可以混合使用
      • 4.Mybatis-Plus 操作数据库
        • (1).什么是MybatisPlus
        • (2).整合Mybatisplus
      • 5.整合Redis 非SQL数据库
        • (1).Redis 自动配置
        • (2).RedisTemplate与Lettuce
        • (3).切换至jedis

(三)、SpringBoot核心功能

2.Web

4.数据响应与内容协商

在这里插入图片描述

(1).响应JSON

(1.1)jackson.jar+@ResponseBody

在SpringMVC场景中,并不会自动给我们返回Json字符串的也没有Json字符串的过滤器,在SpringBoot中

只要我们使用了@ResponseBody (就会利用返回值处理器里面的消息转换器进行处理)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
web场景自动引入了json场景
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>

在这里插入图片描述
给前端自动返回json数据;

package com.jsxs.controller;

import com.jsxs.bean.Person;
import com.jsxs.bean.Pet;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * @Author Jsxs
 * @Date 2023/7/6 10:16
 * @PackageName:com.jsxs.controller
 * @ClassName: ResponseTestController
 * @Description: TODO
 * @Version 1.0
 */

@Controller
@ResponseBody
public class ResponseTestController {

    @GetMapping("/test/person")
    public Person person() throws ParseException {
        Person person = new Person("jsxs", 12, new SimpleDateFormat("yyyy-MM-dd").parse("2012-02-01"), new Pet("哈吉米", 2));
        return person;
    }
}
(1.1.1)、返回值解析器

15个方法返回值解析器
在这里插入图片描述

		try {
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}

在这里插入图片描述

RequestResponseBodyMethodProcessor   类。


	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		mavContainer.setRequestHandled(true);
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
	// 使用消息转换器进行调用
		// Try even with null return value. ResponseBodyAdvice could get involved.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

(1.1.2)、返回值解析器原理
  • 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
  • 2、返回值处理器调用 handleReturnValue 进行处理
    • 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
        1. 利用 MessageConverters 进行处理 将数据写为json
        • 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型 浏览器接受7种)
          在这里插入图片描述
        • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
          在这里插入图片描述
        • 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter (消息转换器),看谁能处理?
          在这里插入图片描述

            • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
            • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
(1.2).SpringMVC到底支持哪些返回值

这里对应着15种返回值解析器

ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask@ModelAttribute 且为对象类型的


@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor
(1.3)/HTTPMessageConverter原理
(1.3.1)、MessageConverter规范

在这里插入图片描述
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据

例子:Person对象转为JSON。或者 JSON转为Person

(1.3.2)、默认的MessageConverter

在这里插入图片描述

0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true (不管是谁直接为true,也就是说不管是啥文件直接接受)
8 - true (不管是谁直接为true,也就是说不管是啥文件直接接受)
9 - 支持注解方式 xml处理的。

最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)

·AbstractGenericHttpMessageConverter类种的·

outputMessage.getBody().flush();

在这里插入图片描述

(2).内容协商 (MessageConverter)

根据客户端接收能力不同,返回不同媒体类型的数据。

(2.1)、引入XML文件
 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

在这里插入图片描述

(2.2)、postman分别测试返回json和xml

只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。

在这里插入图片描述

(2.3)、基于请求参数的内容协商 ⭐⭐

为了方便内容协商,开启基于请求参数的内容协商功能

spring:
	mvc:
     contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

通过点开源码我们发现我们需要什么样的类型,我们只需要在路径后面添加上 format='xxx' 格式即可。前提是我们需要什么类型的时候要有依赖在 pom.xml 中。比如我们需要使用xml的,我们要有上面的那个 jackson-dataformat-xml 依赖。

在这里插入图片描述

获取json: http://localhost:8080/test/person?format=json
获取xml格式: http://localhost:8080/test/person?format=xml
在这里插入图片描述

源码中发现相比于以前的请求头的内容协商多了一个参数的请求协商。

在这里插入图片描述

确定客户端接收什么样的内容类型;
1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值

在这里插入图片描述

2、最终进行内容协商返回给客户端xml即可。
在这里插入图片描述

(2.4)、内容协商原理
  • 1.判断当前响应头种是否已经有已经确定的媒体类型。MediateType
  • 2.获取客户端(浏览器或者PostMan)支持的请求的Accept头(浏览器)。通过 contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
    • (1).先得到客户端能够接受的所有媒体类型是什么。
  • 3.获取服务器能够生产的媒体类型。(服务器方)
  • 4.遍历服务器所有支持的媒体类型 进行 与客户端能够接受的类型进行匹配的操作,选择最佳匹配。
  • 5.用支持将对象转为最佳媒体类型的converter,调用它进行转化。

为什么说引入xml的转环包就会被底层接受的原理

WebMvcConfigurationSupport 类下

917行 jackson2XmlPresent

在这里插入图片描述
在这里插入图片描述

(2.5)、自定义 MessageConverter

实现多协议数据兼容。json、xml、x-guigu

0、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
1、Processor 处理方法返回值。通过 MessageConverter 处理
2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
3、内容协商找到最终的 messageConverter;

package com.jsxs.controller;

import com.jsxs.bean.Person;
import com.jsxs.bean.Pet;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * @Author Jsxs
 * @Date 2023/7/6 10:16
 * @PackageName:com.jsxs.controller
 * @ClassName: ResponseTestController
 * @Description: TODO
 * @Version 1.0
 */

@Controller
@ResponseBody
public class ResponseTestController {

    /**
     *
     * @return
     * @throws ParseException
     *
     * @TODO: 1.浏览器请求返回xml文件。2.ajax请求返回json文件。3.硅谷app请求返回自定义文件
     *  在以前一个请求完成这项工作这是不可能完成的任务,但是在现在我们有了内容协商我们可以完成这个任务。
     *  步骤: 1.添加自定义的MessageConverter进入系统底层。2.系统底层就会统计出所有MessageConverter
     * 		3.进行内容客户端与服务器内容协商匹配。
     */

    @GetMapping("/test/person")
    public Person person() throws ParseException {
        Person person = new Person("jsxs", 12, new SimpleDateFormat("yyyy-MM-dd").parse("2012-02-01"), new Pet("哈吉米", 2));
        return person;
    }
}

在这里插入图片描述
SpringMVC的什么功能。一个入口给容器中添加一个 WebMvcConfigurer

 @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {

            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

            }
        }
    }

首先配置协议转换器

package com.jsxs.convert;

import com.jsxs.bean.Person;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/7 12:38
 * @PackageName:com.jsxs.convert
 * @ClassName: GguiGuMessageConverter
 * @Description: TODO   自定义的消息转换器
 * @Version 1.0
 */
public class GuiGuMessageConverter implements HttpMessageConverter<Person> {

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    /**
     *   条件是什么
     * @param clazz
     * @param mediaType
     * @return
     */
    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(Person.class);  //只有返回的类型是Person类行就能进行读写
    }

    /**
     *   服务器要统计所有的MessageConverter 都能写哪些内容类型
     *
     *   application/x-jsxs
     * @return
     */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-jsxs");
    }

    @Override
    public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        // 自定义协议的写出: (也就是在返回的格式)
        String data =person.getUserName()+";"+person.getAge()+";"+person.getAge()+";"+person.getPet();

        // 写出去
        OutputStream body = outputMessage.getBody();
        body.write(data.getBytes());
    }
}

package com.jsxs.config;

import com.jsxs.bean.Pet;
import com.jsxs.convert.GuiGuMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;

import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/3 11:13
 * @PackageName:com.jsxs.config
 * @ClassName: WebConfig
 * @Description: TODO
 * @Version 1.0
 */
@Configuration(proxyBeanMethods = false)
// 第一种方式 @Configuration + 实现WebMvcConfigurer接口 (因为JDK8允许接口的默认方法和默认实现所以我们不需要将所有方法全部重写)
// 第二种方式: @Configuration +@Bean 重新注入我们的组件
public class WebConfig /*implements WebMvcConfigurer */{

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("aaaa");
        return hiddenHttpMethodFilter;
    }

//    @Override
//    public void configurePathMatch(PathMatchConfigurer configurer) {
//        UrlPathHelper helper = new UrlPathHelper();
//        helper.setRemoveSemicolonContent(false);
//        configurer.setUrlPathHelper(helper);
//    }


    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer(){
            //  配置支持我们的矩阵注解
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper helper = new UrlPathHelper();
                helper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(helper);
            }
            // 配置支持我们的自定义converter转换器

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {
                    @Override
                    public Pet convert(String source) {  //source 就是页面提交过来的值。只获得过来的值
                        if (!StringUtils.isEmpty(source)){  // 假如说提交的数据不为空
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);  // 逗号之前的设置成姓名
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }

            // 扩展 内容消息转换器

            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new GuiGuMessageConverter());
            }
        };
    }
}

上面的内容可以通过 PostMan 进行处理。浏览器因为我们自己设置不了请求头,所以目前测试不了我们自定义的内容协商。

(2.6)、运用参数的方式请求自定义内容协商

我们通过debug的方式进入到了我们浏览器接受的内容协议上,并查看到有两种接收方式,并在请求参数的方式上没有看到 自定义的格式,所以我们要进行自定义的操作。
在这里插入图片描述
因为只兼容上面两种 所以我们要进行配置内容协商功能

package com.jsxs.config;

import com.jsxs.bean.Pet;
import com.jsxs.convert.GuiGuMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;

import java.util.*;

/**
 * @Author Jsxs
 * @Date 2023/7/3 11:13
 * @PackageName:com.jsxs.config
 * @ClassName: WebConfig
 * @Description: TODO
 * @Version 1.0
 */
@Configuration(proxyBeanMethods = false)
// 第一种方式 @Configuration + 实现WebMvcConfigurer接口 (因为JDK8允许接口的默认方法和默认实现所以我们不需要将所有方法全部重写)
// 第二种方式: @Configuration +@Bean 重新注入我们的组件
public class WebConfig /*implements WebMvcConfigurer */{

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("aaaa");
        return hiddenHttpMethodFilter;
    }

//    @Override
//    public void configurePathMatch(PathMatchConfigurer configurer) {
//        UrlPathHelper helper = new UrlPathHelper();
//        helper.setRemoveSemicolonContent(false);
//        configurer.setUrlPathHelper(helper);
//    }


    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer(){
            //  配置支持我们的矩阵注解
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper helper = new UrlPathHelper();
                helper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(helper);
            }
            // 配置支持我们的自定义converter转换器

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {
                    @Override
                    public Pet convert(String source) {  //source 就是页面提交过来的值。只获得过来的值
                        if (!StringUtils.isEmpty(source)){  // 假如说提交的数据不为空
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);  // 逗号之前的设置成姓名
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }

            // 扩展 内容消息转换器
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new GuiGuMessageConverter());
            }

            // 自定义(重写)内容协商  ⭐⭐⭐

            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                // 要求需要为String
                HashMap<String, MediaType> mediaTypeHashMap = new HashMap<>();
                // 配置支持的请求参数
                mediaTypeHashMap.put("json",MediaType.APPLICATION_JSON);
                mediaTypeHashMap.put("xml",MediaType.APPLICATION_XML);
                mediaTypeHashMap.put("jsxs",MediaType.parseMediaType("application/x-jsxs"));
                // 支持解析哪些参数对应的哪些媒体类型 -》 参数内容协商支持
                ParameterContentNegotiationStrategy parameterContentNegotiationStrategy = new ParameterContentNegotiationStrategy(mediaTypeHashMap);
                //  支持解析哪些参数对应的哪些媒体类型 -》 请求头内容协商支持 (这里通过PostMan进行测试)
                HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy();
                //    真正执行
                configurer.strategies(Arrays.asList(parameterContentNegotiationStrategy,headerContentNegotiationStrategy));
            }
        };
    }
}

在这里插入图片描述

在这里插入图片描述
有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。

大家考虑,上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?【提示:参照SpringBoot官方文档web开发内容协商章节】

5.视图解析与模板引擎

视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染
在这里插入图片描述

(1).视图解析
(1.1)、视图解析原理流程

1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
2、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
3、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
4、processDispatchResult 处理派发结果(页面该如何响应)

DisplatchServlet的 第1078
  • 1、render(mv, request, response); 进行页面渲染逻辑
    • 1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】

      • 1、所有的视图解析器尝试是否能根据当前返回值得到View对象 (for遍历尝试)
      • 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
      • 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
        在这里插入图片描述
      • 4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
    • view.render(mv.getModelInternal(), request, response); 1393行

        • RedirectView类 如何渲染【重定向到一个页面】
        • 1、获取目标url地址
        • 2、response.sendRedirect(encodedURL);

视图解析:

  • 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
  • 返回值以 redirect: 开始: new RedirectView() --》 render就是重定向
(2).Thymeleaf基本语法
(2.1)、表达式

在这里插入图片描述

(2.2)、字面量

文本值: ‘one text’ , ‘Another one!’ ,…数字: 0 , 34 , 3.0 , 12.3 ,…布尔值: true , false
空值: null
变量: one,two,… 变量不能有空格

(2.3)、文本操作

字符串拼接: +
变量替换: |The name is ${name}|

(2.4)、数学运算

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

(2.5)、布尔运算

运算符: and , or
一元运算: ! , not

(2.6)、比较运算

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

(2.7)、条件运算

If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)

(2.8)、特殊操作

无操作: _

(2.9)、设置属性值-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}">

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes
行内写法

1. 假如要写的内容不在标签中而在行内,那么就用这个方式。(非session)

<h1>[[${xxx}]]</h1>

2. 假如是取Session的值
<h1>[[$session.name.xxx}]]</h1>
(2.10)、迭代
<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>
(2.11)、条件运算
<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>
(3).Thymeleaf的使用
(3.1)、引入依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
(3.2)、自动配置好了thymeleaf

ThymeleafAutoConfiguration 类

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { }

自动配好的策略

  • 1、所有thymeleaf的配置值都在 ThymeleafProperties 类
  • 2、配置好了SpringTemplateEngine
  • 3、配好了ThymeleafViewResolver
  • 4、我们只需要直接开发页面

在这里插入图片描述

(3.2)、页面开发
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Success</h1>
<h1 th:text="${A}"></h1>
<a th:href="${baidu}">点击我去金橘社区 ${baidu}</a>
<br>
<br>
<a th:href="@{baidu}">点击我去金橘社区 @{baidu}</a>
<br>
<br>
<a th:href="@{/baidu}">点击我去金橘社区 @{/baidu}</a>
</body>
</html>
package com.jsxs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @Author Jsxs
 * @Date 2023/7/7 17:30
 * @PackageName:com.jsxs.controller
 * @ClassName: ViewTestController
 * @Description: TODO
 * @Version 1.0
 */
@Controller
public class ViewTestController {

    @GetMapping("/toTest")
    public String toTest(Model model){
        // model 会自动放入到请求域中和HttpRequest是一样的,只能接受一次请求的操作
        model.addAttribute("A","a");
        model.addAttribute("baidu","https://www.jsxs1.cn");
        return "success";   // 假如说没有模板解析器的话,这里的路径会报黄。
    }
}

# 给整个服务器添加前缀
server:
  servlet:
    context-path: /jsxs

在这里插入图片描述

(4).后台管理系统总结
1. 假如在template中再新建包的话,我们只需要在 controller 的返回值中添加上新建包路径即可  /新建包名/xxx。

2. controller 页面跳转的实质是转发;不是重定向。

3. return: 的值会默认拼接  templates/xxxx.html; return forward: return redirect 找的都是请求的路径不是页面。

4. 静态资源只要放在四大区域就行,前端调用的时候可以省略掉前面的四大区域路径只写相对路径即可。

5. 抽取公共模板(第一种)
	(1). 公共页(top.html): 在标签中设置  th:fragment="AAAA"
		eg: <div  th:fragment="AAAA"></div>
	(2). 使用公共页方: th:insert="~{公共页的HTML名字 :: AAAA}"
		eg:   1. <div th:insert="~{top :: AAAA}"></div>2.<div th:insert="top :: AAAA"></div>3.<div th:replace="~{top :: utopbar}"></div>4.<div th:include="~{top :: topbar}"></div>
	假如说公共页面和被添加公共页面不再同一个包中那么就要加上路径指定在哪
		eg:<div th:include="~{commons/top :: topbar}"></div>

6.抽取公共模板(第二种 ->选择器方式)
	(1). 公共页(top.html): 在标签中设置  id="BBB"
	(2). 使用方: <div th:replace="top :: #BBB"> (当然以上的几种方法都适用)

公共方:

<footer th:fragment="copy">
  &copy; 2011 The Good Thymes Virtual Grocery
</footer>

使用公共方

<body>

  ...
	会带上div(全部都要)
  <div th:insert="~{footer :: copy}"></div>
	引入的东西不会在div里面
  <div th:replace="~{footer :: copy}"></div>
  	引入的中西会在div里面
  <div th:include="~{footer :: copy}"></div>
  
</body>

实际效果:

假如引入的是css、js。也是一样的只不过把div改为link或script
<body>

  ...

  <div>
    <footer>
      &copy; 2011 The Good Thymes Virtual Grocery
    </footer>
  </div>

  <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
  </footer>
  
  <div>
      &copy; 2011 The Good Thymes Virtual Grocery
  </div>
  
</body>
(4.1)、举列子(公共方)

1.公共方的页面
在这里插入图片描述

2.replace->引入的东西会在link标签里面内嵌link标签

<link th:replace="~{}">

在这里插入图片描述
3.include->引入的东西会在link标签里面内嵌link标签

<link th:include="~{}">

在这里插入图片描述
4.标签改为div,div是万能的。

<div th:replace="~{}"></div>

在这里插入图片描述

(4.2)、配置拦截器

SpringMvc的拦截器配置

1.Filter:Servlet定义的原生组件。好处:脱离Spring也能够使用
2.Interceptor:Spring定义的接口。可以使用Spring的自动装配功能

第一种拦截机制

package com.jsxs.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * @Author Jsxs
 * @Date 2023/7/8 17:58
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyFilter
 * @Description: TODO   配置过滤器
 * @Version 1.0
 */

@Slf4j
//@WebFilter(urlPatterns = {"/css/*","/imag/*"})   //设置拦截我们CSS和imag的所有文件

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
       log.info("MyFilter初始化完成.....");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("MyFilter工作中.....");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        log.info("MyFilter初始化销毁.....");
    }
}

第二种拦截机制

在这里进行拦截的操作

package com.jsxs.config;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        登入成功之后存在登入后的session
        if (request.getSession().getAttribute("LoginUser")!=null){
            return true;
        }else {
            request.setAttribute("msg","没有权限请先进行登入");
            request.getRequestDispatcher("/index.html").forward(request,response);
            return false;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

在这里设定规则

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

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
}

拦截器原理:

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

如图:
在这里插入图片描述

(4.3)、文件上传(表单)
1. 需要指定 enctype
2. 单个文件上传 什么也不加
3. 多个文件上传 需要加上: multiple 属性

<form method="post" action="/upload" enctype="multipart/form-data">
 单文件   <input type="file" name="file"><br>
多文件    <input type="file" name="file" multiple><br>
    <input type="submit" value="提交">
</form>
1.一定要加上参数注解 @RequestPart

    /**
     * MultipartFile 自动封装上传过来的文件,以后文件上传用这个类型和注解
     * @param email
     * @param username
     * @param headerImg
     * @param photos
     * @return
     */
    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg") MultipartFile headerImg,
                         @RequestPart("photos") MultipartFile[] photos) throws IOException {

        log.info("上传的信息:email={},username={},headerImg={},photos={}",
                email,username,headerImg.getSize(),photos.length);

        if(!headerImg.isEmpty()){
            //保存到文件服务器,OSS服务器
            String originalFilename = headerImg.getOriginalFilename();  //获取上传的文件名
            headerImg.transferTo(new File("H:\\cache\\"+originalFilename)); //这个方法直接传输了,内部封装了InputStrean和OutStream流(需要指定路径+文件名即可)
        }

        if(photos.length > 0){
            for (MultipartFile photo : photos) {
                if(!photo.isEmpty()){
                    String originalFilename = photo.getOriginalFilename();
                    photo.transferTo(new File("H:\\cache\\"+originalFilename)); //遍历传输各个文件
                }
            }
        }


        return "main";
    }

在这里插入图片描述

原理

MultipartAutoConfigurationMultipartProperties

在这里插入图片描述

  1. 自动配置原理

文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties

  • 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】
  • 原理步骤
    • 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求`
    • 2、参数解析器来解析请求中的文件内容封装成`MultipartFile
    • 3、将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>
      FileCopyUtils。实现文件流的拷贝

在这里插入图片描述

6.异常处理

(1).错误处理
(5.1)、默认规则
  • 默认情况下,Spring Boot提供 /error处理所有错误的映射
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

在这里插入图片描述
在这里插入图片描述
要对其进行自定义,添加View解析为error

  • 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
  • 静态资源或templates目录下放 error/下的4xx,5xx页面会被自动解析

在这里插入图片描述

(5.2)、定制错误处理逻辑 (==三种方法 ==)
  • 自定义错误页 (第一种)
    • error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
在自定义的4xx页面或者5xx页面的某个提示标签中可以这么写
th:text="${status}"  ->在配置的错误页面中会获得返回的状态码
th:text="${message}"  ->在配置的错误页面中会获得返回的信息
  • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的 (第二种⭐)

在这里插入图片描述

  • @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error (第三种)

先定义: 编写有参构造和无参构造,并不是继承父类的方法

在这里插入图片描述
后使用

在这里插入图片描述

  • Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
    • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());

在这里插入图片描述

  • ErrorViewResolver 实现自定义处理异常;
    • response.sendError 。error请求就会转给controller
    • 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
    • basicErrorController 要去的页面地址是 ErrorViewResolver ;
(5.3)、异常处理自动配置原理
  • ErrorMvcAutoConfiguration 自动配置异常处理规则
    • 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
      • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
      • DefaultErrorAttributes:定义错误页面中可以包含哪些数据。

在这里插入图片描述
在这里插入图片描述

  • 容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)
      • 处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
      • 容器中有组件 View->id是error;(响应默认错误页)
      • 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
  • 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
      • 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面
      • error/404、5xx.html
(5.4)、异常处理步骤流程

1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException
2、进入视图解析流程(页面渲染?)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;

  • 1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器

在这里插入图片描述

  1. 系统默认的 异常解析器;
  • 1、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
  • 2、默认没有任何人能处理异常,所以异常会被抛出
      • 1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理
      • 2、解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。
      • 3、默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
      • 4、模板引擎最终响应这个页面 error/500.html

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

(1).使用Servlet API (第一种方式)
首先要在主类中设置需要扫描的servlet包在哪。
1. @ServletComponentScan(basePackages = "com.atguigu.admin") :指定原生Servlet组件都放在那里,默认是扫描主类所在包的子包

然后我们在servlet包中配置我们的原生servlet
2. @WebServlet(urlPatterns = "/my"):效果:直接响应,没有经过Spring的拦截器?

3. @WebFilter(urlPatterns={"/css/*","/images/*"})
4. @WebListener

扩展:DispatchServlet 如何注册进来

  • 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc
    在这里插入图片描述

  • 实质上通过 ServletRegistrationBean<DispatcherServlet> 把 DispatcherServlet 配置进来。
    在这里插入图片描述

  • 默认映射的是 / 路径。
    在这里插入图片描述

Tomcat-Servlet

多个Servlet都能处理到同一层路径,精确优选原则
A: /my/ ->(假如说这个路径可以处理my/下的所有路径)
B: /my/1 -> (假如说这个路径可以处理my/1这个准确路径)
在这里插入图片描述

主类

package com.jsxs;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
public class SpringBootLs02Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootLs02Application.class, args);
    }

}

servlet

package com.jsxs.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author Jsxs
 * @Date 2023/7/8 17:24
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyServlet
 * @Description: TODO  1.添加上注解.   2.重写get和post方法
 * @Version 1.0
 */
@WebServlet(urlPatterns = "/my")  //标注是原生的API,并指定拦截路径
public class MyServlet extends HttpServlet {

    //  请求后的真实业务
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("666666");  //拦截后展示666
    }
    //  请求后的真实业务
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

Filter

package com.jsxs.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * @Author Jsxs
 * @Date 2023/7/8 17:58
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyFilter
 * @Description: TODO   配置过滤器
 * @Version 1.0
 */

@Slf4j
@WebFilter(urlPatterns = {"/css/*","/imag/*"})   //设置拦截我们CSS和imag的所有文件

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
       log.info("MyFilter初始化完成.....");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("MyFilter工作中.....");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        log.info("MyFilter初始化销毁.....");
    }
}

Listener

package com.jsxs.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 * @Author Jsxs
 * @Date 2023/7/8 18:05
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyServletContextListener
 * @Description: TODO
 * @Version 1.0
 */

@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("MyServletContextListener监听器初始化完成.....");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("MyServletContextListener监听器销毁完成.....");
    }
}

在这里插入图片描述

(2).使用RegistrationBean (第二种方式)

去掉以上方法的各个注解(但保留类),并添加一个配置类实现三个原生API。
主类

package com.jsxs;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
public class SpringBootLs02Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootLs02Application.class, args);
    }

}

servlet: 将注册的组件删除

package com.jsxs.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author Jsxs
 * @Date 2023/7/8 17:24
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyServlet
 * @Description: TODO  1.添加上注解.   2.重写get和post方法
 * @Version 1.0
 */
//@WebServlet(urlPatterns = "/my")  //标注是原生的API,并指定拦截的路径(就是不放行)
public class MyServlet extends HttpServlet {

    //  请求后的真实业务
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("666666");  //拦截后展示666
    }
    //  请求后的真实业务
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

Filter:将注册的组件删除

package com.jsxs.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * @Author Jsxs
 * @Date 2023/7/8 17:58
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyFilter
 * @Description: TODO   配置过滤器
 * @Version 1.0
 */

@Slf4j
//@WebFilter(urlPatterns = {"/css/*","/imag/*"})   //设置拦截我们CSS和imag的所有文件

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
       log.info("MyFilter初始化完成.....");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("MyFilter工作中.....");
        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
        log.info("MyFilter初始化销毁.....");
    }
}

Listener:将注册的组件删除

package com.jsxs.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 * @Author Jsxs
 * @Date 2023/7/8 18:05
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyServletContextListener
 * @Description: TODO
 * @Version 1.0
 */

@Slf4j
//@WebListener
public class MyServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("MyServletContextListener监听器初始化完成.....");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("MyServletContextListener监听器销毁完成.....");
    }
}

MyRegisterConfig 配置类实现

package com.jsxs.config;

import com.jsxs.servlet.MyFilter;
import com.jsxs.servlet.MyServlet;
import com.jsxs.servlet.MyServletContextListener;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;

/**
 * @Author Jsxs
 * @Date 2023/7/8 18:14
 * @PackageName:com.jsxs.config
 * @ClassName: MyServlet
 * @Description: TODO
 * @Version 1.0
 */
@Configuration
public class MyRegisterConfig {
    @Bean
    public ServletRegistrationBean myServlet() {
        MyServlet myServlet = new MyServlet();  // //这个是我们写在Servlet包中的 MyServlet类

        return new ServletRegistrationBean(myServlet, "/my01", "/my02"); // 第一个参数是myServlet实列对象,第二个参数是映射的路径
    }


    @Bean
    public FilterRegistrationBean myFilter() {

        MyFilter myFilter = new MyFilter();  //这个是我们写在Servlet包中的 MyFilter类

        // 1.第一种

//      return new FilterRegistrationBean(myFilter,myServlet());  //第一个参数是MyFilter实列对象,第二个参数是ServletRegistrationBean对象 ->就是只拦截"/my01","/my02"这两个路径

        // 2.第二种

        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);  // MyFilter实列对象
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my", "/css/*"));  // 拦截的路径
        return filterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener() {
        MyServletContextListener mySwervletContextListener = new MyServletContextListener(); //这个是我们写在Servlet包中的 MyServletContextListener类
        return new ServletListenerRegistrationBean(mySwervletContextListener); //  MyServletContextListener实列对象
    }
}

在这里插入图片描述

8.嵌入式Servlet容器

(1).切换嵌入式Servlet容器
  • 默认支持的webServer
    • Tomcat, Jetty, or Undertow
    • ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
ServletWebServerApplicationContext: 174private void createWebServer() {
		WebServer webServer = this.webServer;
		// 1.获取servletContext容器
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
		// 2.创建服务器容器
			StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
			ServletWebServerFactory factory = getWebServerFactory();
			createWebServer.tag("factory", factory.getClass().toString());
		// ⭐⭐3.获取Tomcate容器
			this.webServer = factory.getWebServer(getSelfInitializer());
			createWebServer.end();
			getBeanFactory().registerSingleton("webServerGracefulShutdown",
					new WebServerGracefulShutdownLifecycle(this.webServer));
			getBeanFactory().registerSingleton("webServerStartStop",
					new WebServerStartStopLifecycle(this, this.webServer));
		}
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context", ex);
			}
		}
		initPropertySources();
	}
  • 切换服务器

在这里插入图片描述

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
  • 原理
    • SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat

    • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext

    • ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂—> Servlet 的web服务器)

    • SpringBoot底层默认有3个的WebServer工厂TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory

    • 底层直接会有一个自动配置类ServletWebServerFactoryAutoConfiguration类

    • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)

    • ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory

    • TomcatServletWebServerFactory 创建出Tomcat服务器并启动TomcatWebServer 的构造器拥有初始化方法initialize—this.tomcat.start();
      在这里插入图片描述

    • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)

(2).定制Servlet容器
  • 实现 WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>

    • 把配置文件的值和ServletWebServerFactory 进行绑定
  • 修改配置文件 server.xxx (第一种方式)
    在这里插入图片描述

  • 直接自定义 ConfigurableServletWebServerFactory (第二种)

因为我们项目已启动ServletWebServerFactory就会引导创建服务器,所以我们直接引入子类集大成者即可。

在这里插入图片描述

import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }

}

9.定制化原理

(1).定制化的常见方式
  • 修改配置文件
  • xxxxxCustomizer;
  • 编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件;视图解析器
  • Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件
@Configuration
public class AdminWebConfig implements WebMvcConfigurer
  • @EnableWebMvc + WebMvcConfigurer + @Configuration —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能
    • 原理

      • 1、WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页…
      • 2、一旦使用 @EnableWebMvc 、。会 @Import(DelegatingWebMvcConfiguration.class)
      • 3、DelegatingWebMvcConfiguration 的 作用,只保证SpringMVC最基本的使用
        • 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效 (也就是说手动生效了)
        • 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
        • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
      • 4、WebMvcAutoConfiguration 里面的配置要能生效 必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
        在这里插入图片描述
      • 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。
        ● … …
(2).原理分析套路

场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项

3.数据访问

1.SQL

(1).数据源的自动配置-HikariDataSource

(1.1) 、导入JDBC场景
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>        

在这里插入图片描述

数据库驱动?
为什么导入JDBC场景,官方不导入驱动?官方不知道我们接下要操作什么数据库(MySQL.Orangle)。

数据库版本和驱动版本对应

SpringBoot默认版本:<mysql.version>8.0.22</mysql.version>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
<!--            <version>5.1.49</version>-->
        </dependency>
        
想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、重新声明版本(maven的属性的就近优先原则)
    <properties>
        <java.version>1.8</java.version>
        <mysql.version>5.1.6</mysql.version>
    </properties>
(1.2) 、分析自动配置JDBC引入了啥

自动配置的类

  • DataSourceAutoConfiguration : 数据源的自动配置
    • 修改数据源相关的配置spring.datasource
    • 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
    • 底层配置好的连接池是:HikariDataSource
	@Configuration(proxyBeanMethods = false)
	@Conditional(PooledDataSourceCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
			DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
			DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
	protected static class PooledDataSourceConfiguration
  • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置

  • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud

    • 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
      在这里插入图片描述

    • @Bean@Primary JdbcTemplate;容器中有这个组件(所以我们自己使用即可)
      在这里插入图片描述

  • JndiDataSourceAutoConfiguration: jndi的自动配置

  • XADataSourceAutoConfiguration: 分布式事务相关的

(1.3) 、修改配置项
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_account
    username: root
    password: ******
    driver-class-name: com.mysql.jdbc.Driver

配置数据源成功且各项信息正确
在这里插入图片描述
引入了JDBC和驱动 对数据源没有进行配置
在这里插入图片描述

2.使用Druid数据源

HikariDataSource: 是世界上性能最好的数据源。
Druid: 是世界上安全属性最高的数据源。

(1).druid官方github地址

德鲁伊帮助文档
https://github.com/alibaba/druid
整合第三方技术的两种方式

  • 自定义
  • 找starter

(2).自定义方式 (使用德鲁伊数据源)

(2.1)、创建数据源
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>
(2.2)、Spring时代配置德鲁伊数据源

1. dataSource

主要作用: 链接数据库和开启监控和防火墙等功能。

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
		destroy-method="close">
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="maxActive" value="20" />
		<property name="initialSize" value="1" />
		<property name="maxWait" value="60000" />
		<property name="minIdle" value="1" />
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<property name="minEvictableIdleTimeMillis" value="300000" />
		<property name="testWhileIdle" value="true" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
		<property name="poolPreparedStatements" value="true" />
		<property name="maxOpenPreparedStatements" value="20" />
    /**
     *
     * @return  主要用途链接数据库和配置数据库信息
     */

    // @ConditionalOnMissingBean(DataSource.class) ->当数据中没有的话,才会使用默认的数据源
    @Bean // 这里有数据源了所以使用我们配置的数据源
//    @ConfigurationProperties("spring.datasource")  // 因为德鲁伊的链接名和Hik的连接名一致,我们可以使用这个
    public DataSource dataSource() throws SQLException {

        DruidDataSource druidDataSource = new DruidDataSource();

        // 1.链接数据库
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/demo1");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("121788");
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");

        // 2.打开SQL监控和防火墙
        druidDataSource.setFilters("stat,wall");

        System.out.println(druidDataSource.getClass());
        return druidDataSource;
    }

2. 配置 StatViewServle

StatViewServlet的用途包括:

  • 提供监控信息展示的html页面
  • 提供监控信息的JSON API
	<servlet>
		<servlet-name>DruidStatView</servlet-name>
		<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>DruidStatView</servlet-name>
		<url-pattern>/druid/*</url-pattern>
	</servlet-mapping>
    /**
     *
     * @return :  主要提供监控视图 和 JSON API的操作
     */

    @Bean
    public ServletRegistrationBean StatViewServlet(){
        StatViewServlet statViewServlet = new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
        return registrationBean;
    }

3.StatFilter

用于统计监控信息;如WEB监控URI监控

  <filter>
  	<filter-name>DruidWebStatFilter</filter-name>
  	<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
  	<init-param>
  		<param-name>exclusions</param-name>
  		<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
  	</init-param>
  </filter>
  <filter-mapping>
  	<filter-name>DruidWebStatFilter</filter-name>
  	<url-pattern>/*</url-pattern>
  </filter-mapping>
    /**
     *
     * @return  WebStatFilter用于采集web-URL关联监控的数据。
     */

    @Bean
    public FilterRegistrationBean WebStatFilter(){
        WebStatFilter webStatFilter = new WebStatFilter();
        FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean<>(webStatFilter);
        registrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //排除这些路径
        registrationBean.setUrlPatterns(Arrays.asList("/*"));   // 拦截这个路径

        return registrationBean;
    }
(2.3)、 配置类SpringBoot时代

在这里插入图片描述
在这里插入图片描述

package com.jsxs.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * @Author Jsxs
 * @Date 2023/7/9 14:28
 * @PackageName:com.jsxs.config
 * @ClassName: MyDuridConfig
 * @Description: TODO   德鲁伊数据源配置
 * @Version 1.0
 */

@Configuration
public class MyDruidConfig {
    /**
     *
     * @return  主要用途链接数据库和配置数据库信息
     */

    // @ConditionalOnMissingBean(DataSource.class) ->当数据中没有的话,才会使用默认的数据源
    @Bean // 这里有数据源了所以使用我们配置的数据源
//    @ConfigurationProperties("spring.datasource")  // 因为德鲁伊的链接名和Hik的连接名一致,我们可以使用这个
    public DataSource dataSource() throws SQLException {

        DruidDataSource druidDataSource = new DruidDataSource();

        // 1.链接数据库
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/demo1");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("121788");
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");

        // 2.打开SQL监控和防火墙
        druidDataSource.setFilters("stat,wall");

        System.out.println(druidDataSource.getClass());
        return druidDataSource;
    }

    /**
     *
     * @return :  主要提供监控视图 和 JSON API的操作
     */

    @Bean
    public ServletRegistrationBean StatViewServlet(){
        StatViewServlet statViewServlet = new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");

        // 1.添加监控的密码和账户
        registrationBean.addInitParameter("loginUsername","1");
        registrationBean.addInitParameter("loginPassword","1");
        return registrationBean;
    }

    /**
     *
     * @return  WebStatFilter用于采集web-URL关联监控的数据。
     */

    @Bean
    public FilterRegistrationBean WebStatFilter(){
        WebStatFilter webStatFilter = new WebStatFilter();
        FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean<>(webStatFilter);
        registrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //排除这些路径
        registrationBean.setUrlPatterns(Arrays.asList("/*"));   // 拦截这个路径

        return registrationBean;
    }
}

在这里插入图片描述

(3).使用官方starter方式

(3.1)、引入druid-starter
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>
@Deprecated   // 标注已经过时,

在这里插入图片描述

(3.2)、分析自动配置

我们发现德鲁伊的包中自己带了自动配置的包

在这里插入图片描述
并且按需导入的方式进行导入 (即屏蔽掉以前SpringBoot默认的数据源)
在这里插入图片描述

  • 原来的项: spring.datasource 依然存在
  • 扩展配置项 spring.datasource.druid
  • DruidSpringAopConfiguration.class, 监控SpringBean的配置;配置项:spring.datasource.druid.aop-patterns
  • DruidStatViewServletConfiguration.class, 监控页的配置spring.datasource.druid.stat-view-servlet;默认开启
  • DruidWebStatFilterConfiguration.class, web监控配置spring.datasource.druid.web-stat-filter;默认开启
  • DruidFilterConfiguration.class 所有Druid自己filter的配置
    private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
    private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
    private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
    private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
    private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
    private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
    private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
    private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
(3.3)、示列
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_account
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

    druid:
      aop-patterns: com.atguigu.admin.*  #监控SpringBean
      filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)

      stat-view-servlet:   # 配置监控页功能
        enabled: true
        login-username: admin
        login-password: admin
        resetEnable: false

      web-stat-filter:  # 监控web
        enabled: true
        urlPattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'


      filter:
        stat:    # 对上面filters里面的stat的详细配置
          slow-sql-millis: 1000
          logSlowSql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false

https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

Druid 高级配置属性列表…

3.Mybatis 操作数据库

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

在这里插入图片描述

(1).原始(Spring) - 配置模式

  • 全局配置文件
  • SqlSessionFactory: 自动配置好了
  • SqlSession:自动配置了 SqlSessionTemplate 组合了SqlSession
  • @Import(AutoConfiguredMapperScannerRegistrar.class);
  • Mapper: 只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class)MyBatis配置项绑定类。
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{}


@ConfigurationProperties(prefix = "mybatis")  前缀
public class MybatisProperties

MybatisX 插件。 这个插件可以实现映射跳转

在这里插入图片描述
命名空间指向的是 接口。 返回值结果是 实体类 。
在这里插入图片描述

(2).非注解的配置模式

application.yaml

mybatis:
  config-location: classpath:mybatis/mybatis-config.xml  #指定mybatis的核心文件位置
  mapper-locations: classpath:mybatis/mapper/*.xml #指定mybatis的映射文件位置

在这里插入图片描述

实体类

package com.jsxs.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author Jsxs
 * @Date 2023/7/17 11:01
 * @PackageName:com.jsxs.bean
 * @ClassName: Admin
 * @Description: TODO
 * @Version 1.0
 */

@Data
@AllArgsConstructor
@NoArgsConstructor

public class Admin {
    Integer id;
    String name;
    String password;
}

mapper层接口: mapper注解是为了让mybatis发现,respoity目的是为了让

package com.jsxs.mapper;

import com.jsxs.bean.Admin;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/17 11:01
 * @PackageName:com.jsxs.mapper
 * @ClassName: AdminMapper
 * @Description: TODO
 * @Version 1.0
 */

@Mapper
@Repository
public interface AdminMapper {

    // 1.查询所有的用户
    public List<Admin> allAdmin();
}

mybatis.xml映射SQL文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jsxs.mapper.AdminMapper">
    <select id="allAdmin" resultType="com.jsxs.bean.Admin">
        select *from admin;
    </select>
</mapper>

接口

package com.jsxs.service;

import com.jsxs.bean.Admin;
import com.jsxs.mapper.AdminMapper;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/17 13:39
 * @PackageName:com.jsxs.service
 * @ClassName: AdminService
 * @Description: TODO
 * @Version 1.0
 */


public interface AdminService {

    public List<Admin> allAdmin();
}

接口实现类

package com.jsxs.service.impl;

import com.jsxs.bean.Admin;
import com.jsxs.mapper.AdminMapper;
import com.jsxs.service.AdminService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/17 13:44
 * @PackageName:com.jsxs.service.impl
 * @ClassName: AdminServiceImpl
 * @Description: TODO
 * @Version 1.0
 */

@Service
public class AdminServiceImpl implements AdminService {

    @Resource
    AdminMapper adminMapper;


    @Override
    public List<Admin> allAdmin() {
        return  adminMapper.allAdmin();
    }
}

测试实验

package com.jsxs.service.impl;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @Author Jsxs
 * @Date 2023/7/17 13:47
 * @PackageName:com.jsxs.service.impl
 * @ClassName: AdminServiceImplTest
 * @Description: TODO
 * @Version 1.0
 */
@SpringBootTest
class AdminServiceImplTest {

    @Resource
    AdminServiceImpl adminService;

    @Test
    void test(){
        System.out.println(adminService.allAdmin());
    }
}

Mybatis 默认是不支持我们的数据库字段的驼峰命名的。假如说数据库的字段名的间隔以下划线分割,那么实体类应该写成驼峰命名。那么如果我们没有配置mybatis使用驼峰命名那么我们就会查询到的数据为null。

第一种: 使用全局配置文件配置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="multipleResultSetsEnabled" value="true"/>
    </settings>
</configuration>

第二种: 使用application.yaml

mybatis:
  config-location: classpath:mybatis/mybatis-config.xml  #指定mybatis的核心文件位置
  mapper-locations: classpath:mybatis/mapper/*.xml #指定mybatis的映射文件位置
  configuration:
    multiple-result-sets-enabled: true  ⭐

注意: 全局配置文件(mybatis-config)与application.yaml中只能存在一个对mybatis进行配置。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

基本步骤:

  • 导入mybatis官方starter
  • 编写mapper接口。标准@Mapper注解
  • 编写sql映射文件并绑定mapper接口
  • 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件的信息 (建议;配置在mybatis.configuration)

(3).纯注解的配置模式

如果我们使用注解开发的话,我们就可以省略掉xml映射文件而不是xml全局配置文件(mybatis-config.xml)

package com.jsxs.mapper;

import com.jsxs.bean.Admin;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/17 11:01
 * @PackageName:com.jsxs.mapper
 * @ClassName: AdminMapper
 * @Description: TODO
 * @Version 1.0
 */

@Mapper
@Repository
public interface AdminMapper {

    // 1.查询所有的用户
    public List<Admin> allAdmin();

    // 2.根据id进行查找数据  ⭐⭐
    @Select("select *from admin where id = #{id}")
    public Admin getAdminById(Long id);
}

Service 接口实现类

package com.jsxs.service;

import com.jsxs.bean.Admin;
import com.jsxs.mapper.AdminMapper;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/17 13:39
 * @PackageName:com.jsxs.service
 * @ClassName: AdminService
 * @Description: TODO
 * @Version 1.0
 */


public interface AdminService {

    public List<Admin> allAdmin();

    public Admin getAdminById(Long id);
}

Service接口

package com.jsxs.service.impl;

import com.jsxs.bean.Admin;
import com.jsxs.mapper.AdminMapper;
import com.jsxs.service.AdminService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/17 13:44
 * @PackageName:com.jsxs.service.impl
 * @ClassName: AdminServiceImpl
 * @Description: TODO
 * @Version 1.0
 */

@Service
public class AdminServiceImpl implements AdminService {

    @Resource
    AdminMapper adminMapper;


    @Override
    public List<Admin> allAdmin() {
        return  adminMapper.allAdmin();
    }

    @Override
    public Admin getAdminById(Long id) {
        return adminMapper.getAdminById(id);
    }
}

测试结果

package com.jsxs.service.impl;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @Author Jsxs
 * @Date 2023/7/17 13:47
 * @PackageName:com.jsxs.service.impl
 * @ClassName: AdminServiceImplTest
 * @Description: TODO
 * @Version 1.0
 */
@SpringBootTest
class AdminServiceImplTest {

    @Resource
    AdminServiceImpl adminService;

    @Test
    void test(){
        System.out.println(adminService.allAdmin());
        System.out.println(adminService.getAdminById(1L));
    }
}

在这里插入图片描述

(4).非注解和纯注解可以混合使用

我们只需要各自写各自的即可,即非注解的要写 xml映射文件和全局配置文件;非注解的不需要xml映射文件但需要全局文件

package com.jsxs.mapper;

import com.jsxs.bean.Admin;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/17 11:01
 * @PackageName:com.jsxs.mapper
 * @ClassName: AdminMapper
 * @Description: TODO
 * @Version 1.0
 */

@Mapper
@Repository
public interface AdminMapper {

    // 1.查询所有的用户
    public List<Admin> allAdmin();

    // 2.根据id进行查找数据
    @Select("select *from admin where id = #{id}")
    public Admin getAdminById(Long id);

    // 3.xinz
    @Insert("insert into admin(name,password) values(#{name},#{password})")
    @Options(useGeneratedKeys = true,keyColumn = "id")  // 指定主键是那个?
    public int add(String name,String password);
}

在这里插入图片描述

最佳实践

  • 引入mybatis-starter
  • 配置application.yaml中,指定mapper-location位置即可;config-location:核心配置文件就不需要了。核心配置文件最好直接再application.yaml中使用。
  • 编写Mapper接口并标注@Mapper注解
  • 简单方法直接注解方式
  • 复杂方法编写mapper.xml进行绑定映射
  • @MapperScan("com.atguigu.admin.mapper") 简化,其他的Mapper接口就可以不用标注@Mapper注解

4.Mybatis-Plus 操作数据库

(1).什么是MybatisPlus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
mybatis plus 官网 https://baomidou.com/
建议安装 MybatisX 插件

(2).整合Mybatisplus

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

在这里插入图片描述
帮助我们自动引入了JDBC和Mybatis的包,所以我们不需要再引入JDBC和Mybatis了

自动配置

  • MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对mybatis-plus的定制
  • SqlSessionFactory 自动配置好。底层是容器中默认的数据源
  • mapperLocations 自动配置好的。有默认值 (3.1.2下才有默认值)classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件,放在 mapper下
  • 容器中也自动配置好了 SqlSessionTemplate
  • @Mapper 标注的接口也会被自动扫描;建议直接 @MapperScan("com.atguigu.admin.mapper") 批量扫描就行

优点:

  • 只需要我们的Mapper继承 BaseMapper 就可以拥有crud能力。
  1. 数据库层继承的是: BaseMapper
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml  #3.1.2版本之后有默认的值了。

实体类

为什么说:我们这里写类名为 Admin ; 然后Mybatisplus是怎么找到数据库的具体表的? 因为默认有一个注解 @TableName(“xxx”) xxx默认是写的是类名,就会通过这个类名去数据库中寻找。如果表名更改了,那么我们可以通过这个注解更改映射的关系。

package com.jsxs.bean;

import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author Jsxs
 * @Date 2023/7/17 11:01
 * @PackageName:com.jsxs.bean
 * @ClassName: Admin
 * @Description: TODO
 * @Version 1.0
 */

@Data
@AllArgsConstructor
@NoArgsConstructor

public class Admin {
    @TableField(exist = true)  // 假如我们需要临时的属性,但这个属性不存在我们可以再上面添加一个注解 @TableField(exist = false)表示在表中不存在
    Integer id;
    String name;
    String password;
}

Mapper接口-> 需要继承一个接口

package com.jsxs.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jsxs.bean.Admin;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/17 11:01
 * @PackageName:com.jsxs.mapper
 * @ClassName: AdminMapper
 * @Description: TODO  继承接口之后我们不需要再编写 CRUD 就自带的已经存在了
 * @Version 1.0
 */

@Mapper
@Repository
public interface AdminMapper extends BaseMapper<Admin> {   // 需要继承BaseMapper,因为这个父类有很多已经分装好的方法


}

测试实现

package com.jsxs.mapper;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @Author Jsxs
 * @Date 2023/7/18 11:03
 * @PackageName:com.jsxs.mapper
 * @ClassName: AdminMapperTest
 * @Description: TODO
 * @Version 1.0
 */
@SpringBootTest
class AdminMapperTest {

    @Resource
    AdminMapper adminMapper;

    @Test
    void test(){
        System.out.println(adminMapper.selectById(1));
    }
}

在这里插入图片描述

  1. 业务层接口继承的是 IService<操作的实体类名> ; 业务层实现类继承业务层的实现类要继承ServiceImpl<要实现表的BaseMapper,操作表对应的实体类名> 且 要实现业务层的接口

实体类

package com.jsxs.bean;

import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author Jsxs
 * @Date 2023/7/17 11:01
 * @PackageName:com.jsxs.bean
 * @ClassName: Admin
 * @Description: TODO
 * @Version 1.0
 */

@Data
@AllArgsConstructor
@NoArgsConstructor

public class Admin {
    @TableField(exist = true)  // 假如我们需要临时的属性,但这个属性不存在我们可以再上面添加一个注解 @TableField(exist = false)表示在表中不存在
    Integer id;
    String name;
    String password;
}

Mapper接口

package com.jsxs.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jsxs.bean.Admin;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/17 11:01
 * @PackageName:com.jsxs.mapper
 * @ClassName: AdminMapper
 * @Description: TODO  继承接口之后我们不需要再编写 CRUD 就自带的已经存在了
 * @Version 1.0
 */

@Mapper
@Repository
public interface AdminMapper extends BaseMapper<Admin> {   // 需要继承BaseMapper,因为这个父类有很多已经分装好的方法


}

Service 接口

package com.jsxs.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.jsxs.bean.Admin;

/**
 * @Author Jsxs
 * @Date 2023/7/18 11:26
 * @PackageName:com.jsxs.service
 * @ClassName: AdminService
 * @Description: TODO  我们业务层的接口只需要实现IService<Admin>
 * @Version 1.0
 */

public interface AdminService extends IService<Admin> {
}

业务层实现类接口:

package com.jsxs.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jsxs.bean.Admin;
import com.jsxs.mapper.AdminMapper;
import com.jsxs.service.AdminService;
import org.springframework.stereotype.Service;

import java.io.Serializable;

/**
 * @Author Jsxs
 * @Date 2023/7/18 11:27
 * @PackageName:com.jsxs.service.impl
 * @ClassName: AdminServiceImpl
 * @Description: TODO  业务层的实现类要继承ServiceImpl<要实现表的BaseMapper,操作表对应的实体类名> 且 要实现业务层的接口
 * @Version 1.0
 */
@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper,Admin> implements AdminService {

}

测试类

package com.jsxs.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jsxs.bean.Admin;
import com.jsxs.service.AdminService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @Author Jsxs
 * @Date 2023/7/18 11:44
 * @PackageName:com.jsxs.service.impl
 * @ClassName: AdminServiceImplTest
 * @Description: TODO
 * @Version 1.0
 */

@SpringBootTest
@Slf4j
class AdminServiceImplTest {

    @Resource
    AdminService adminService;

    @Test
    void test(){
        System.out.println(adminService.getById(1));

        Page<Admin> page = new Page<>(1, 2); //当前页(初始页为1),一页几条

        IPage<Admin> iPage = adminService.page(page, null);  // Page分页的实列
        log.info("全部数据为: {}",iPage.getRecords()); // 分页遍历的话,遍历这个
        log.info("当前业为: {}",iPage.getCurrent());
        log.info("总多少页: {}",iPage.getPages());
        log.info("总多少条: {}",iPage.getTotal());
    }

}

假如说查询到的数据都为0,那么就是没有配置分页插件

package com.jsxs.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author Jsxs
 * @Date 2023/7/18 13:05
 * @PackageName:com.jsxs.config
 * @ClassName: MybatisPlusConfig
 * @Description: TODO
 * @Version 1.0
 */
@Configuration
public class MybatisPlusConfig {
    //  分页插件
    @Bean
    public PaginationInterceptor paginationinterceptor(){
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        return paginationInterceptor;
    }
}

在这里插入图片描述

为何Controller中调用接口就能实现接口实现类里的方法?

  1. 如何在删除之后仍然在原来的页面 ?

我们只需要在前端的删除按钮上绑定上当前页的页码,然后在后端进行页码的重定向跳转。
前端的操作:
在这里插入图片描述
后端的操作
在这里插入图片描述

5.整合Redis 非SQL数据库

Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。

(1).Redis 自动配置

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

在这里插入图片描述
自动配置:

  • RedisAutoConfiguration 自动配置类。RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
  • 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration (默认配置好了客户端)
  • 自动注入了RedisTemplate<Object, Object> : xxxTemplate;
  • 自动注入了StringRedisTemplate;k:v都是String
  • key:value
  • 底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis

redis环境搭建

如果我们使用window的话,我们在启动redis服务端的时候需要指定使用哪个配置 redis.windows-service.conf redis.windows.conf。在这里我们使用redis.windows.conf设定了密码。

两种打开方式:

  1. 键入“cmd”切到安装目录后输出redis-server.exe redis.windows.conf,回车,就可以了。

  2. 在redis安装目录下新建文件startup.bat后,右击“编辑”,或者先用记事本建立该文件,再把扩展名改一下,文件里面写上:redis-server.exe redis.windows.conf。保存,以后再运行就直接运行这个文件,不要再直接运行redis-server.exe了,就可以了

在这里插入图片描述
在这里插入图片描述

(2).RedisTemplate与Lettuce

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: xxxxx
package com.jsxs.mapper;

import com.alibaba.fastjson2.JSON;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import javax.annotation.Resource;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @Author Jsxs
 * @Date 2023/7/18 11:03
 * @PackageName:com.jsxs.mapper
 * @ClassName: AdminMapperTest
 * @Description: TODO
 * @Version 1.0
 */
@SpringBootTest
class AdminMapperTest {


    @Resource
    RedisTemplate redisTemplate;

    @Test
    void test2(){
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();

        opsForValue.set(JSON.toJSONString("hello5"),JSON.toJSONString("hello5"));
        System.out.println(opsForValue.get("hello5"));

    }
}

在这里插入图片描述

需要进行序列化处理

没有对 redisTemplate 进行序列化处理,java 代码会将 key 转化成对应的十六进制的数值进行操作.

注意事项: 我们不管通过 Java代码/Redis客户端 进行设置值还是获取值,我们都需要对其进行JSON(如果不是java会获取不到值)处理并且序列化(序列化可以避免默认的16进制)

package com.jsxs.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.net.UnknownHostException;

/**
 * @Author Jsxs
 * @Date 2023/7/20 13:56
 * @PackageName:com.jsxs.config
 * @ClassName: RedisConfig
 * @Description: TODO
 * @Version 1.0
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        // 将template 泛型设置为 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate();
        // 连接工厂,不必修改
        template.setConnectionFactory(redisConnectionFactory);
        /*
         * 序列化设置
         */
        // key、hash的key 采用 String序列化方式
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // value、hash的value 采用 Jackson 序列化方式
        template.setValueSerializer(RedisSerializer.json());
        template.setHashValueSerializer(RedisSerializer.json());
        template.afterPropertiesSet();

        return template;
    }
}

假如说key键值对的值是用的JOSN字符串,那么Java端获取的值就是JSON字符串。如果键值对的值用的是非JSON字符串,那么Java后端获取的值就是非JSON字符串。
在这里插入图片描述
总结: 以后开发全部使用JSON字符串

(3).切换至jedis

客户端类型有: lettuce 和 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>
  redis:
    host: 127.0.0.1
    port: 6379
    password: 121788
    client-type: jedis  #指定客户端的类型为jedis
    @Test
    void test3(){
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.auth("121788");
        System.out.println(jedis.ping());
        jedis.set("username1","123456");
        System.out.println(jedis.get("username1"));
    }

总结: 不管是 Letture 还是Jedis客户端,这两个客户端都需要用到序列化和 JSON

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/43441.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【STL】模拟实现简易 list

目录 1. 读源码 2. 框架搭建 3. list 的迭代器 4. list 的拷贝构造与赋值重载 拷贝构造 赋值重载 5. list 的常见重要接口实现 operator--() insert 接口 erase 接口 push_back 接口 push_front 接口 pop_back 接口 pop_front 接口 size 接口 clear 接口 别…

Window环境RabbitMq搭建部署

Erlang下载安装及配置环境变量 下载erlang&#xff0c;原因在于RabbitMQ服务端代码是使用并发式语言Erlang编写的 Erlang下载 Erlang官网下载&#xff1a; http://www.erlang.org/downloads Erlang国内镜像下载&#xff08;推荐&#xff09;&#xff1a; http://erlang.org/d…

旧版Xcode文件较大导致下载总是失败但又不能断点续传重新开始的解决方法

问题&#xff1a; 旧版mac下载旧版Xcode时需要进入https://developer.apple.com/download/all/?qxcode下载&#xff0c;但是下载这些文件需要登录。登录后下载中途很容易失败&#xff0c;失败后又必须重新下载。 解决方案&#xff1a; 下载这里面的内容都需要登录&#xff0…

Appium+python自动化(十九)- Monkey(猴子)参数(超详解)

前边几篇介绍了Monkey以及Monkey的事件&#xff0c;今天就给小伙伴们介绍和分享一下Monkey的参数。 首先我们看一下这幅图来大致了解一下&#xff1a; 1、Monkey 命令 基本参数介绍 -p <允许的包名列表> 用此参数指定一个或多个包。指定包之后&#xff0c;mon…

用html+javascript打造公文一键排版系统7:落款排版

一、公文落款的格式 公文落款包括单位署名和成文日期两个部分&#xff0c;其中成文日期中的数字 用阿拉伯数字将年、月、日标全&#xff0c;年份应标全称&#xff0c;月、日不编虚位&#xff08;即 1 不编为 01&#xff09;。 在实际应用工作中分为三种情况&#xff1a; &am…

【Selenium+Pytest+allure报告生成自动化测试框架】附带项目源码和项目部署文档

目录 前言 【文章末尾给大家留下了大量的福利】 测试框架简介 首先管理时间 添加配置文件 conf.py config.ini 读取配置文件 记录操作日志 简单理解POM模型 简单学习元素定位 管理页面元素 封装Selenium基类 创建页面对象 简单了解Pytest pytest.ini 编写测试…

保护数字世界的壁垒

随着科技的不断发展和互联网的普及&#xff0c;我们的生活日益依赖于数字化的世界。然而&#xff0c;随之而来的是网络安全威胁的不断增加。网络攻击、数据泄露和身份盗窃等问题已经成为我们所面临的现实。因此&#xff0c;网络安全变得尤为重要&#xff0c;我们需要采取措施来…

什么是分布式操作系统?我们为什么需要分布式操作系统?

分布式操作系统是一种特殊的操作系统&#xff0c;本质上属于多机操作系统&#xff0c;是传统单机操作系统的发展和延伸。它是将一个计算机系统划分为多个独立的计算单元(或者也可称为节点)&#xff0c;这些节点被部署到每台计算机上&#xff0c;然后被网络连接起来&#xff0c;…

Win10环境下Android Studio中运行Flutter HelloWorld项目

一、引言 Android Studio是Android的官方IDE(Integrated Development Environment)。它专为Android而打造&#xff0c;可以加快开发速度&#xff0c;为Android设备构建最高品质的应用。 Flutter是Google推出并开源的移动应用开发框架&#xff0c;主打跨平台、高保真、高性能。开…

【STL】list用法试做_底层实现

目录 一&#xff0c;list 使用 1. list 文档介绍 2. 常见接口 1. list中的sort 2. list sort 与 vector sort效率对比 3. 关于迭代器失效 4. clear 二&#xff0c;list 实现 1.框架搭建 2. 迭代器类——核心框架 3. operator-> 实现 4. const——迭代…

【计算机网络 01】说在前面 信息服务 因特网 ISP RFC技术文档 边缘与核心 交换方式 定义与分类 网络性能指标 计算机网络体系结构 章节小结

第一章--概述 说在前面1.1 计算机网络 信息时代作用1.2 因特网概述1.3 三种交换方式1.4 计算机网络 定义与分类1.5 计算机网络的性能指标1.6 计算机网络体系结构1 常见的计算机网络体系结构2 计算机网络体系结构分层的必要性3 计算机网络体系结构分层思想举例4 计算机网络体系结…

RuntimeError: DataLoader worker (pid 2105929) is killed by signal: Killed.

PyTorch DataLoader num_workers Test - 加快速度 可以利用PyTorch DataLoader类的多进程功能来加快神经网络训练过程。 加快训练进程 为了加快训练过程&#xff0c;我们将利用DataLoader类的num_workers可选属性。 num_workers属性告诉DataLoader实例要使用多少个子进程进…

23.多项式与非多项式曲线拟合对比(matlab程序)

1.简述 拟合标准&#xff1a; (1)原始数据向量与拟合向量之间的距离最小&#xff0c;该距离的度量一般使用误差平方和表示&#xff0c;即均方误差&#xff1a;R||Q-Y||22 (2)当均方误差最小时&#xff0c;说明构造的拟合向量与原始向量最为接近&#xff0c;这种曲线拟合的方法…

git commit -m时候没有保存package.json等文件

项目场景&#xff1a; 提示&#xff1a;git add . 和 git commit -m "保存" 操作&#xff0c;没有保存package.json等文件。 解决方案&#xff1a; 1.确保 package.json 文件没有被列在 .gitignore 文件中。打开 .gitignore 文件&#xff0c;检查是否有类似于 packa…

论文工具——ChatGPT结合PlotNeuralNet快速出神经网络深度学习模型图

文章目录 引言正文PlotNeuralNet安装使用使用python进行编辑使用latex进行编辑 样例利用chatGPT使用chatGPT生成Latex代码利用chatGPT生成对应的python代码 总结引用 引言 介绍如何安装PlotNeuralNet工具&#xff0c;并结合chatGPT减少学习成本&#xff0c;快速出图。将按照软…

4.2 Bootstrap HTML编码规范

文章目录 Bootstrap HTML编码规范语法HTML5 doctype语言属性IE 兼容模式字符编码引入 CSS 和 JavaScript 文件HTML5 spec links 实用为王属性顺序布尔&#xff08;boolean&#xff09;型属性减少标签的数量JavaScript 生成的标签 Bootstrap HTML编码规范 语法 用两个空格来代替…

通过 EXPLAIN 分析 SQL 的执行计划

通过 EXPLAIN 分析 SQL 的执行计划 EXPLAIN SELECTleave_station_area_id,ROUND( ( SUM( station_dist ) / 1000 ) / ( SUM( station_travel_time ) / 60 ), 2 ) evnPeakAvgSpeedFROMV3_SHIFT_ANALYSISWHERESTAT_DATE DATE_SUB( CURRENT_DATE, INTERVAL 1 DAY )AND LEAVE_STA…

NetSuite财务报表General Ledger Report的缺陷及改造案例

本周有用户提到一个特殊的业务场景&#xff0c;比较有代表性&#xff0c;在此分享。 问题 “如果在一张JE中&#xff0c;某个科目既有借又有贷&#xff0c;金额相同。那么在General Ledger Report中此JE的借贷都显示为0。这与事实不符&#xff0c;所以是不对的。” JE 155&a…

vue3-element-plus,控制表格多选的数量

1. 需求描述 控制表格的多选&#xff0c;最多只能选择5条数据&#xff0c;并且其他项禁用 2. 需求描述 <!-- selection-change 当选择项发生变化时会触发该事件--><template><el-tableref"multipleTableRef"v-loading"loading":data"…

[Linux] CentOS7 中 pip3 install 可能出现的 ssl 问题

由于解决问题之后, 才写的博客, 所以没有图片记录. 尽量描述清楚一些 今天写代码的时候, 突然发现 文件里用了#define定义宏之后, coc.nvim的coc-clangd补全就用不了 :checkhealth了一下, 发现nvim忘记支持python3了 尝试pip3 install neovim的时候, 发现会警告然后安装失败.…