SpringBoot笔记

学习目标:

1.能够理解Spring的优缺点

2.能够理解SpringBoot的特点

3.能够理解SpringBoot的核心功能

4.能够搭建SpringBoot的环境

5.能够完成application.properties配置文件的配置

6.能够完成application.yml配置文件的配置

7.能够使用SpringBoot集成Mybatis

8.能够使用SpringBoot集成Junit

9.能够使用SpringBoot集成SpringData JPA

一、SpringBoot简介

1.1原有Spring优缺点分析

1.1.1 Spring的优点分析

Spring是Java企业版(Java Enterprise Edition,JEE,也称J2EE)的轻量级代替品。无需开发重量级的Enterprise JavaBean(EJB),Spring为企业级Java开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的Java对象(Plain Old Java Object,POJO)实现了EJB的功能。

1.1.2Spring的缺点分析

虽然Spring的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring用XML配置,而且是很多XML配 置。Spring 2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML配置。Spring 3.0引入了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML。

所有这些配置都代表了开发时的损耗。因为在思考Spring特性配置和解决业务问题之间需要进行思维切换,所以编 写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring实用,但与此同时它要求的回报也不少。

除此之外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要 分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开 发进度。

1.2SpringBoot的概述

1.2.1SpringBoot解决上述Spring的缺点

SpringBoot对上述Spring的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑 业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短 了项目周期。

1.2.2SpringBoot的特点

为基于Spring的开发提供更快的入门体验

开箱即用,没有代码生成,也无需XML配置。同时也可以修改默认值来满足特定的需求

提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等

SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式

1.2.3SpringBoot的核心功能

起步依赖

起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。

简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。 自动配置

Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的。

注意:起步依赖和自动配置的原理剖析会在第三章《SpringBoot原理分析》进行详细讲解

二、SpringBoot快速入门

2.1代码实现

2.1.1 sts创建springboot工程

使用创建向导完成工程的创建:

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9CTLV1Br-1660034045617)(/pic/sts_springboot_1.png)]

注:springboot最低版本要求jdk1.8

继续完善创建过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1OYcVqJL-1660034045618)(/pic/sts_springboot_2.png)]

next

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2S7N0Ejy-1660034045620)(/pic/sts_springboot_3.png)]

next

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJIPg1Nf-1660034045620)(/pic/sts_springboot_4.png)]

next

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dq7tXdgK-1660034045621)(/pic/sts_springboot_5.png)]

finish;----工程创建成功

测试:springboot默认支持springMVC,发起请求,经过控制器进行视图渲染,同时支持异步请求;

2.1.2 idea创建springboot工程

从maven工程着手创建********************************************************************

A 创建Maven工程

使用idea工具创建一个 maven工程,该工程为普通的java工程即可

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0lFRT7ij-1660034045621)(/pic/01.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-06vYwufy-1660034045623)(pic/02.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OwbPoCL8-1660034045623)(pic/03.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kte8HXzz-1660034045624)(pic/04.png)]

a.1添加SpringBoot的起步依赖

SpringBoot要求,项目要继承SpringBoot的起步依赖spring-boot-starter-parent

      <!--所有的springboot工程都必须继承spring-boot-starter-parent-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
    </parent>

SpringBoot要集成SpringMVC进行Controller的开发,所以项目要导入web的启动依赖

  	<dependencies>
  		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
     </dependencies>

a.2 编写SpringBoot引导类

要通过SpringBoot提供的引导类起步SpringBoot才可以进行访问

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Created by 54110 on 2019-06-03.
 */

@SpringBootApplication
public class MySpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApplication.class);
    }
}

a.3编写Controller

在引导类MySpringBootApplication同级包或者子级包中创建QuickStartController

package controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * Created by 54110 on 2019-06-03.
 */
@Controller
public class QuickStartController {

    @RequestMapping("/quick")
    @ResponseBody
    public String quick(){
        return "Spring Boot 访问成功";
    }
}

a.4测试

 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.1.RELEASE)

2019-06-03 16:57:11.817  INFO 12412 --- [  restartedMain] com.MySpringBootApplication              : Starting MySpringBootApplication on miaohangbo with PID 12412 (D:\project\springboo_qucik\target\classes started by 54110 in D:\project\springboo_qucik)
2019-06-03 16:57:11.818  INFO 12412 --- [  restartedMain] com.MySpringBootApplication              : No active profile set, falling back to default profiles: default
2019-06-03 16:57:11.872  INFO 12412 --- [  restartedMain] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@62bb93a5: startup date [Mon Jun 03 16:57:11 CST 2019]; root of context hierarchy
2019-06-03 16:57:13.994  INFO 12412 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-06-03 16:57:14.013  INFO 12412 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-06-03 16:57:14.013  INFO 12412 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.29
2019-06-03 16:57:14.020  INFO 12412 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [G:\java1.8\bin;C:\Windows\Sun\Java\bin;C:\Windows\system32;C:\Windows;C:\ProgramData\Oracle\Java\javapath;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\iCLS\;C:\Program Files\Intel\Intel(R) Management Engine Components\iCLS\;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;G:\java1.8\bin;G:\java1.8\jre\bin;G:\python3;C:\Program Files\MySQL\MySQL Utilities 1.6\;C:\Program Files\Git\cmd;G:\apache-maven-3.5.0\bin;G:\nodejs\;G:\RabbitMqServer\erl9.3\bin;G:\edusoft\ffmpeg-20180227-fa0c9d6-win64-static\bin;G:\OpenSSL-Win64\bin;G:\RabbitMqServer\rabbitmq_server-3.7.8\sbin;G:\Elastic\Ruby25\bin;C:\Users\54110\AppData\Local\Microsoft\WindowsApps;G:\docker\Docker Toolbox;C:\Users\54110\AppData\Roaming\npm;C:\Users\54110\AppData\Local\BypassRuntm;.]
2019-06-03 16:57:14.083  INFO 12412 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-06-03 16:57:14.084  INFO 12412 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2214 ms
2019-06-03 16:57:14.173  INFO 12412 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]
2019-06-03 16:57:14.176  INFO 12412 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2019-06-03 16:57:14.176  INFO 12412 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2019-06-03 16:57:14.176  INFO 12412 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2019-06-03 16:57:14.176  INFO 12412 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2019-06-03 16:57:14.256  INFO 12412 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2019-06-03 16:57:14.384  INFO 12412 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@62bb93a5: startup date [Mon Jun 03 16:57:11 CST 2019]; root of context hierarchy
2019-06-03 16:57:14.438  INFO 12412 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/quick]}" onto public java.lang.String com.qf.controller.QuickStartController.quick()
2019-06-03 16:57:14.440  INFO 12412 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2019-06-03 16:57:14.441  INFO 12412 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2019-06-03 16:57:14.458  INFO 12412 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2019-06-03 16:57:14.458  INFO 12412 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2019-06-03 16:57:14.572  INFO 12412 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2019-06-03 16:57:14.592  INFO 12412 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2019-06-03 16:57:14.680  INFO 12412 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-06-03 16:57:14.683  INFO 12412 --- [  restartedMain] com.MySpringBootApplication              : Started MySpringBootApplication in 3.164 seconds (JVM running for 3.567)

