SpringBoot2 - 基础入门【一 ~ 五】,详见:
六、配置文件
6.1 properties文件格式
同之前的用法。
6.2 yaml文件格式【推荐】
YAML本意:“YAML”不是一种标记语言。但在开发中,实际把它理解为:“Yet Another Markup Language”(仍是一种标记语言)
戏称为:薛定谔的YAML
● 非常适合以数据为中心的配置文件
● 后缀:.yaml 或 .yml
【语法】
(1) key: value
● 对于数组、List、Set的每个元素:
● 行内写法:[x1,x2,x3, ...]
● 层次写法:每一层使用 - x1
● 对于Map、Object的每个元素:
● 行内写法:{key1: value1, key2: value2, ...}
● 层次写法:空格缩进后,每一层直接写key : value
(2) 使用空格缩进表示层级关系
● 数量不重要,只要相同层级对齐即可
● 不允许使用tab,只允许空格,但在IDE中我们不用关心
(3) 字符串我们可以不用加引号
● '...' 单引号:表示保留字符串原始内容,尽管是转义字符,也不会进行转义
● "..." 双引号:表示会将转义字符转义
(4) 大小写敏感
(5) #表示注释
e.g
@Data
public class Person{private String userName;private Boolean boss;private Date birth;private Integer age;private Pet pet;private String[] interests;private List<String> animals;private Map<String, Object> scores;private Set<Double> salarys;private Map<String, List<Pet>> allPets;
}
@Data
class Pet{private String name;private Double weight;
}application.yml
person: userName: zhangsanboss: truebirth: 2019/12/9age: 18interests: - 篮球- 足球- 18动漫animals: [阿猫, 阿狗]scores: english: 80math: 90salarys: - 9999.98- 9999.99pet: name: 阿狗weight: 99.99allPets: sick: - {name: 阿狗, weight: 99.99}- name: 阿猫- weight: 88.88- name: 阿虫- weight: 77.77health: - {name: 阿花, weight:199.99}- {name: 阿明, weight:11.55}
⭕ 自定义类绑定的配置提示(配置处理器)
<dependency> <groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><!-- 某个依赖项是可选的。这意味着当其他项目依赖于该项目时,该依赖项不会自动传递给其他项目,除非其他项目也显式地声明了该依赖项。 --><optional>true</optional>
</dependency><!-- 官方建议:移除这个配置处理器,新版本SpringBoot自动移除了 -->
<build><plugins><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin<artifactId><configuration><excludes><exclude><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor<artifactId></exclude></excludes></configuration></plugins>
</build>
七、Web开发
7.1 静态资源
7.1.1 存放静态资源的目录
(1) 默认情况下,当用户键入:当前项目根路径/ + 静态资源文件
SpringBoot就会在项目的/static(或/public、/resources、/META-INF/resources)目录下查找静态资源
● 原因:默认的静态资源映射路径为:/**
当请求进来后,先会去Controller处理器中看看能不能处理,如果不能处理,则交给默认的静态资源处理器去查找。
(2) 修改存放静态资源的目录
SpringBoot会在你修改后的目录中找静态资源
application.yaml
spring:resources:static-locations: [classpath:/myResource/]add-mappings: false # 这里可以禁用查找静态资源(默认是true)
7.1.2 静态资源的映射路径
(1) 默认情况下:无前缀(即/**)
(2) 修改静态资源的映射路径【推荐】
这样当我们后续设置Filter时,不会拦截到静态资源。
application.yaml
spring:mvc:static-path-pattern: /res/**
● 当用户键入:当前项目 + static-path-pattern + 静态资源名 = SpringBoot就会前往静态资源目录中寻找对应的资源
7.1.3 webjar(了解)
一些第三方静态资源也打成了jar包,当需要获取时,可以通过加上前缀:/webjar/...
7.1.4 欢迎页和自定义Favicon
(1) 欢迎页
当用户键入项目根目录时,Spring Boot会:
● 先找ReqeustMappingHandlerMapping【保存了我们所有controller的映射规则】,即查找@RequestMapping("/")的处理器;
● 找不到,再去找WelcomePageHandlerMapping,查找静态资源目录下的欢迎页作为默认首页。还找不到,才会去寻找模板,即对应的Controller中写了@RequestMapping("/index")并返回"index"视图名的处理器方法。
⭕ 注意:当配置了自定义的静态资源的映射路径时,用户键入项目根路径将无法找到index.html这类静态资源。
(2) favicon
在静态资源目录下,放入名为"favicon.ico"的静态资源,则会自动的将icon显示,作为网页的图标。
⭕ 注意:当配置了自定义的静态资源的映射路径时,用户键入项目根路径将无法显示favicon.ico图标。
7.2 请求参数处理
7.2.1 支持Rest风格的HiddenHttpMethodFilter
在SpringBoot的WebMvcAutoConfiguration自动配置类中,已经帮我们自动配置了一个该过滤器对象,但是它需要配置文件的支持!
⭕ 如果是表单中使用,需要手动开启HiddenHttpMethodFilter!
application.yaml
spring: mvc: hiddenmethod: filter: enabled: true
因为有很多客户端(如Postman),可以发送DELETE、PUT等请求,这一项是选择性开启。
7.2.2 自定义转换器
(若干个)参数解析器中大多都会有一个类WebDataBinder,它用来做数据绑定,里面有一个conversionService对象,注册了(若干)转换器converter,用来做数据类型的转换。
⭕ 假设现在有一个需求:表单中提交name="pet",value="阿猫, 18";服务器(SpringBoot)的handler中参数接收一个Pet类型的数据。
● 默认converter没有办法处理这种String→Pet的转换,会报错。
● 我们自定义一个converter,并把它注册进容器中。
//1、WebMvcConfigurer定制化SpringMVC的功能@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {UrlPathHelper urlPathHelper = new UrlPathHelper();// 不移除;后面的内容。矩阵变量功能就可以生效urlPathHelper.setRemoveSemicolonContent(false);configurer.setUrlPathHelper(urlPathHelper);}@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addConverter(new Converter<String, Pet>() {@Overridepublic Pet convert(String source) {// 啊猫,3if(!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;}});}};}
7.3 数据响应和内容协商
7.3.1 原理
SpringBoot【处理@ResponseBody响应】中,根据handler返回值类型(假设是自定义Person类)确定一个返回值处理器(有若干个),在这个处理器内部,先获取浏览器可以接收的数据类型,然后去(第一次)遍历HttpMessageConverter,根据【canWrite方法】,拿到所有可以将Person写成别的数据类型的转换器,将这些转换器支持的MediaType保存起来,即获取服务器可以写出的数据类型,然后做浏览器和服务器的【内容协商】,确定最终匹配的MediaType(可能有多个),最后,(第二次)遍历MessageConverter,仍然根据【canWrite方法】,找到含有最终匹配MediaType的这些转换器。然后按照优先级排序,选出【第一个】可以将Person转换成最终的MediaType的转换器,使用它将Person转换为最终格式并返回给浏览器。
● 第一次遍历HttpMessageConverter:服务器具备写哪些数据格式的能力
● 第二次遍历HttpMessageConverter:浏览器和服务器协商后,找到可以对应的转换器
⭕ 另:canRead:带有@RequestBody的请求参数值是否可以读成handler形参中的类型。
7.3.2 如何响应Json和xml
响应json:
● json相关依赖 + @ResponseBody
(1) json相关依赖
引入spring-boot-starter-web场景启动器,内部就含有spring-boot-starter-json场景启动器,主要是通过jackson来实现的。
(2) 在handler方法或者类上标注@ResponseBody
响应xml:
(1) 引入xml依赖
由于spring-boot-starter-web中没有内置对xml的解析支持,所以需要引入额外的依赖。
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
</dependency>
(2) 在handler方法或者类上标注@ResponseBody
7.3.3 开启浏览器:使用请求参数,来进行内容协商
spring:contentnegotiation:favor-parameter: true #开启请求参数内容协商模式
发请求:(带上键为"format"的请求参数,如:json)
http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml
7.3.4 自定义MessageConverter(修改服务器需要的MediaType)
假定业务需求,有一种自定义的输出格式"application/myFormat",它要求将Person的属性值按照分号间隔,返回给客户端。
在@Confiugration配置类中添加WebMvcConfig的Bean组件,并在里面添加自定义的MessageConverter功能。
省略了Person的JaveBean@RestController
public class testController{@GetMapping("/test")public Person getPerson(){Person p = new Person("张三", 18);return p;}
}// 自定义的转换器
class MyMessageConverter implements HttpMessageConverter<Person>{@Overridepublic boolean canRead(Class<?> clazz, MediaType mediaType){return false; // 这部分是@requestBody需要做的,我们现在不关心}// 自定义判断是否可写@Overridepublic boolean canWrite(Class<?> clazz, MediaType mediaType){return clazz.isAssignableFrom(Person.class);}@Overridepublic List<MediaType> getSupportMediaTypes(){return MediaType.parseMediaType("application/myFormat"); // 自定义MediaType,即可以处理的数据类型}@Overridepublic Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage)throws IOException{return null; // 这部分是@requestBody需要做的,我们现在不关心}// 自定义如何将Person写出 @Overridepublic Person write(Class<? extends Person> clazz, HttpOutputMessage outputMessage)throws IOException{String data = person.getUserName + ";" + person.getAge();OutputStream os = outputMessage.getBody()os.write(data.getBytes());}
}在我们的含有@Configuration配置类中自定义WebMvcConfig的Bean组件,对其中的功能(这里是添加MessageConverter)进行定制。@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {// 这个方法是追加MessageConverter,并不会覆盖掉原来的@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new MyMessageConverter()); // 添加自定义MessageConverter}}}
7.3.5 自定义ContentNegotiationStrategy(通过携带自定义参数,修改浏览器需要的MediaType)
开启【基于参数format的】,在WebMvcConfigurer里配置一个新的ParameterContentNegotiationStrategy
在我们的含有@Configuration配置类中自定义WebMvcConfig的Bean组件,对其中的功能(这里是覆盖内容协商管理器的策略)进行定制。@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {// 配置自定义内容协商器@Overridepublic void configureContentNegotiation(ContentNegotiationConfigurer configurer) {Map<String, MediaType> mediaTypes = new HashMap<>();map.put("json", MediaType.APPLICATION_JSON);map.put("xml", MediaType.APPLICAION_XML);map.put("myFormat", MediaType.parseMediaType("application/myFormat")); // 自定义MediaType数据格式ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);configurer.strategies(Arrays.asList(strategy)); //【这里会覆盖原有的内容协商管理器的策略,可以使用别的方法,实现新增】// configurer.strategies(Arrays.asList(strategy, new HeaderContentNegotiationStrategy());}}}
● 使用这种方法会覆盖原有的请求头内容协商策略,导致默认功能失效,我们尽量修改的【原则是新增,而不是覆盖】!(参照SpringBoot用别的方法开发自定义内容协商部分)
7.4 视图解析与模板引擎
SpringBoot默认不支持JSP(需要服务器解压,并提供Java编译器,带来额外的负担),需要引入第三方模板引擎技术(自身的模板引擎技术是支持JSP的)。
● 引入Thymeleaf模板引擎场景启动器
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
</dependencies>
它帮我们配置了ThymeleafViewResolver,并绑定到了ThymeleafProperties类。
7.4.1 视图解析原理
(1) 所有的handler执行完成后会返回ModelAndView对象;
(2) DispatcherServlet调用processDispatchResult方法,遍历各个视图解析器,根据视图名称确定可以处理的解析器;
(3) 解析器会给我们返回View对象(View是接口,里面有render方法定义了渲染逻辑)
(4) View调用render方法渲染视图
e.g:
● RedirectView:render方法最终会调用:
response.sendRedirect("浏览器解析的路径");
● InternalResourceView:render方法最终会调用:
request.getRequestDispatcher("服务器解析的路径").forward(request, response);
● TymeleafView:有自己的渲染规则...
7.5 拦截器 Interceptor
通常情况下,我们除了登录页面之外,其他页面的都需要用户登录后才能进入,我们会使用拦截器实现这个功能。
(1) 自定义拦截器
/*** 登录检查* 1、配置好拦截器要拦截哪些请求* 2、把这些配置放在容器中*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {/*** 目标方法执行之前* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic 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","请先登录");
// re.sendRedirect("/");request.getRequestDispatcher("/").forward(request,response);return false;}/*** 目标方法执行完成以后* @param request* @param response* @param handler* @param modelAndView* @throws Exception*/@Overridepublic 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*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("afterCompletion执行异常{}",ex);}
}
(2) 将拦截器添加注册到WebMvcConfigurer中
/*** 1、编写一个拦截器实现HandlerInterceptor接口* 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)* 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】*/
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") //所有请求都被拦截包括静态资源.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求}
}
拦截器的原理详见SpringMVC:框架3:SpringMVC - 简书 (jianshu.com)
7.6 文件上传
SpringBoot中帮我们自动配置了【MultipartAutoConfiguration】文件上传自动配置类,它里面帮我们配置好了StandardServletMultipartResolver文件上传解析器,并和MultipartProperties绑定(我们可以通过修改application的全局配置文件来修改,诸如:单个文件上传最大大小、整个请求上传的最大大小...)。
● @RequestPart注解:当有多个文件需要上传时,显式指定提交表单项的"name"名称,用于确定哪一个上传的文件。
(当然如果页面中使用<input type="file" name="files" multiple/>,handler中可以使用形参MultipartFile[]接收)
7.6.1 文件上传源码分析
(1) DispatcherServlet中的doDispach()方法,接收请求,然后会使用multipartResolver文件上传解析器判断当前是不是一个分段请求(即文件上传请求),如果是:会将当前Request封装成新的MultipartHttpServletRequest对象。
⭕ 具体的判断条件:
multipartResolver.isMultipart(request);
↓
StringUtils.startWithIgnoreCase(request.getContentType(), prefix:"multipart/");
(2) 然后DispatcherServlet再去找到对应的HandlerMapping,做后续的处理……
(3) 在使用HandlerAdapter处理器适配器去执行handler时,匹配到RequestPartMethodParameterResolver参数解析器,会把封装过的request对象中的文件封装成MultipartFile对象,放到handler的形参列表中,对应的handler就可以进行处理了。
7.7 异常处理
SpringBoot在默认的情况下,对于来自机器客户端的错误,将生成JSON响应,其中包括错误、HTTP状态、异常消息的详细信息;对于浏览器的错误,会响应一个"whitelabel"错误视图,以HTML格式呈现同样的数据。
7.7.1 ErrorMvcAutoConfiguration
这是SpringBoot为异常处理提供的自动配置类。里面主要包含三大组件:
(1) DefaultErrorAttributes——handlerExceptionResolver实现类之一
● 作用:它主要是用来定义错误页面可以包含哪些数据,并将它保存到request域中,以便在错误页面展示。
(2) BasicErrorController——handler
● 作用:可以理解为是一个普通的handler,但他是专门处理默认的"/error"请求。默认情况下(没有任何的自定义处理异常),会使用SpringBoot放在容器中的默认错误页,即id为"error"的View组件【这就是浏览器错误白页的由来】。
(3) DefaultErrorViewResolver——视图解析器
● 作用:ErrorMvcAutoConfiguration默认配置,专门用来处理异常视图的解析器。它首先会先根据状态响应码,对状态码进行匹配。先进行精确匹配,如"404";如果匹配不到,则进行序列匹配,如"4xx"、"5xx"【可以在"/error"目录下,自定义4xx、5xx、400等html页面】。如果找不到,则解析上述的"error"默认错误视图。
7.7.2 异常处理步骤
(1) 执行目标方法,如果方法运行期间出现异常,会被捕获,并标志请求结束。
(2) 进入视图解析阶段,调用processDispatchResult(request, response, handler, mv, exception)方法,派发返回页面。
● 遍历所有的handlerExceptionResolver,看谁能处理当前异常。【HandlerExceptionResolver是一个接口,它里面只定义了一个方法,即resolveException,如何处理异常,返回类型是ModelAndView】
a) 第一个执行的解析器是 DefaultErrorAttributes,它仅 定义了将哪些错误信息放到request域,供错误页面使用,返回ModelAndView为null。
b) 执行第二个解析器,该解析器中定义了三个自带的异常处理解析器。
● ExceptionHandlerExceptionResolver:携带了注解@ExceptionHandler,并能够处理当前异常的解析器【它就是@ControllerAdvice + @ExceptionHandler 的底层支持】
● ResponseStatusExceptionResolver:携带了注解@ResponseStatus,并能够处理当前异常的解析器【它就是@ResponseStatus 的底层支持】
● DefaultHandlerExceptionResolver:SpringBoot专门用来处理内部异常的解析器,如参数不存在...
c) 自己实现了HandlerExceptionResolver接口的解析器类【可以通过@Order来更改优先级】
● 只要上述任意一个异常处理解析器返回的ModelAndView对象不为空,则直接跳出。如果找到了解析器,那么就使用该解析器解析,处理异常;如果所有的异常处理解析器都无法处理,那么会直接将异常抛出去,给Tomcat处理。但是SpringBoot在这里进行了封装,它不会直接由Tomcat的默认错误页处理,而是会转发"/error"请求,被SpringBoot中的BasicErrorController组件处理。
7.7.3 自定义异常处理
● 方式一:自定义错误页
在静态资源目录,或者templates目录下,创建一个"/error"目录,里面存放"404"、"4xx"、"5xx"等以状态码命名的html页面。DefaultErrorViewResolver会先找这样的错误模板页,如果找不到才使用默认的错误视图"error"。
● 方式二:@ControllerAdvice + @ExceptionHandler【推荐】 ⭐
它可以:自定义如果解析异常信息,并返回ModelAndView。底层由ExceptionHandlerExceptionResolver支持。
● 方式三:@ResponseStatus 自定义状态码,以及详细的错误信息
它不能定义如何解析这个异常以及返回ModelAndView,只能手动标识一个HTTP状态码和详细错误信息,【可以将注解加到控制器方法或异常类上】。最终还是会调用response.sendError(statusCode, revolcedReason),即转发"/error"请求(由BasicErrorController处理,由DefaultErrorViewResolver解析)。可以通过这个搭配自定义错误页(方式一)使用!底层由ResponseStatusExceptionResolver支持。
@ResponseStatus
● value/code:类型HttpStatus,它是由Spring框架提供的枚举类型,用于表示HTTP响应码
● reason:自定义的相应信息
(没有办法自己创建一个HttpStatus对象)
● 方式四:实现HandlerExceptionResolver接口的解析器类
实现里面的resolveException方法,并返回ModelAndView对象。【用的比较少】,因为它可以更改优先级,会改变SpringBoot默认的处理异常的顺序。
7.8 Web原生组件 和 嵌入式Servlet容器
7.8.1 注入Servlet、Filter、Listener
(1) 方式一:@ServletComponentScan(basePackage="...") + ( @WebServlet / @WebFilter / @WebListener )
如,可以像原生Servlet一样书写,并且在类上标注@WebServlet,然后在主程序类(主配置类)上加上@ServletComponentScan注解,如果不写basePackage,则默认是主配置类所在包目录
(2) 方式二:使用RegistrationBean
可以在@Configuration配置类中,添加@Bean组件——ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean
@Configuration
public class MyRegistConfig {@Beanpublic ServletRegistrationBean myServlet(){MyServlet myServlet = new MyServlet();return new ServletRegistrationBean(myServlet,"/my","/my02");}@Beanpublic FilterRegistrationBean myFilter(){MyFilter myFilter = new MyFilter();
// return new FilterRegistrationBean(myFilter,myServlet());FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));return filterRegistrationBean;}@Beanpublic ServletListenerRegistrationBean myListener(){MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();return new ServletListenerRegistrationBean(mySwervletContextListener);}
}
7.8.2 嵌入式Servlet容器
SpringBoot默认支持的WebServer:Tomcat、Jetty、Undertow
(1) 原理
● SpringBoot应用在启动时,检测到是Web应用,就会创建一个ServletWebServerApplicationContext的IOC容器。
● 该容器启动时,会寻找ServletWebServerFactory。
● SpringBoot底层拥有ServletWebServerFactoryAutoConfiguration自动配置类。该配置类中装配了Tomcat、Jetty、Undertow这些Servlet容器。并不会全部生效!导入了哪个,生效哪个!
● 生效的ServletWebServerFactory会创建对应的Servlet容器,并启用它。
(2) 如何切换服务器
默认的web场景启动器,内置了Tomcat的Servlet容器。可以排除它,并引入其他的Servlet容器。
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!-- 排除Tomcat --><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency><!-- 引入undertow --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-undertow</artifactId></dependency>
</dependencies>
八、数据访问
SpringBoot中有关数据访问的场景启动器:【spring-boot-starter-data-*】
8.1 JDBC场景
8.1.1 引入 场景启动器 + 驱动 依赖
(1) 导入spring-boot-starter-data-jdbc
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
里面内置了HikariCp连接池这个数据源,以及Spring中支持jdbc、事务相关的包
(2) 引入驱动的依赖
⭕ 为什么官方不给我们提供在jdbc场景中呢?——因为它不知道我们要使用哪个数据库!所以我们需要自己引入
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><!--<version>5.1.49</version>-->
</dependency>想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、依照parent的properties重新声明版本(maven的属性的就近优先原则)<properties><java.version>1.8</java.version><mysql.version>5.1.49</mysql.version></properties>
spring-boot-starter-parent里面给我们管理了主流的驱动依赖。我们可以不用写版本,但是一定要看看驱动版本和我们自己用的数据库对不对应的上!
● 配置驱动的连接信息
spring:datasource:url: jdbc:mysql://localhost:3306/db_accountusername: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driver
(3) 测试
@Slf4j
@SpringBootTest
class Boot05WebAdminApplicationTests {@AutowiredJdbcTemplate jdbcTemplate;@Testvoid contextLoads() {// jdbcTemplate.queryForObject("select * from account_tbl")
// jdbcTemplate.queryForList("select * from account_tbl",)Long aLong = jdbcTemplate.queryForObject("select count(*) from account_tbl", Long.class);log.info("记录总数:{}",aLong);}}
8.1.2 DataSource的自动配置类
● DataSourceConfiguration
数据库连接池的自动配置类,和DataSourceProperties绑定(prefix="spring.datasource"),当容器中没有自己的DataSource时才自动配置
底层配置好的连接池:HikariDataSource
● DataSourceTransactionManagerAutoConfiguration
事务管理器的自动配置类
● JdbcTemplateAutoConfiguration
JdbcTemplate的自动配置类,可以用来对数据库进行crud操作。
8.2 使用Druid数据源(第三方)
8.2.1 自定义整合
(1) 引入Druid连接池数据源
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.17</version>
</dependency>
(2) 给Spring容器中添加DruidDataSource组件
● 使用【@ConfigurationProperties】,指定application全局配置文件的前缀,会自动地为当前组件的属性注入值。【底层:反射,需要依赖于无参构造+set】
@Configuration
public class MyDataSourceConfiguration{@ConfigurationProperties("spring.datasource")@Beanpublic DataSource dataSource(){DruidDataSource druidDataSource = new DruidDataSource();// druidDataSource.serUrl("...");// druidDataSource.serUsername("...");// druidDataSource.serPassword("...");return druidDataSource;}
}
(3) 添加Druid功能到我们的自动配置类
功能很多,比如:查询慢SQL记录等等...
● 基本原则:参照官方文档说明,只要有<bean>标签,就可以在配置类中添加一个@Bean组件进Spring容器,并在里面放好指定的属性,就可以完成功能。
官方链接:
https://github.com/alibaba/druid
8.2.2 starter整合
(1) 引入druid-spring-boot-starter
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version>
</dependency>
(2) 根据官方的自动配置类,配置文件
给个示例:application.yamlspring:datasource:url: jdbc:mysql://localhost:3306/db_accountusername: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driverdruid:aop-patterns: com.atguigu.admin.* #监控SpringBeanfilters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)stat-view-servlet: # 配置监控页功能enabled: truelogin-username: adminlogin-password: adminresetEnable: falseweb-stat-filter: # 监控webenabled: trueurlPattern: /*exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'filter:stat: # 对上面filters里面的stat的详细配置slow-sql-millis: 1000logSlowSql: trueenabled: truewall:enabled: trueconfig:drop-table-allow: false
8.3 整合MyBatis
https://github.com/mybatis
● 引入mybatis提供的场景启动器
可以在官方中查看pom.xml文件提取坐标信息,也可以使用Spring Initializer向导帮助我们创建MyBatis Spring Boot Application。
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version>
</dependency>
8.3.0 回顾原生mybatis整合spring
(1) mybatis自己的配置
● 在mybatis-config.xml全局配置文件中,指定mapper包路径
● 在XXXmapper.xml映射sql文件中,编写sql语句,并在namespace、sql标签id上绑定对应接口
通过sqlSessionFactory,创建sqlSession对象,调用getMapper方法时传入接口类名,创建代理对象,执行sql。
(2) 整合配置
需要在Spring容器中(Spring配置文件或者Spring配置类中),放入SqlSessionFactory组件(里面配置了dataSource信息、通过configLocation配置了mybatis-config.xml全局配置文件路径),并开启Mapper接口的扫描。
而在springboot中,提供了三种模式供我们进行整合。
8.3.1 配置模式【重要】
(0) 导入mybatis官方starter
(1) 编写Mapper接口,标注@Mapper注解。【推荐】或者在SpringBoot主启动类中标注@MapperScan("Mapper接口的包路径")【AutoConfiguredMapperScannerRegistrar自动帮我们扫描该路径下的Mapper接口】
使用 @Mapper 注解时,需要在每个 Mapper 接口上都标注 @Mapper 注解。如果应用中的 Mapper 接口比较多,这样做会显得繁琐。此时,可以使用 @MapperScan 注解指定 Mapper 接口的扫描路径,以避免重复标注 @Mapper 注解。
(2) 编写sql映射文件并通过namespace、sql标签id,分别绑定接口类名、接口方法名。
(3) 在application.yaml中指定mybatis-config.xml全局配置文件路径,和mapper映射文件位置。
application.yaml# 配置mybatis规则
mybatis:
# config-location: classpath:mybatis/mybatis-config.xml# 这里配置了,mybatis全局配置中就不用指定mapper的包路径了,它的作用:创建代理对象时找到对应的接口mapper-locations: classpath:mybatis/mapper/*.xml configuration:map-underscore-to-camel-case: true
⭕ 注意:
● 在yaml中配置mybatis.configuration,相当于在mybatis-config.xml(MyBatis全局配置文件)中配置。只能二选一!
● (1)只是帮我们把实现了接口的代理对象作为组件放入Spring的IOC容器中;(2)、(3)是mybatis中的逻辑,需要指定sql映射文件的位置、指定全局配置文件路径、以及两处绑定接口的操作。
8.3.2 注解模式
直接省略Sql的映射文件,在接口方法上书写sql。
@Mapper
public interface CityMapper {@Select("select * from city where id=#{id}")public City getById(Long id);
}
8.3.3 混合模式(最佳实践)
● 简单方法直接注解方式
● 复杂方法编写mapper.xml进行绑定映射
8.4 整合MyBatis-Plus
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
● 建议安装 MybatisX 插件
● 只需要我们的Mapper继承 BaseMapper 就可以拥有简单的crud能力
● 一些Service接口的实现类直接调用Dao方法,没有什么额外的业务逻辑。我们可以让它的接口实现 IService<T>,让该实现类继承 ServiceImpl<Mapper, Bean>(Mapper是操作数据库的Mapper接口,Bean是返回封装的JavaBean)
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {ServiceImpl<Mapper, Bean>是Mybatis-Plus提供给我们的
}public interface UserService extends IService<User> {IService是Mybatis-Plus提供给我们的
}
65、数据访问-整合MyBatisPlus操作数据库_哔哩哔哩_bilibili
8.5 整合Redis
Redis是一个开源的(BSD许可),内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
● 引入redis的场景启动器
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
</dependencies>
自动装配了RedisAutoConfiguration配置类,绑定了RedisProperties属性类(前缀为"spring.redis")
● 配置了 客户端连接工厂:Lettuce、Jedis(早期默认Jedis,高版本默认Lettuce)
● 配置了 RedisTemplate<Object, Object>组件。redisTemplate 是 【Spring Framework 提供的】一个用于【简化 Redis 操作的模板类】,它可以使用多种 Redis 客户端实现,包括 Jedis 和 Lettuce 等。
● 配置了 StringRedisTemplate组件,它的key、value都是String
● 高版本的redis场景启动器,默认的客户端是Lettuce,如果要切换客户端到Jedis
(1) 引入Jedis依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><!-- 这里可以排除Lettuce,不排除的话,相当于有两个客户端包。在application.yaml中配置一下使用的客户端为Jedis也可以 -->
</dependency><!--导入jedis-->
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId>
</dependency>
(2) applcaition.yaml全局指定redis客户端
spring:redis:host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.comport: 6379password: lfy:Lfy123456client-type: jedisjedis:pool:max-active: 10
● 简单测试
class Test{@Testvoid testRedis(){ValueOperations<String, String> operations = redisTemplate.opsForValue();operations.set("hello","world");String hello = operations.get("hello");System.out.println(hello);}
}
九、单元测试
SpringBoot 2.2.0 版本开始引入JUnit5作为单元测试默认库。
SpringBoot 2.4 以上版本移除了默认对Vintage的依赖。如果需要兼容JUnit4,需要自行引入
● 引入测试场景启动器
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- 以下是对Junit4的兼容 --><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>
</dependencies>
在JUnit4中,我们在SpringBoot中测试:@SpringBootTest + @RunWith(SpringRunner.class)
在JUnit5中,我们在SpringBoot中测试:@SpringBootTest 即可。JUnit类具有Spring的功能。如可以使用@Autowired自动注入,使用@Transactional标注事务,测试完成后会自动回滚……
9.1 常用的测试注解
(1) @Test:无参数测试
(2) @ParameterizedTest:参数化测试
参数化测试时JUnit5很重要的一个新特性,它使得不同的参数多次运行测试成为了可能。
● @ValueSource
● @NullSource
● @EnumSource
● @CsvSource
● @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流,并且方法是静态的)
⭕ 另外:它可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {System.out.println(string);Assertions.assertTrue(StringUtils.isNotBlank(string));
}@ParameterizedTest
@MethodSource("method") //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {System.out.println(name);Assertions.assertNotNull(name);
}static Stream<String> method() {return Stream.of("apple", "banana");
}
(3) @RepeatedTest(次数):表示方法可重复执行的次数
(4) @DisplayName("名字"):为测试类或者测试方法设置展示名字
(5) @BeforeEach:在【每个】单元测试方法之前执行
(6) @AfterEach:在【每个】单元测试方法之后执行
(7) @BeforeAll:在【所有】单元测试方法之前执行,只执行一次
(8) @AfterAll:在【所有】单元测试方法之后执行,只执行一次
(9) @Tag("标签名"):设置单元测试的类别,可以在运行的时候筛选
(10) @Disabled:测试类或者测试方法不执行(报告方法跳过,而不是错误)
(11) @Timeout:当测试方法运行超出了时间会返回错误
● 可以设置时间单位
● 设置具体的时间数
(12) @ExtendWith:给测试类或者测试方法提供【扩展类】的引用
例如:
● @SpringBootTest:复合注解
● @ExtendWith(SpringExtension.class)——对接了Spring的测试驱动
● @BootstrapWith(SpringBootTestContextBootstrapper.class)
其他的注解:JUnit 5 User Guide
import org.junit.jupiter.api.Test; //注意这里使用的是jupiter的Test注解!!@DisplayName("我的测试类")
public class TestDemo {@Test@DisplayName("第一次测试")public void firstTest() {System.out.println("hello world");}
9.2 断言
断言(Assertions)是测试方法中的核心部分,是org.junit.jupiter.api.Assertions包下的静态方法。
在maven项目中进行test,会生成一个详细的测试报告。
(1) 简单断言
● 参数列表:期望值(Boolean类型的就不用期望值了)、实际运行值、String(message)(可以为空。当断言失败时,输出的消息)
(2) 数组断言:判断两个数组长度和内容上是否相等
● 参数列表:期望值、实际运行值、String(message)(可以为空。当断言失败时,输出的消息)
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
(3) 组合断言:多个断言组合起来,只有全部断言成功,才成功;否则,报告所有失败的断言【即所有断言均会执行,无论是否存在失败】
● 参数列表:String(heading,指定一个组合名字用于测试报告显示)、Executable可变形参
assertAll("Math",() -> assertEquals(2, 1 + 1),() -> assertTrue(1 > 0));
(4) 异常断言:如果出现指定异常,才断言成功;否则失败
● 参数列表:期望值、Executable、String(message)
@Test
@DisplayName("异常测试")
public void exceptionTest() {ArithmeticException exception = Assertions.assertThrows(//扔出断言异常ArithmeticException.class, () -> {int i = 10 / 0;}, message:"业务逻辑居然正常运行?");}
(5) 超时断言:断定方法会在指定时间内运行完成
● 参数列表:期望值、Executable、String(message)
@Test
@DisplayName("超时测试")
public void timeoutTest() {//如果测试方法时间超过1s将会异常Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
(6) 快速失败
fail方法,直接使得测试失败
@Test
@DisplayName("fail")
public void shouldFail() {fail("This should fail");
}
9.3 前置条件
JUnit5 中的前置条件(Assumptions)类似于断言,都会在不满足条件时,终止执行。
● 区别在于:断言会报告方法失败,而前置条件不满足会报告方法被跳过。
@DisplayName("前置条件")
public class AssumptionsTest {private final String environment = "DEV";@Test@DisplayName("simple")public void simpleAssume() {assumeTrue(Objects.equals(this.environment, "DEV"));assumeFalse(() -> Objects.equals(this.environment, "PROD"));}@Test@DisplayName("assume then do")public void assumeThenDo() {assumingThat(Objects.equals(this.environment, "DEV"),() -> System.out.println("In DEV"));}
}
● assumeTrue 和 assumeFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。
● assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行。
9.4 嵌套测试
● @Nested:加在测试类的【内部类】上。
● 测试【内部类】的方法,【会】驱动外部类的Before(After)Each/All方法;
● 测试【外部类】的方法,【不会】驱动内部类的Before(After)Each/All方法。
@DisplayName("A stack")
class TestingAStackDemo {Stack<Object> stack;@Test@DisplayName("is instantiated with new Stack()")void isInstantiatedWithNew() {new Stack<>();}@Nested@DisplayName("when new")class WhenNew {@BeforeEachvoid 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";@BeforeEachvoid 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());}}}
}
十、指标监控
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
笔记:08、指标监控 (yuque.com)
视频:77、指标监控-SpringBoot Actuator与Endpoint_哔哩哔哩_bilibili
十一、原理解析
11.1 多环境适配——Profile
生产环境、开发环境、测试环境等等...配置文件内容、配置类等可能会随着环境的不同而有不同的设置。
11.1.1 配置文件application.yaml相关
如果我们每次都在一个配置文件上修改,显得太麻烦了。SpringBoot允许我们使用多个配置文件。
● 默认的配置文件:application.yaml——任何时候都会加载;
● 自定义环境配置文件:application-{env}.yaml
⭕ 激活自定义环境配置文件
● 方式一:在默认的配置文件中指定
假定自定义环境配置文件:application-myEnv1.yaml、application-myEnv2.yaml(1) 在默认配置文件中【激活某个】自定义的环境配置文件:
application.yaml
spring.profiles.active = myEnv1(2) 在默认配置文件中,对profile进行分组,【激活组】:
spring.profiles.group.myEnv[0] = myEnv1
spring.profiles.group.myEnv[0] = myEnv2
如果在默认配置文件中:spring.profiles.active = myEnv,则两个配置文件都会生效
● 方式二:命令行激活(我们的spring-boot项目最终都可以被打包成一个jar包部署到服务器)
可以在外部修改配置文件的任何信息
java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
11.1.2 @Profile条件装配
● 可以加在配置类,也可以加在方法上。可以指定在某个环境生效。(application.yaml中spring.profiles.active指定)
application.yaml
spring.profile.active: production@Configuration(proxyBeanMethods = false)
@Profile("production") 【默认是"default",可以写成数组形式】
public class ProductionConfiguration {// ...}
11.2 外部化配置
将数据不是写死在代码中,而是抽取成外部文件,集中管理。
SpringBoot中支持各种外部化配置,其中包括:Java Properties文件,YAML文件,环境变量和命令行参数。
11.3 自定义starter
模拟源码编写自定义starter。
● 步骤
(1) 编写一个maven项目,用来作为myservice-spring-boot-starter,只写pom.xml,里面引入了MyServiceAutoConfiguration自动配置类的依赖(除了pom文件以外,没有任何其他的Java代码、配置文件)
(2) 编写一个maven项目,用来作为MyServiceAutoConfiguration自动配置类(被自定义starter引用),编写自动配置类(@Configuration + @EnableConfigurationProperties)、业务组件(@Bean + 条件装配)、MyServiceProperties类(@ConfigurationProperties + 绑定到自动配置类)。
(3) 在MyServiceAutoConfiguration项目中的resources目录下准备META-INF目录,存放spring.factories文件,里面编写项目启动时自动加载的配置类"xxx.xxx.EnableAutoConfiguration=MyServiceAutoConfiguration自动配置类的全类名"
(4) 在外部项目中,引入my-spring-boot-starter即可使用我们自定义的功能。
● myservice-spring-boot-starter:maven项目pom.xml
...
<!-- my-spring-boot-starter坐标 -->
<groupId>xxxx</groupId>
<artifactId>myservice-spring-boot-starter</artifactId>
<version>1.0</version>
...
<dependencies><dependency><!-- 引入MyAutoConfiguration自动配置类 --><groupId>xxx</groupId><artifactId>myservice-spring-boot-starter-autoconfigure</artifactId><version>1.0</version></dependency>
</dependencies>
...
● MyServiceAutoConfiguration项目myservice-spring-boot-starter-autoconfigure:maven项目pom.xml
...
<!-- 自己的坐标 -->
<groupId>xxx</groupId>
<artifactId>myservice-spring-boot-starter-autoconfigure</artifactId>
<version>1.0</version>
...// 逻辑业务组件
@Service
public class MyService{@Autowiredprivate MyServiceProperties myServiceProperties;public string sayHello(String name){return myServiceProperties.getHelloWord() + name;}
}@ConfigurationProperties(prefix="my.myService")
public class MyServiceProperties{private String helloWord;public String getHelloWord(){return helloWord;}
}application.yaml
my: myService: helloWord: hello~@Configuration
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration{@ConditionalOnMissingBean(MyService.class)@Beanpublic MyService myService(){MyService myService = new MyService();return myService;}
}在项目resources文件夹下创建/META-INF,创建spring.factories文件
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
MyServiceAutoConfiguration全类名
● 自己测试myservice-spring-boot-starter,创建一个外部项目
pom.xml
...
<!-- 自己的坐标(略) -->
<!-- 引入我们自定义的starter -->
<dependencies><dependency><groupId>xxxx</groupId><artifactId>myservice-spring-boot-starter</artifactId><version>1.0</version></dependency>
</dependencies>
...@RestController
public class MyTestController{@Autowiredprivate MyService myService;@RequestMapping("/hello")public String sayHello(){return myService.sayHello("张三");}
}
11.4 原理总结
【创建】并【运行】SpringApplication。
● 【创建】:主要是通过spring.factories文件去创建一些关键组件。如:启动引导器(BootStrapppers)、IOC初始化器(ApplicationContextInitializer)、应用监听器(ApplicationListener)
● 【运行】:找到spring.factories中所有SpringApplicationRunListener,对每个阶段进行监听。包括准备环境(读取并绑定系统配置、外部配置源等等)、根据Web项目类型创建IOC容器、刷新IOC容器(创建Bean组件)、Runner启动(应用启动后会调用run方法)等等。
——以上ApplicationContextInitializer、ApplicationListener、SpringApplicationRunListener、Runner均可以自定义。
喜欢的朋友记得点赞、收藏、关注哦!!!