通过日志发现,Tomcat started on port(s): 8080 (http) with context path ‘’ tomcat已经起步,端口监听8080,web应用的虚拟工程名称为空

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W5wnpyK4-1660034045626)(pic\1559552508083.png)]

打开浏览器访问

url

地址为:

http://localhost:8080/quick

B 直接创建springboot工程

b1 创建工程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FdtrZEKl-1660034045627)(pic\idea创建springboot.png)]

b2设置工程名和包名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OK4qUXAG-1660034045628)(pic\idea创建springboot_2.png)]

b3 关联web包:选择web–spring Web,及spring boot版本(目前已测2.2.2版本)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tPh39HHh-1660034045630)(pic\idea创建springboot_3.png)]

最后finish结束;

2.2快速入门解析

2.2.2SpringBoot代码解析

@SpringBootApplication:标注SpringBoot的启动类,该注解具备多种功能(后面详细剖析) SpringApplication.run(MySpringBootApplication.class) 代表运行SpringBoot的启动类,参数为SpringBoot 启动类的字节码对象

2.2.3SpringBoot工程热部署

我们在开发中反复修改类、页面等资源,每次修改后都是需要重新启动才生效,这样每次启动都很麻烦,浪费了大 量的时间,我们可以在修改代码后不重启就能生效,在 pom.xml 中添加如下配置就可以实现这样的功能,我们称 之为热部署。

  		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

注意:IDEA进行SpringBoot热部署失败原因

出现这种情况,并不是热部署配置问题,其根本原因是因为Intellij IEDA默认情况下不会自动编译,需要对IDEA进行自动编译的设置,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LT7L9BHm-1660034045630)(pic\1561356698866.png)]

然后 Shift+Ctrl+Alt+/,选择Registry

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UtkC6lxv-1660034045630)(pic\1561356737110.png)]

2.2.4使用idea快速创建SpringBoot项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t6xDB0fk-1660034045630)(pic\1559553636258.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cXgcw6Yc-1660034045631)(pic\1559553509842.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OBQdEfWk-1660034045631)(pic\1559553550985.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-obduB19m-1660034045632)(pic\1559554091276.png)]

通过idea快速创建的SpringBoot项目的pom.xml中已经导入了我们选择的web的起步依赖的坐标

可以使用快速入门的方式创建Controller进行访问,此处不再赘述

三、SpringBoot原理分析

3.1.1分析spring-boot-starter-parent

按住Ctrl点击pom.xml中的spring-boot-starter-parent,跳转到了spring-boot-starter-parent的pom.xml,xml配 置如下(只摘抄了部分重点配置):

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.5.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

按住Ctrl点击pom.xml中的spring-boot-starter-dependencies,跳转到了spring-boot-starter-dependencies的

pom.xml,xml配置如下(只摘抄了部分重点配置):

    <properties>
        <activemq.version>5.15.3</activemq.version>
        <antlr2.version>2.7.7</antlr2.version>
        <appengine-sdk.version>1.9.63</appengine-sdk.version>
        <artemis.version>2.4.0</artemis.version>
        <aspectj.version>1.8.13</aspectj.version>
        <assertj.version>3.9.1</assertj.version>
        <atomikos.version>4.0.6</atomikos.version>
        <bitronix.version>2.1.4</bitronix.version>
        <build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>
        <byte-buddy.version>1.7.11</byte-buddy.version>
        ... ... ...
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot</artifactId>
                <version>2.0.1.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-test</artifactId>
                <version>2.0.1.RELEASE</version>
            </dependency>
        ... ... ...
        </dependencies>
    </dependencyManagement>
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.jetbrains.kotlin</groupId>
                    <artifactId>kotlin-maven-plugin</artifactId>
                    <version>${kotlin.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.jooq</groupId>
                    <artifactId>jooq-codegen-maven</artifactId>
                    <version>${jooq.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>2.0.1.RELEASE</version>
                </plugin>
            ... ... ...
            </plugins>
        </pluginManagement>
    </build>

从上面的spring-boot-starter-dependencies的pom.xml中我们可以发现,一部分坐标的版本、依赖管理、插件管理已经定义好,所以我们的SpringBoot工程继承spring-boot-starter-parent后已经具备版本锁定等配置了。所以起步依赖的作用就是进行依赖的传递。

3.1.2分析spring-boot-starter-web

按住Ctrl点击pom.xml中的spring-boot-starter-web,跳转到了spring-boot-starter-web的pom.xml,xml配置如 下(只摘抄了部分重点配置):

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starters</artifactId>
        <version>2.0.1.RELEASE</version>
    </parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.0.1.RELEASE</version>
    <name>Spring Boot Web Starter</name>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.0.1.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-json</artifactId>
            <version>2.0.1.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <version>2.0.1.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.9.Final</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
        <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.0.5.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.0.5.RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

从上面的spring-boot-starter-web的pom.xml中我们可以发现,spring-boot-starter-web就是将web开发要使用的spring-web、spring-webmvc等坐标进行了“打包”,这样我们的工程只要引入spring-boot-starter-web起步依赖的坐标就可以进行web开发了,同样体现了依赖传递的作用。

3.2 自动配置原理解析

按住Ctrl点击查看启动类MySpringBootApplication上的注解@SpringBootApplication

@SpringBootApplication
public class MySpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApplication.class);
    }
}

注解@SpringBootApplication的源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes =
AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
        /**
        * Exclude specific auto-configuration classes such that they will never be
        applied.
        * @return the classes to exclude
        */
        @AliasFor(annotation = EnableAutoConfiguration.class)
        Class<?>[] exclude() default {};
        ... ... ...
}

其中:

@SpringBootConfiguration:等同与@Configuration,既标注该类是Spring的一个配置类@EnableAutoConfiguration:SpringBoot自动配置功能开启

按住Ctrl点击查看注解@EnableAutoConfiguration

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationImportSelector;
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

其中,@Import(AutoConfifigurationImportSelector.class) 导入了AutoConfifigurationImportSelector类

按住Ctrl点击查看AutoConfifigurationImportSelector源码

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        ... ... ...
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
        attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return StringUtils.toStringArray(configurations);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
        getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        return configurations;
}

其中,SpringFactoriesLoader.loadFactoryNames 方法的作用就是从META-INF/spring.factories文件中读取指定类对应的类名称列表

spring.factories 文件中有关自动配置的配置信息如下:

... ... ...
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConf
iguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfigu
ration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
... ... ... 

上面配置文件存在大量的以Configuration为结尾的类名称,这些类就是存有自动配置信息的类,而

SpringApplication在获取这些类名后再加载

我们以ServletWebServerFactoryAutoConfiguration为例来分析源码:

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
    public class ServletWebServerFactoryAutoConfiguration {
    ... ... ...
    }

其中,@EnableConfigurationProperties(ServerProperties.class) 代表加载ServerProperties服务器配置属性类

进入ServerProperties.class源码如下:

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
    
    /**
    * Server HTTP port.
    */
    private Integer port;
   
   	/**
    * Network address to which the server should bind.
    */
    private InetAddress address;
    
    ... ... ...
}

其中,

prefix = “server” 表示SpringBoot配置文件中的前缀,SpringBoot会将配置文件中以server开始的属性映射到该类的字段中。映射关系如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c1oiz3XD-1660034045633)(pic\1559624049656.png)]

四、SpringBoot的配置文件

4.1SpringBoot配置文件类型

4.1.1SpringBoot配置文件类型和作用

SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml(application.yaml)进行配置。

SpringBoot默认会从Resources目录下加载application.properties或application.yml(application.yaml)文件

其中,application.properties文件是键值对类型的文件,之前一直在使用,所以此处不在对properties文件的格式进行阐述。除了properties文件外,SpringBoot还可以使用yml文件进行配置,下面对yml文件进行讲解。

4.1.2application.yml配置文件

4.1.2.1 yml配置文件简介

YML文件格式是YAML (YAML Aint Markup Language)编写的文件格式,YAML是一种直观的能够被电脑识别的数据序列化格式,并且容易被人类阅读,容易和脚本语言交互的,可以被支持YAML库的不同的编程语言程序导 入,比如: C/C++, Ruby, Python, Java, Perl, C#, PHP等。YML文件是以数据为核心的,比传统的xml方式更加简洁。

YML文件的扩展名可以使用.yml或者.yaml。

4.1.2.2yml配置文件的语法
4.1.2.2.1 配置普通数据

语法: key: value

示例代码:

name: haohao

注意:value之前有一个空格

4.1.2.2.2 配置对象数据语法:

语法:

key:

key1: value1

key2: value2

或者:

key: {key1: value1,key2: value2}

示例代码:

person:
    name: haohao
    age: 31
    addr: beijing
    
#或者
person: {name: haohao,age: 31,addr: beijing}

注意:key1前面的空格个数不限定,在yml语法中,相同缩进代表同一个级别

4.1.2.2.2配置Map数据同上面的对象写法

同上面的对象写法

4.1.2.2.3配置数组(List、Set)数据语法:

key:

- value1

- value2

或者:

key: [value1,value2]

示例代码:

my:
  city:
    - beijing
    - tianjin
    - shanghai
    - chongqing
#或者
my:
 city: [beijing,tianjin,shanghai,chongqing]
#集合中的元素是对象形式
student:
   -name: zhangsan
    age: 18
    score: 100
   -name: lisi
    age: 28
    score: 88
   -name: wangwu
    age: 38
    score: 90

注意:value1与之间的 - 之间存在一个空格

4.1.3SpringBoot配置信息的查询

上面提及过,SpringBoot的配置文件,主要的目的就是对配置信息进行修改的,但在配置时的key从哪里去查询 呢?我们可以查阅SpringBoot的官方文档

文档URL:https://docs.spring.io/spring-boot/docs/2.0.1.RELEASE/reference/htmlsingle/#common-application- properties

常用的配置摘抄如下:

# QUARTZ SCHEDULER (QuartzProperties)
spring.quartz.jdbc.initialize-schema=embedded # Database schema initialization mode.
spring.quartz.jdbc.schema=classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.
sql # Path to the SQL file to use to initialize the database schema.
spring.quartz.job-store-type=memory # Quartz job store type.
spring.quartz.properties.*= # Additional Quartz Scheduler properties.
# ----------------------------------------
# WEB PROPERTIES
# ----------------------------------------
# EMBEDDED SERVER CONFIGURATION (ServerProperties)
server.port=8080 # Server HTTP port.
server.servlet.context-path= # Context path of the application.
server.servlet.path=/ # Path of the main dispatcher servlet.
# HTTP encoding (HttpEncodingProperties)
spring.http.encoding.charset=UTF-8 # Charset of HTTP requests and responses. Added to
the "Content-Type" header if not set explicitly.
# JACKSON (JacksonProperties)
spring.jackson.date-format= # Date format string or a fully-qualified date format
class name. For instance, `yyyy-MM-dd HH:mm:ss`.
# SPRING MVC (WebMvcProperties)
spring.mvc.servlet.load-on-startup=-1 # Load on startup priority of the dispatcher
servlet.
spring.mvc.static-path-pattern=/** # Path pattern used for static resources.
spring.mvc.view.prefix= # Spring MVC view prefix.
spring.mvc.view.suffix= # Spring MVC view suffix.
# DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.driver-class-name= # Fully qualified name of the JDBC driver. Autodetected based on the URL by default.
spring.datasource.password= # Login password of the database.
spring.datasource.url= # JDBC URL of the database.
spring.datasource.username= # Login username of the database.
# JEST (Elasticsearch HTTP client) (JestProperties)
spring.elasticsearch.jest.password= # Login password.
spring.elasticsearch.jest.proxy.host= # Proxy host the HTTP client should use.
spring.elasticsearch.jest.proxy.port= # Proxy port the HTTP client should use.
spring.elasticsearch.jest.read-timeout=3s # Read timeout.
spring.elasticsearch.jest.username= # Login username.

我们可以通过配置application.poperties 或者 application.yml 来修改SpringBoot的默认配置例如:

application.properties文件

server.port=8888
server.servlet.context-path=dem

application.yml文件

server:
	port: 8888
	servlet:
		context-path: /demo

4.2配置文件与配置类的属性映射方式

4.2.1使用注解@Value映射

我们可以通过@Value注解将配置文件中的值映射到一个Spring管理的Bean的字段上 例如:

application.properties配置如下:

person.name=zhangsan
person.age=18 

或者,application.yml配置如下:

person:
    name: zhangsan
    age: 18

实体Bean代码如下:

@Controller
public class QuickStartController {
    @Value("${person.name}")
    private String name;
    @Value("${person.age}")
    private Integer age;
    @RequestMapping("/quick")
    @ResponseBody
    public String quick(){
    return "springboot 访问成功! name="+name+",age="+age;
    }
}

浏览器访问地址:http://localhost:8080/quick 结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CAA3jMRl-1660034045634)(pic/1561356920808.png)]

4.2.2 使用注解@ConfigurationProperties映射

通过注解@ConfigurationProperties(prefix=“配置文件中的key的前缀”)可以将配置文件中的配置自动与实体进行映射

application.properties配置如下:

person:
    name: zhangsan
    age: 18

或者,application.yml配置如下:

person:
    name: zhangsan
    age: 18

实体Bean代码如下:

@Controller
@ConfigurationProperties(prefix = "person")
public class QuickStartController {
    private String name;
    private Integer age;
    @RequestMapping("/quick")
    @ResponseBody
    public String quick(){
    	return "springboot 访问成功! name="+name+",age="+age;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
}

浏览器访问地址:

http://localhost:8080/quick 结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ax4TmpqL-1660034045634)(pic/1561356960501.png)]

注意:使用@ConfigurationProperties方式可以进行配置文件与实体字段的自动映射,但需要字段必须提供set方 法才可以,而使用@Value注解修饰的字段不需要提供set方法

五、SpringBoot与整合其他技术

5.1 SpringBoot整合Mybatis

5.1.1 添加Mybatis的起步依赖

<!--mybatis起步依赖-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.1.1</version>
</dependency>

5.1.2 添加数据库驱动坐标

<!-- MySQL连接驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

5.1.3 添加数据库连接信息

在application.properties中添加数据量的连接信息

#DB Configuration:
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?
useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=59852369

5.1.4创建user表

在test数据库中创建user表

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'zhangsan', '123', '张三');
INSERT INTO `user` VALUES ('2', 'lisi', '123', '李四');

5.1.5创建实体Bean

public class User {
    // 主键
    private Long id;
    // 用户名
    private String username;
    // 密码
    private String password;
    // 姓名
    private String name;
    //此处省略getter和setter方法 .. ..
}

5.1.6 编写Mapper

@Mapper
public interface UserMapper {
	public List<User> queryUserList();
}

注意:@Mapper标记该类是一个mybatis的mapper接口,可以被spring boot自动扫描到spring上下文中

5.1.7配置Mapper映射文件

在src\main\resources\mapper路径下加入UserMapper.xml配置文件"

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.qf.dao.UserMapper">
    <select id="queryUserList" resultType="user">
    	select * from user
    </select>
</mapper>

5.1.8在application.properties中添加mybatis的信息

#spring集成Mybatis环境
#pojo别名扫描包
mybatis.type-aliases-package=com.qf.domain
#加载Mybatis映射文件
mybatis.mapper-locations=classpath:mapper/*Mapper.xml

5.1.9 编写测试Controller

@Controller
public class MapperController {
    
    @Autowired
    private UserMapper userMapper;
   
   @RequestMapping("/queryUser")
    @ResponseBody
    public List<User> queryUser(){
    List<User> users = userMapper.queryUserList();
    return users;
    }
}

5.1.10 测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QRKrxeJx-1660034045635)(pic\1559572350451.png)]

5.1.11 事务管理

单个数据源的情况只需要在service层中加入注解@Transactional 就可以实现事务的管理

5.1.12 多数据源

在实际的开发中,有可能我们在一个项目中需要去查询两个不同的数据库,那么就需要我们来配置多数据源,如何在springboot中配置多数据源

5.1.12.1 导入依赖
   <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

5.1.12.2 配置文件
#DB1 Configuration:
spring.datasource.account.driverClassName=com.mysql.jdbc.Driver
spring.datasource.account.jdbc-url=jdbc:mysql://127.0.0.1:3306/account&useUnicode=true&characterEncoding=utf-8?useSSL=false
spring.datasource.account.username=root
spring.datasource.account.password=59852369

#DB2 Configuration:
spring.datasource.test.driverClassName=com.mysql.jdbc.Driver
spring.datasource.test.jdbc-url=jdbc:mysql://127.0.0.1:3306/test&useUnicode=true&characterEncoding=utf-8?useSSL=false
spring.datasource.test.username=root
spring.datasource.test.password=59852369

可以看到,我们在配置文件中配置两个数据库,库名是不同的

5.1.12.3 修改启动类
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class SpringMybatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringMybatisApplication.class);
    }
}

其中重点是 exclude = {DataSourceAutoConfiguration.class} 表示我们启动的时候排除自动读取数据源 需要添加两个数据库

5.1.12.4 配置DataSource
@Configuration
public class DataSourceConfig {
    @Bean(name = "ds1")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.account") // application.properteis中对应属性的前缀
    public DataSource dataSource1() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "ds2")
    @ConfigurationProperties(prefix = "spring.datasource.test") // application.properteis中对应属性的前缀
    public DataSource dataSource2() {
        return DataSourceBuilder.create().build();
    }


}

在datasource中配置两个数据库的信息,并且交给springboot来管理

5.1.12.5 第一个连接配置
@Configuration
@MapperScan(basePackages = {"com.qf.dao.ds1"}, sqlSessionFactoryRef = "sqlSessionFactory1")//配置当前数据源生效的包,根据包分别访问不同的数据源
public class MybatisDbAConfig {
    @Autowired
    @Qualifier("ds1")
    private DataSource ds1;

    @Bean
    @Primary
    public SqlSessionFactory sqlSessionFactory1() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds1); // 使用ds1数据源, 连接account库
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/ds1/*.xml"));
        return factoryBean.getObject();

    }
	
    @Bean
    @Primary
    public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory1()); // 使用上面配置的Factory
        return template;
    }
	/**
	*配置事务
	*/
    @Bean(name = "Ds1TranscationManager")
    @Primary
    public DataSourceTransactionManager master1TransactionManager(@Qualifier("ds1") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
5.1.12.6 第二个连接配置
@Configuration
@MapperScan(basePackages = {"com.qf.dao.ds2"}, sqlSessionFactoryRef = "sqlSessionFactory2")
public class MybatisDbBConfig {
    @Autowired
    @Qualifier("ds2")
    private DataSource ds2;

    @Bean
    public SqlSessionFactory sqlSessionFactory2() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds2);// 使用ds2数据源, 连接test库
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/ds2/*.xml"));
        return factoryBean.getObject();

    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2());
        return template;
    }

    @Bean(name = "Ds2TranscationManager")
    public DataSourceTransactionManager master1TransactionManager(@Qualifier("ds2") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
5.1.12.7 整体的项目结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6vpBliNy-1660034045636)(pic\1562222277152.png)]

根据不同的包,去连接不同的数据库执行

5.1.12.8 多数据源下的事务管理

使用多数据源后发现,我们的注解@Transcation注解已经失效了,所以我们需要去改变下

@Transactional(value = "Ds1TranscationManager", rollbackFor = { Exception.class })

在需要事务管理的类上加上该注解,标明我们需要使用那个事务来进行管理,value = “Ds1TranscationManager” 在我们的第一个连接中已经配置了事务管理,我们只需要告诉spring我们要来使用他就可以进行事务的管理了

5.2 SpringBoot整合Junit

5.2.1 添加Juni的起步依赖

<!--测试的起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

5.2.2编写测试类

package com.qf.test;

import com.qf.MySpringBootApplication;
import com.qf.domain.User;
import com.qf.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MySpringBootApplication.class)
public class MapperTest {

    @Autowired
    private UserMapper userMapper;
    
    @Test
    public void test() {
    List<User> users = userMapper.queryUserList();
    	System.out.println(users);
	}
}

其中,

SpringRunner继承自SpringJUnit4ClassRunner,使用哪一个Spring提供的测试测试引擎都可以

public final class SpringRunner extends SpringJUnit4ClassRunner

@SpringBootTest的属性指定的是引导类的字节码对象

5.2.3 控制台打印信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NXdc0nG7-1660034045637)(pic\1561357002490.png)]

5.3SpringBoot整合Spring Data JPA

JPA注解介绍:

  1. @Entity :修饰实体类,指明该类将映射到指定的数据表,例如:Customer 类默认的数据表名为 customer

  2. @Table :当实体类与映射的数据库表名不同名时需要使用 @Table 注解,该注解与 @Entity 注解并列使用,使用其 name 属性指明数据库的表名

  3. @Id :标识该属性为主键一般标注在该属性的 getter 方法上

  4. @GeneratedValue :标注主键的生成策略,通过其 strategy 属性。通常与 @Id 注解一起使用。默认情况下 JPA 会自动选择一个最适合底层数据库的主键生成策略,MySQL 默认为 AUTO,常用策略有:

    –IDENTITY:采用数据库 ID自增长的方式来自增主键字段,Oracle 不支持这种方式;

    AUTOJPA自动选择合适的策略,是默认选项;

    –SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式

    –TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植

  5. @Basic :用于没有任何标注的 getXxx() 方法,默认即为 @Basic,所以若一个 getter 方法无任何注解,可以使用 @Basic 注解,也可以不使用

  6. @Column :当实体的属性与其映射的数据表的列不同名时使用,一般用于 getter 方法上。其 name 属性用来指明此属性在数据表中对应的列名unique 属性指明是否为唯一约束nullable 属性用来指明是否可以为空,false 为不能为空length 属性指明此列的长度

  7. @Transient标注此注解后在创建数据表的时候将会忽略该属性 Customer 类并没有 info 这个属性,所以数据库中也不应该有 info 这个字段

  8. @Temporal :向数据库映射日期(Date)属性时用来调整映射的精度。Date 类型的数据有 DATE, TIME, 和 TIMESTAMP 三种精度(即单纯的日期,时间,或者两者兼备).

    Birth 属性应该使用 DATE 类型(生日只具体到日即可,如:2015-10-22),而 CreateTime 应该使用 TIMESTAMP 类型(创建时间应该具体到秒,如:2017-10-11 22:39:13)

5.3.1添加Spring Data JPA的起步依赖

<!-- springBoot JPA的起步依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

5.3.2 添加数据库驱动依赖

<!-- MySQL连接驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

5.3.3在application.properties中配置数据库和jpa的相关属性

#DB Configuration:
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?
useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
#JPA Configuration:
spring.jpa.database=MySQL
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy

5.3.4创建实体配置实体

@Entity
public class User {
    // 主键
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // 用户名
    private String username;
    // 密码
    private String password;
    // 姓名
    private String name;
    //此处省略setter和getter方法... ...
}

5.3.5 编写UserRepository

public interface UserRepository extends JpaRepository<User,Long>{
	public List<User> findAll();
}

5.3.6 编写测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes=MySpringBootApplication.class)
public class JpaTest {

    @Autowired
    private UserRepository userRepository;
    
    @Test
    public void test(){
    List<User> users = userRepository.findAll();
    	System.out.println(users);
    }
}

5.3.7控制台打印信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XWF6fSzn-1660034045637)(pic\1559572900120.png)]

注意:如果是jdk9,执行报错如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-91FksMez-1660034045637)(pic\1561357031534.png)]

原因:jdk缺少相应的jar

解决方案:手动导入对应的maven坐标,如下:

<!--jdk9需要导入如下坐标-->
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>

5.4SpringBoot整合Redis

5.4.1添加redis的起步依赖

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- 配置使用redis启动器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

5.4.2 配置redis的连接信息

#Redis
spring.redis.host=127.0.0.1
spring.redis.port=6379

5.4.3 配置RedisConnectionFactory

/**
 * Created by 54110 on 2019-08-01.
 */
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        //使用fastjson序列化
        Jackson2JsonRedisSerializer fastJsonRedisSerializer = new Jackson2JsonRedisSerializer (Object.class);
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }


5.4.4 配置RedisUtils

package com.qf;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Created by 54110 on 2019-08-01.
 */
@Component
public class RedisUtils {
    @Autowired
    private RedisTemplate redisTemplate;

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    //=============================common============================
    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key,long time){
        try {
            if(time>0){
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key){
        return redisTemplate.getExpire(key,TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key){
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String ... key){
        if(key!=null&&key.length>0){
            if(key.length==1){
                redisTemplate.delete(key[0]);
            }else{
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================String=============================
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key){
        return key==null?null:redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key,Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key,Object value,long time){
        try {
            if(time>0){
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            }else{
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     * @param key 键
     * @param by 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     * @param key 键
     * @param by 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    //================================Map=================================
    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key,String item){
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object,Object> hmget(String key){
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String,Object> map){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String,Object> map, long time){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key,String item,Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key,String item,Object value,long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }

    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item){
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item,double by){
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item,double by){
        return redisTemplate.opsForHash().increment(key, item,-by);
    }

    //============================set=============================
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key){
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key,Object value){
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object...values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key,long time,Object...values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if(time>0) expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key){
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object ...values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //===============================list=================================

    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束  0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end){
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     * @param key 键
     * @return
     */
    public long lGetListSize(String key){
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key,long index){
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index,Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key,long count,Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //=========================Zset 有序不重复集合====================================

    /**
     * 添加一个元素, zset与set最大的区别就是每个元素都有一个score,因此有个排序的辅助功能;  zadd
     *
     * @param key
     * @param value
     * @param score
     */
    public boolean ZSet(String key, String value, double score) {

        try {
            redisTemplate.opsForZSet().add(key, value, score);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }
    /**
     * 删除元素 zrem
     *
     * @param key
     * @param value
     */
    public boolean ZRemove(String key, String value) {
        try {
            redisTemplate.opsForZSet().remove(key, value);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * score的增加or减少 zincrby
     *
     * @param key
     * @param value
     * @param score
     */
    public Double ZIncrScore(String key, String value, double score) {
        return redisTemplate.opsForZSet().incrementScore(key, value, score);
    }
    /**
     * 查询value对应的score   zscore
     *
     * @param key
     * @param value
     * @return
     */
    public Double ZScore(String key, String value) {
        return  redisTemplate.opsForZSet().score(key, value);
    }

    /**
     * 判断value在zset中的排名  zrank
     *
     * @param key
     * @param value
     * @return
     */
    public Long ZRank(String key, String value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }

    /**
     * 查询集合中指定顺序的值, 0 -1 表示获取全部的集合内容  zrange
     *
     * 返回有序的集合,score小的在前面
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<String> ZRange(String key, int start, int end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 查询集合中指定顺序的值和score,0, -1 表示获取全部的集合内容
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<ZSetOperations.TypedTuple<String>> ZRangeWithScore(String key, int start, int end) {
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    /**
     * 查询集合中指定顺序的值  zrevrange
     *
     * 返回有序的集合中,score大的在前面
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<String> ZRevRange(String key, int start, int end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }

    /**
     * 根据score的值,来获取满足条件的集合  zrangebyscore
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<String> ZSortRange(String key, int min, int max) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }

}



5.4.5 注入RedisTemplate测试redis操作

package com.qf;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Created by 54110 on 2019-08-01.
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class redistTest {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private RedisUtils redisUtils;

    @Test
    public void testString (){
        redisTemplate.opsForValue().set("username","zhangsan");
        String username = redisTemplate.opsForValue().get("username");
        System.out.println(username);

    }

    //hash 测试
    @Test
    public void testHash(){

        boolean hset = redisUtils.hset("user", "name", "张三");
        if (hset){
            Object user = redisUtils.hasKey("user");
            Map<Object, Object> user1 = redisUtils.hmget("user");
            System.out.println(user1);
        }
    }
    //测试List
    @Test
    public void testList(){
        List list = new ArrayList<>();
        list.add("aaaa");
        list.add("bbbb");
        list.add("cccc");
        list.add("dddd");
        list.add("eeee");
        boolean list1 = redisUtils.lSet("list", list);

        if (list1){
            List<Object> list2 = redisUtils.lGet("list", 0, -1);
            for (Object string:
                    list2) {
                System.out.println(string);
            }
        }
    }
    //测试Set
    @Test
    public void testSet(){
        long set = redisUtils.sSet("set", 123, 123, 123, 123, 456);

        boolean set2 = redisUtils.hasKey("set");
        if (set2){
            Set<Object> set1 = redisUtils.sGet("set");
            for (Object val:
                 set1) {
                System.out.println(val);
            }

        }
    }
    //测试ZSet
    @Test
    public void testZset(){

            redisUtils.ZSet("person","cccc",12);
            redisUtils.ZSet("person","dddd",13);
            redisUtils.ZSet("person","aaaa",10);
            redisUtils.ZSet("person","bbbb",11);




        //scope从小到大排列
        Set<String> person = redisUtils.ZRange("person", 0, -1);

        for (Object val:
                person) {
            System.out.println(val);
        }
        //scope从大到小排列
        Set<String> person1 = redisUtils.ZRevRange("person", 0, -1);
        for (Object val:
                person1) {
            System.out.println(val);
        }
    }
}



5.5 SpringBoot整合quartz 定时任务

5.5.1启动类启用定时

@SpringBootApplication
@EnableScheduling //启动类启用定时
public class MySpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApplication.class);
    }
}

5.5.2创建定时任务实现类

5.5.2.1 定时任务1
package com.qf.Scheduler;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * Created by 54110 on 2019-06-04.
 */
@Component
public class SchedulerTask {

    private int count=0;

    @Scheduled(cron="*/6 * * * * ?")
    private void process() {
        System.out.println("this is scheduler task runing  " + (count++));
    }
}

5.5.2.2定时任务2
@Component
public class Scheduler2Task {    
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");    

    @Scheduled(fixedRate = 6000)    
    public void reportCurrentTime() {
        System.out.println("现在时间:" + dateFormat.format(new Date()));
    }

}

5.5.2.3结果如下
this is scheduler task runing  2
现在时间:13:11:01
this is scheduler task runing  3
现在时间:13:11:07
this is scheduler task runing  4
现在时间:13:11:13
this is scheduler task runing  5
现在时间:13:11:19
this is scheduler task runing  6
现在时间:13:11:25


5.5.3参数说明

@Scheduled 参数可以接受两种定时的设置,一种是我们常用的cron="*/6 * * * * ?",一种是 fixedRate = 6000,两种都表示每隔六秒打印一下内容。

​ fixedRate 说明

  • @Scheduled(fixedRate = 6000) :上一次开始执行时间点之后6秒再执行
  • @Scheduled(fixedDelay = 6000) :上一次执行完毕时间点之后6秒再执行
  • @Scheduled(initialDelay=1000, fixedRate=6000) :第一次延迟1秒后执行,之后按fixedRate的规则每6秒执行一次

5.6 SpringBoot 整合mail邮件

5.6.1 添加mail的起步依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

5.6.2 获取QQ邮箱授权码

QQ邮箱->设置->账户->POP3/SMTP服务:开启服务后会获得QQ的授权码.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S7xXU3vY-1660034045638)(pic\1559628130602.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c9FZuMn3-1660034045638)(pic\1559628344501.png)]

5.6.3 Mail配置文件

5.6.3.1 qq邮箱的配置
# JavaMailSender 邮件发送的配置
spring.mail.host=smtp.qq.com
spring.mail.username=用户qq邮箱
#QQ邮箱的授权码
spring.mail.password=qq邮箱授权码
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.default-encoding=UTF-8

5.6.3.2 163邮箱配置
spring.mail.host=smtp.163.com
spring.mail.username=用户163邮箱
spring.mail.password=邮箱密码
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.default-encoding=UTF-8

5.6.4 实现过程

5.6.4.1配置文件
spring:
  #邮箱配置
  mail:
    host: smtp.qq.com
    username: 541109497@qq.com
    #QQ邮箱的授权码
    password: 自己申请
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true
#设置一个邮件发送人
lance:
  mail:
    sender: 541109497@qq.com

5.6.4.2 实体bean
/**
 * @author mhb(mhb)
 * @function  发送邮件-封装接受者信息
 */
@Data
public class MailBean implements Serializable {
    private String recipient;   //邮件接收人
    private String subject; //邮件主题
    private String content; //邮件内容

}

5.6.4.3 发送简单格式的邮件
@RunWith(SpringRunner.class)
@SpringBootTest(classes =QuartzSpringBootApplication.class )

	
	public class client {
	
	
    @Value("${spring.mail.username}")
    private String MAIL_SENDER;
    @Autowired
    private JavaMailSender javaMailSender;
    private static final Logger LOG = LoggerFactory.getLogger(Logger.class);
	
	//发送简单的邮件
	@Test
	public void contextLoads() {

		MailBean mailBean = new MailBean();
		mailBean.setContent("测试1111");
		mailBean.setRecipient("miao_18035739505@163.com");
		mailBean.setSubject("测试邮件");

		try {
			SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
			//邮件发送人
			simpleMailMessage.setFrom(MAIL_SENDER);
			//邮件接收人
			simpleMailMessage.setTo(mailBean.getRecipient());
			//邮件主题
			simpleMailMessage.setSubject(mailBean.getSubject());
			//邮件内容
			simpleMailMessage.setText(mailBean.getContent());
			javaMailSender.send(simpleMailMessage);
		} catch (Exception e) {
			LOG.error("邮件发送失败", e.getMessage());
		}

	}
}

5.6.4.4 发送html格式的邮件
//发送html格式的邮件
	@Test
	public void sendHTMLMail() {
		MailBean mailBean = new MailBean();
		mailBean.setContent("测试html格式的邮件");
		mailBean.setRecipient("miao_18035739505@163.com");
		mailBean.setSubject("测试html格式的邮件");
		MimeMessage mimeMailMessage = null;
		try {
			mimeMailMessage = javaMailSender.createMimeMessage();
			MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMailMessage, true);
			mimeMessageHelper.setFrom(MAIL_SENDER);
			mimeMessageHelper.setTo(mailBean.getRecipient());
			mimeMessageHelper.setSubject(mailBean.getSubject());
			StringBuilder sb = new StringBuilder();
			sb.append("<h1>SpirngBoot测试邮件HTML</h1>")
					.append("\"<p style='color:#F00'>你是真的太棒了!</p>")
					.append("<p style='text-align:right'>右对齐</p>");
			mimeMessageHelper.setText(sb.toString(), true);
			javaMailSender.send(mimeMailMessage);
		} catch (Exception e) {
			LOG.error("邮件发送失败", e.getMessage());
		}
	}

5.6.4.5 发送带附件格式的邮件
   //发送带附件格式的邮件
	@Test
	public void sendAttachmentMail() {
		MailBean mailBean = new MailBean();
		mailBean.setContent("测试附件格式的邮件");
		mailBean.setRecipient("miao_18035739505@163.com");
		mailBean.setSubject("测试附件格式的邮件");
		MimeMessage mimeMailMessage = null;
		try {
			mimeMailMessage = javaMailSender.createMimeMessage();
			MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMailMessage, true);
			mimeMessageHelper.setFrom(MAIL_SENDER);
			mimeMessageHelper.setTo(mailBean.getRecipient());
			mimeMessageHelper.setSubject(mailBean.getSubject());
			mimeMessageHelper.setText(mailBean.getContent());
			//文件路径
			FileSystemResource file = new FileSystemResource(new File("src/main/resources/static/image/mail.jpg"));
			mimeMessageHelper.addAttachment("mail.jpg", file);

			javaMailSender.send(mimeMailMessage);
		} catch (Exception e) {
			LOG.error("邮件发送失败", e.getMessage());
		}
	}

5.6.4.6 发送基于thymeleaf为模板的邮件

1)pom中导入thymeleaf的包

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

2)在resorces/templates下创建emailTemplate.html

<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
您好,这是验证邮件,请点击下面的链接完成验证,<br/>
<a href="#" th:href="@{http://www.mobiletrain.org/{id}(id=${id}) }">激活账号</a>
</body>
</html>

3)解析模板并发送

 @Test
    public void sendTemplateMail() {
        //创建邮件正文
        Context context = new Context();
        context.setVariable("id", "006");
        String emailContent = templateEngine.process("emailTemplate", context);

        MimeMessage mimeMailMessage = null;
        try {
            mimeMailMessage = javaMailSender.createMimeMessage();
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMailMessage, true);
            mimeMessageHelper.setFrom(MAIL_SENDER);
            mimeMessageHelper.setTo("miao_18035739505@163.com");
            mimeMessageHelper.setSubject("测试thymyleaf");
           /* StringBuilder sb = new StringBuilder();
            sb.append(emailContent);*/

            mimeMessageHelper.setText(emailContent,true);
            javaMailSender.send(mimeMailMessage);
        } catch (Exception e) {
            LOG.error("邮件发送失败", e.getMessage());
        }
    }


5.7 SpringBoot 整合 thymeleaf静态化模板

5.7.1添加Thymeleaf依赖

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

Spring Boot默认存放模板页面的路径在src/main/resources/templates或者src/main/view/templates,这个无论是使用什么模板语言都一样,当然默认路径是可以自定义的,不过一般不推荐这样做。另外Thymeleaf默认的页面文件后缀是.html

5.7.2数据显示

在MVC的开发过程中,我们经常需要通过Controller将数据传递到页面中,让页面进行动态展示

5.7.2.1显示普通的文本

创建一个Controller对象,在其中进行参数的传递

@Controller
@RequestMapping("/thymeleaf")
public class ThymeleafController {

        @RequestMapping(value = "show", method = RequestMethod.GET)
        public String show(Model model){
            model.addAttribute("uid","123456789");
            model.addAttribute("name","Jerry");
            return "show";
        }

}

在SpringBoot默认的页面路径下创建show.html文件,内容如下

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>SpringBoot模版渲染</title>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
    <p th:text="'用户ID:' + ${uid}"/>
    <p th:text="'用户名称:' + ${name}"/>
</body>
</html>

果我们要显示的信息是存在资源文件中的,同样可以在页面中显示,例如资源文件中定义了内容welcome.msg=你好你好光临!。可以在页面中将其显示

<h2 th:text="#{welcome.msg}"/>

另外,在th:utext中还能做一些基础的数学运算

<p th:text="'数学计算:1+2=' + (1 + 2)"/>

5.7.2.2 显示带有样式的普通文本

如果我们想要传递到的页面的信息,它本身是带有CSS样式的,这个时候如何在页面中将携带的样式信息也显示出来?此时我们的控制器方法这样写。

@RequestMapping(value = "showStyle", method = RequestMethod.GET)
public String showStyle(Model model){
    model.addAttribute("uid","123456789");
    model.addAttribute("name","<span style='color:red'>Jerry</span>");
    return "show_style";
}

此时页面中需要借助th:utext属性进行显示

<p th:utext="'用户名称:' + ${name}"/>

通过浏览器查看页面源码可以看出th:utextth:text的区别是:th:text会对<>进行转义,而th:utext不会转义。

5.7.2.3显示对象

我们常常需要将一个bean信息展示在前端页面当中。

1.用于前端展示的VO类

public class User implements Serializable {
    private Long uid ;
    private String name ;
    private Integer age ;
    private Date birthday ;
    private Double salary ;
    //省略get/set方法
}

2.控制器方法

 @RequestMapping(value = "/member_show", method = RequestMethod.GET)
        public String memberShow(Model model) {
            User vo = new User();
            vo.setUid(12345678L);
            vo.setName("尼古拉丁.赵四");
            vo.setAge(59);
            vo.setSalary(1000.00);
            vo.setBirthday(new Date());
            model.addAttribute("member", vo);
            return "member_show";
     }

3.页面展示

<div>
    <p th:text="'用户编号:' + ${member.uid}"/>
    <p th:text="'用户姓名:' + ${member.name}"/>
    <p th:text="'用户年龄:' + ${member.age}"/>
    <p th:text="'用户工资:' + ${member.salary}"/>
    <p th:text="'出生日期:' + ${member.birthday}"/>
    <p th:text="'出生日期:' + ${#dates.format(member.birthday,'yyyy-MM-dd')}"/>
</div>

<hr/>
<div th:object="${member}">
    <p th:text="'用户编号:' + *{uid}"/>
    <p th:text="'用户姓名:' + *{name}"/>
    <p th:text="'用户年龄:' + *{age}"/>
    <p th:text="'用户工资:' + *{salary}"/>
    <p th:text="'出生日期:' + *{birthday}"/>
    <p th:text="'出生日期:' + *{#dates.format(birthday,'yyyy-MM-dd')}"/>
</div>

上面给出了两种展现方式,一种是通过 属性,另外一种是通过 ∗ 属性。 ∗ ∗ 关于“ {属性},另外一种是通过*{属性}。**关于“ 属性,另外一种是通过属性关于{属性}”和“{属性}”的区别?**$访问完整信息,而*访问指定对象中的属性内容, 如果访问的只是普通的内容两者没有区别;

5.7.2.4 数据逻辑处理

所有的页面模版都存在各种基础逻辑处理,例如:判断、循环处理操作。在 Thymeleaf 之中对于逻辑可以使用如下的一些运算符来完成,例如:and、or、关系比较(>、<、>=、<=、==、!=、lt、gt、le、ge、eq、ne)。

通过控制器传递一些属性内容到页面之中:

<span th:if="${member.age lt 18}">
未成年人!
</span>
<span th:if="${member.name eq '啊三'}">
欢迎小三来访问!
</span>

不满足条件的判断

<span th:unless="${member.age gt 18}">
你还不满18岁,不能够看电影!
</span>

通过swith进行分支判断

<span th:switch="${member.uid}">
<p th:case="100">uid为101的员工来了</p>
<p th:case="99">uid为102的员工来了</p>
<p th:case="*">没有匹配成功的数据!</p>
</span>

5.7.3数据遍历

在实际开发过程中常常需要对数据进行遍历展示,一般会将数据封装成list或map传递到页面进行遍历操作。

1.定义控制器类,向页面传递List数据和Map数据

    @RequestMapping(value = "/user/map", method = RequestMethod.GET)
    public String map(Model model) {
        Map<String,User> allMembers = new HashMap<String,User>();
        for (int x = 0; x < 10; x++) {
            User vo = new User();
            vo.setUid(101L + x);
            vo.setName("赵四 - " + x);
            vo.setAge(9);
            vo.setSalary(99999.99);
            vo.setBirthday(new Date());
            allMembers.put("mldn-" + x, vo);
        }
        model.addAttribute("allUsers", allMembers);
        return "user_map";
    }

    @RequestMapping(value = "/user/list", method = RequestMethod.GET)
    public String list(Model model) {
        List<User> allMembers = new ArrayList<User>();
        for (int x = 0; x < 10; x++) {
            User vo = new User();
            vo.setUid(101L + x);
            vo.setName("赵四 - " + x);
            vo.setAge(9);
            vo.setSalary(99999.99);
            vo.setBirthday(new Date());
            allMembers.add(vo) ;
        }
        model.addAttribute("allUsers", allMembers);
        return "user_list";
    }


<body>
<table>
    <tr><td>No.</td><td>KEY</td><td>UID</td><td>姓名</td><td>年龄</td><td>偶数</td><td>奇数</td></tr>
    <tr th:each="memberEntry,memberStat:${allUsers}">
        <td th:text="${memberStat.index + 1}"/>
        <td th:text="${memberEntry.uid}"/>
        <td th:text="${memberEntry.name}"/>
        <td th:text="${memberEntry.age}"/>
        <td th:text="${memberStat.even}"/>
        <td th:text="${memberStat.odd}"/>
    </tr>
</table>
</body>

<body>
    <table>
        <tr><td>No.</td><td>KEY</td><td>UID</td><td>姓名</td><td>年龄</td><td>偶数</td><td>奇数</td></tr>
        <tr th:each="memberEntry,memberStat:${allUsers}">
            <td th:text="${memberStat.index + 1}"/>
            <td th:text="${memberEntry.key}"/>
            <td th:text="${memberEntry.value.uid}"/>
            <td th:text="${memberEntry.value.name}"/>
            <td th:text="${memberEntry.value.age}"/>
            <td th:text="${memberStat.even}"/>
            <td th:text="${memberStat.odd}"/>
        </tr>
    </table>
</body>

  1. map类型数据遍历
    <body>
        <table>
            <tr><td>No.</td><td>KEY</td><td>UID</td><td>姓名</td><td>年龄</td><td>偶数</td><td>奇数</td></tr>
            <tr th:each="memberEntry,memberStat:${allUsers}">
                <td th:text="${memberStat.index + 1}"/>
                <td th:text="${memberEntry.key}"/>
                <td th:text="${memberEntry.value.uid}"/>
                <td th:text="${memberEntry.value.name}"/>
                <td th:text="${memberEntry.value.age}"/>
                <td th:text="${memberStat.even}"/>
                <td th:text="${memberStat.odd}"/>
            </tr>
        </table>
    </body>

2.list类型数据遍历

<body>
    <table>
        <tr><td>No.</td><td>UID</td><td>姓名</td><td>年龄</td><td>偶数</td><td>奇数</td></tr>
        <tr th:each="user,memberStat:${allUsers}">
            <td th:text="${memberStat.index + 1}"/>
            <td th:text="${user.uid}"/>
            <td th:text="${user.name}"/>
            <td th:text="${user.age}"/>
            <td th:text="${memberStat.even}"/>
            <td th:text="${memberStat.odd}"/>
        </tr>
    </table>
</body>

5.7.4 页面引入

我们常常需要在一个页面当中引入另一个页面,例如,公用的导航栏以及页脚页面。thymeleaf中提供了两种方式进行页面引入。

  • th:replace
  • th:include
  1. 新建需要被引入的页面文件,路径为"src/main/view/templates/commons/footer.html"

    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
    <footer th:fragment="companyInfo">
        <p>设为首页 ©2018 SpringBoot 使用<span th:text="${projectName}"/>前必读
            意见反馈 京ICP证030173号 </p>
    </footer>
    
    

可以看到页面当中还存在一个变量projectName,这个变量的值可以在引入页面中通过th:with="projectName=百度"传过来。

  1. 引入页面中只需要添加如下代码即可
<div th:include="@{/commons/footer} :: companyInfo" th:with="projectName=百度"/>

5.8 springboot整合jsp页面

5.8.1 加入jsp依赖
<!--        JSP核心引擎依赖-->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
		</dependency>
		<!--        JSTL-->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
		</dependency>
5.8.2 创建webapp

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kUGiZ6lS-1660034045640)(E:/v/m/%E7%AC%AC%E4%B8%80%E5%91%A8/%E7%AC%AC%E4%B8%80%E5%A4%A9/%E6%96%87%E6%A1%A3/pic/web-inf.jpg)]

5.8.3 添加配置文件、
spring.mvc.view.suffix=.jsp
spring.mvc.view.prefix=/WEB-INF/


5.8.4 页面编码格式以及c标签的引入
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
//通过cdn的方式引入jquery
<script src="http://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script>



5.8.5 页面编写 查询全部
<!DOCTYPE html>
<html lang="en">
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>
       <table border="1">
        <tr>
            <td>用户id</td>
            <td>用户名</td>
            <td>用户的地址</td>
            <td>操作</td>
        </tr>
            <c:forEach items="${userlist}" var="user">
           <tr>
                <td>${user.id}</td>
                <td>${user.userName}</td>
                <td>${user.address}</td>
              <td> <a href="/del/${user.id}">删除</a></td>
              <td> <a href="/upd/${user.id}">修改</a></td>
           </tr>
            </c:forEach>

       </table>
    </div>

</body>
</html>


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值