狂神说SpringBoot

转自狂神老师,很是感谢老师,仅做为我的笔记使用javascript

一、SpringBoot: Hello,Worldphp

二、SpringBoot: 运行原理初探css

三、SpringBoot: yaml配置注入html

四、SpringBoot: JSR303数据校验及多环境切换前端

五、SpringBoot:自动配置原理html5

六、SpringBoot: 自定义starterjava

七、SpringBoot整合JDBCmysql

八、SpringBoot整合Druidjquery

九、SpringBoot 整合mybatisgit

十、SpringBoot: Web开发静态资源处理

十一、SpringBoot: Thymeleaf模板引擎

十二、SpringBoot: MVC自动配置原理

1三、SpringBoot: 页面国际化

1四、SpringBoot: 集成Swagger终极版

1五、SpringBoot: 异步、定时、邮件任务

1六、SpringBoot: 富文本编辑器

1七、SpringBoot: Dubbo和Zookeeper集成

1八、SpringBoot: 集成SpringSecurity

一、SpringBoot:Hello,World!

SpringBoot简介

1.一、回顾什么是Spring

Spring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,做者:Rod Johnson 。

Spring是为了解决企业级应用开发的复杂性而建立的,简化开发。

**
**

1.二、Spring是如何简化Java开发的

为了下降Java开发的复杂性,Spring采用了如下4种关键策略:

一、基于POJO的轻量级和最小侵入性编程,全部东西都是bean;

二、经过IOC,依赖注入(DI)和面向接口实现松耦合;

三、基于切面(AOP)和惯例进行声明式编程;

四、经过切面和模版减小样式代码,RedisTemplate,xxxTemplate;

1.三、什么是SpringBoot

学过javaweb的同窗就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat, 跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了如今的SpringBoot,过一两年又会有其余web框架出现;大家有经历过框架不断的演进,而后本身开发项目全部的技术也在不断的变化、改造吗?建议均可以去经历一遍;

言归正传,什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC相似,对比其余javaweb框架的好处,官方说是简化开发,约定大于配置, you can "just run",能迅速的开发web应用,几行代码开发一个http接口。

全部的技术框架的发展彷佛都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只须要进行各类配置而不须要本身去实现它,这时候强大的配置功能成了优势;发展到必定程度以后,人们根据实际生产应用状况,选取其中实用功能和设计精华,重构出一些轻量级的框架;以后为了提升开发效率,嫌弃原先的各种配置过于麻烦,因而开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。

是的这就是Java企业级应用->J2EE->spring->springboot的过程。

随着 Spring 不断的发展,涉及的领域愈来愈多,项目整合开发须要配合各类各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让你们更容易的使用 Spring 、更容易的集成各类经常使用的中间件、开源软件;

Spring Boot 基于 Spring 开发,Spirng Boot 自己并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并非用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提高 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮咱们进行了不少设置,多数 Spring Boot 应用只须要不多的 Spring 配置。同时它集成了大量经常使用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎能够零配置的开箱即用。

简单来讲就是SpringBoot其实不是什么新的框架,它默认配置了不少框架的使用方式,就像maven整合了全部的jar包,spring boot整合了全部的框架 。

Spring Boot 出生名门,从一开始就站在一个比较高的起点,又通过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。

Spring Boot的主要优势:

  • 为全部Spring开发者更快的入门
  • 开箱即用,提供各类默认配置来简化项目配置
  • 内嵌式容器简化Web项目
  • 没有冗余代码生成和XML配置的要求

真的很爽,咱们快速去体验开发个接口的感受吧!

Hello,World

1.四、准备工做

咱们将学习如何快速的建立一个Spring Boot应用,而且实现一个简单的Http请求处理。经过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。

个人环境准备:

  • java version "1.8.0_181"
  • Maven-3.6.1
  • SpringBoot 2.x 最新版

开发工具:

  • IDEA

1.五、建立基础项目说明

Spring官方提供了很是方便的工具让咱们快速构建应用

Spring Initializr:https://start.spring.io/

项目建立方式一:使用Spring Initializr 的 Web页面建立项目

一、打开 https://start.spring.io/

二、填写项目信息

三、点击”Generate Project“按钮生成项目;下载此项目

四、解压项目包,并用IDEA以Maven项目导入,一路下一步便可,直到项目导入完毕。

五、若是是第一次使用,可能速度会比较慢,包比较多、须要耐心等待一切就绪。

项目建立方式二:使用 IDEA 直接建立项目

一、建立一个新项目

二、选择spring initalizr , 能够看到默认就是去官网的快速构建工具那里实现

三、填写项目信息

四、选择初始化的组件(初学勾选 Web 便可)

五、填写项目路径

六、等待项目构建成功

项目结构分析:

经过上面步骤完成了基础项目的建立。就会自动生成如下文件。

一、程序的主启动类

二、一个 application.properties 配置文件

三、一个 测试类

四、一个 pom.xml

1.六、pom.xml 分析

打开pom.xml,看看Spring Boot项目的依赖:

<!-- 父依赖 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath/>
</parent>

<dependencies>
    <!-- web场景启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- springboot单元测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <!-- 剔除依赖 -->
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- 打包插件 -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

1.七、编写一个http接口

一、在主程序的同级目录下,新建一个controller包,必定要在同级目录下,不然识别不到

二、在包中新建一个HelloController类

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        return "Hello World";
    }
    
}

三、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!

简单几步,就完成了一个web接口的开发,SpringBoot就是这么简单。因此咱们经常使用它来创建咱们的微服务项目!

1.八、将项目打成jar包,点击 maven的 package

若是遇到以上错误,能够配置打包时 跳过项目运行测试用例

<!--
    在工做中,不少状况下咱们打包是不想执行测试用例的
    多是测试用例不完事,或是测试用例会影响数据库数据
    跳过测试用例执
    -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <!--跳过项目运行测试用例-->
        <skipTests>true</skipTests>
    </configuration>
</plugin>

若是打包成功,则会在target目录下生成一个 jar 包

打成了jar包后,就能够在任何地方运行了!OK

彩蛋

如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是 banner 图案;

只需一步:到项目下的 resources 目录下新建一个banner.txt 便可。

图案能够到:https://www.bootschool.net/ascii 这个网站生成,而后拷贝到文件中便可!

**SpringBoot这么简单的东西背后必定有故事,咱们以后

二、SpringBoot:运行原理初探

咱们以前写的HelloSpringBoot,究竟是怎么运行的呢,Maven项目,咱们通常从pom.xml文件探究起;

pom.xml

2.一、父依赖

其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!

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

点进去,发现还有一个父依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

这里才是真正管理SpringBoot应用里面全部依赖版本的地方,SpringBoot的版本控制中心;

之后咱们导入依赖默认是不须要写版本;可是若是导入的包没有在依赖中管理着就须要手动配置版本了;

2.二、启动器 spring-boot-starter

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

springboot-boot-starter-xxx:就是spring-boot的场景启动器

spring-boot-starter-web:帮咱们导入了web模块正常运行所依赖的组件;

SpringBoot将全部的功能场景都抽取出来,作成一个个的starter (启动器),只须要在项目中引入这些starter便可,全部相关的依赖都会导入进来 , 咱们要用什么功能就导入什么样的场景启动器便可 ;咱们将来也能够本身自定义 starter;

主启动类

分析完了 pom.xml 来看看这个启动类

2.三、默认的主启动类

//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {

   public static void main(String[] args) {
     //觉得是启动了一个方法,没想到启动了一个服务
      SpringApplication.run(SpringbootApplication.class, args);
   }

}

可是一个简单的启动类并不简单!咱们来分析一下这些注解都干了什么

2.四、@SpringBootApplication

做用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;

进入这个注解:能够看到上面还有不少其余注解!

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    // ......
}

2.五、@ComponentScan

这个注解在Spring中很重要 ,它对应XML配置中的元素。

做用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中

2.六、@SpringBootConfiguration

做用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;

咱们继续进去这个注解查看

// 点进去获得下面的 @Component
@Configuration
public @interface SpringBootConfiguration {}

@Component
public @interface Configuration {}

这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;

里面的 @Component 这就说明,启动类自己也是Spring中的一个组件而已,负责启动应用!

咱们回到 SpringBootApplication 注解中继续看。

2.七、@EnableAutoConfiguration

@EnableAutoConfiguration :开启自动配置功能

之前咱们须要本身配置的东西,而如今SpringBoot能够自动帮咱们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;

点进注解接续查看:

@AutoConfigurationPackage :自动配置包

@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

@import :Spring底层注解@import , 给容器中导入一个组件

Registrar.class 做用:将主启动类的所在包及包下面全部子包里面的全部组件扫描到Spring容器 ;

这个分析完了,退到上一步,继续看

@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;

AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?咱们点击去这个类看源码:

一、这个类中有一个这样的方法

// 得到候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //这里的getSpringFactoriesLoaderFactoryClass()方法
    //返回的就是咱们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

二、这个方法又调用了 SpringFactoriesLoader 类的静态方法!咱们进入SpringFactoriesLoader类loadFactoryNames() 方法

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    //这里它又调用了 loadSpringFactories 方法
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

三、咱们继续点击查看 loadSpringFactories 方法

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //得到classLoader , 咱们返回能够看到这里获得的就是EnableAutoConfiguration标注的类自己
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            //去获取一个资源 "META-INF/spring.factories"
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            //将读取到的资源遍历,封装成为一个Properties
            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryClassName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;

                    for(int var11 = 0; var11 < var10; ++var11) {
                        String factoryName = var9[var11];
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }

            cache.put(classLoader, result);
            return result;
        } catch (IOException var13) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
        }
    }
}

四、发现一个屡次出现的文件:spring.factories,全局搜索它

2.八、spring.factories

咱们根据源头打开spring.factories , 看到了不少自动配置的文件;这就是自动配置根源所在!

WebMvcAutoConfiguration

咱们在上面的自动配置类随便找一个打开看看,好比 :WebMvcAutoConfiguration

能够看到这些一个个的都是JavaConfig配置类,并且都注入了一些Bean,能够找一些本身认识的类,看着熟悉一下!

因此,自动配置真正实现是从classpath中搜寻全部的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,经过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 而后将这些都汇总成为一个实例并加载到IOC容器中。

结论:

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
  2. 将这些值做为自动配置类导入容器 , 自动配置类就生效 , 帮咱们进行自动配置工做;
  3. 整个J2EE的总体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  4. 它会给容器中导入很是多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景须要的全部组件 , 并配置好这些组件 ;
  5. 有了自动配置类 , 免去了咱们手动编写配置注入功能组件等的工做;

如今你们应该大概的了解了下,SpringBoot的运行原理,后面咱们还会深化一次!

SpringApplication

2.九、不简单的方法

我最初觉得就是运行了一个main方法,没想到却开启了一个服务;

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

SpringApplication.run分析

分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;

2.十、SpringApplication

这个类主要作了如下四件事情:

一、推断应用的类型是普通的项目仍是Web项目

二、查找并加载全部可用初始化器 , 设置到initializers属性中

三、找出全部的应用程序监听器,设置到listeners属性中

四、推断并设置main方法的定义类,找到运行的主类

查看构造器:

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    // ......
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances();
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

2.十一、run方法流程分析


跟着源码和这幅图就能够一探究竟了!

三、SpringBoot:yaml配置注入

yaml语法学习

3.一、配置文件

SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的

  • application.properties

    • 语法结构 :key=value
  • application.yml

    • 语法结构 :key:空格 value

配置文件的做用 :修改SpringBoot自动配置的默认值,由于SpringBoot在底层都给咱们自动配置好了;

好比咱们能够在配置文件中修改Tomcat 默认启动的端口号!测试一下!

server.port=8081

3.二、yaml概述

YAML是 "YAML Ain't a Markup Language" (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思实际上是:"Yet Another Markup Language"(还是一种标记语言)

这种语言以数据为中心,而不是以标记语言为重点!

之前的配置文件,大多数都是使用xml来配置;好比一个简单的端口配置,咱们来对比下yaml和xml

传统xml配置:

<server>
    <port>8081<port>
</server>

yaml配置:

server:
  prot: 8080

3.三、yaml基础语法

说明:语法要求严格!

一、空格不能省略

二、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。

三、属性和值的大小写都是十分敏感的。

字面量:普通的值 [ 数字,布尔值,字符串 ]

字面量直接写在后面就能够 , 字符串默认不用加上双引号或者单引号;

k: v

注意:

  • “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会做为自己想表示的意思;

    好比 :name: "kuang \n shen" 输出 :kuang 换行 shen

  • '' 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符同样输出

    好比 :name: ‘kuang \n shen’ 输出 :kuang \n shen

对象、Map(键值对)

#对象、Map格式
k: 
    v1:
    v2:

在下一行来写对象的属性和值得关系,注意缩进;好比:

student:
    name: qinjiang
    age: 3

行内写法

student: {name: qinjiang,age: 3}

数组( List、set )

用 - 值表示数组中的一个元素,好比:

pets:
 - cat
 - dog
 - pig

行内写法

pets: [cat,dog,pig]

修改SpringBoot的默认端口号

配置文件中添加,端口号的参数,就能够切换端口;

server:  
  port: 8082

注入配置文件

yaml文件更强大的地方在于,他能够给咱们的实体类直接注入匹配值!

3.四、yaml注入配置文件

一、在springboot项目中的resources目录下新建一个文件 application.yml

二、编写一个实体类 Dog;

package com.kuang.springboot.pojo;

@Component  //注册bean到容器中
public class Dog {
    private String name;
    private Integer age;
    
    //有参无参构造、get、set方法、toString()方法  
}

三、思考,咱们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下:

@Component //注册bean
public class Dog {
    @Value("阿黄")
    private String name;
    @Value("18")
    private Integer age;
}

四、在SpringBoot的测试类下注入狗狗输出一下;

@SpringBootTest
class DemoApplicationTests {

    @Autowired //将狗狗自动注入进来
    Dog dog;

    @Test
    public void contextLoads() {
        System.out.println(dog); //打印看下狗狗对象
    }

}

结果成功输出,@Value注入成功,这是咱们原来的办法对吧。

五、咱们在编写一个复杂一点的实体类:Person 类

@Component //注册bean到容器中
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
    
    //有参无参构造、get、set方法、toString()方法  
}

六、咱们来使用yaml配置的方式进行注入,你们写的时候注意区别和优点,咱们编写一个yaml配置!

person:
  name: qinjiang
  age: 3
  happy: false
  birth: 2000/01/01
  maps: {k1: v1,k2: v2}
  lists:
   - code
   - girl
   - music
  dog:
    name: 旺财
    age: 1

七、咱们刚才已经把person这个对象的全部值都写好了,咱们如今来注入到咱们的类中!

/*
@ConfigurationProperties做用:
将配置文件中配置的每个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的全部属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的全部属性一一对应
*/
@Component //注册bean
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
}

八、IDEA 提示,springboot配置注解处理器没有找到,让咱们看文档,咱们能够查看文档,找到一个依赖!

<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,须要重启 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

九、确认以上配置都OK以后,咱们去测试类中测试一下:

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    Person person; //将person自动注入进来

    @Test
    public void contextLoads() {
        System.out.println(person); //打印person信息
    }

}

结果:全部值所有注入成功!

yaml配置注入到实体类彻底OK!

课堂测试:

一、将配置文件的key 值 和 属性的值设置为不同,则结果输出为null,注入失败

二、在配置一个person2,而后将 @ConfigurationProperties(prefix = "person2") 指向咱们的person2;

3.五、加载指定的配置文件

@PropertySource :加载指定的配置文件;

@configurationProperties:默认从全局配置文件中获取值;

一、咱们去在resources目录下新建一个person.properties文件

name=kuangshen

二、而后在咱们的代码中指定加载person.properties文件

@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {

    @Value("${name}")
    private String name;

    ......  
}

三、再次输出测试一下:指定配置文件绑定成功!

3.六、配置文件占位符

配置文件还能够编写占位符生成随机数

person:
    name: qinjiang${random.uuid} # 随机uuid
    age: ${random.int}  # 随机int
    happy: false
    birth: 2000/01/01
    maps: {k1: v1,k2: v2}
    lists:
      - code
      - girl
      - music
    dog:
      name: ${person.hello:other}_旺财
      age: 1

3.七、回顾properties配置

咱们上面采用的yaml方法都是最简单的方式,开发中最经常使用的;也是springboot所推荐的!那咱们来唠唠其余的实现方式,道理都是相同的;写仍是那样写;配置文件除了yml还有咱们以前经常使用的properties , 咱们没有讲,咱们来唠唠!

【注意】properties配置文件在写中文的时候,会有乱码 , 咱们须要去IDEA中设置编码格式为UTF-8;

settings-->FileEncodings 中配置;

测试步骤:

一、新建一个实体类User

@Component //注册bean
public class User {
    private String name;
    private int age;
    private String sex;
}

二、编辑配置文件 user.properties

user1.name=kuangshen
user1.age=18user1.sex=男

三、咱们在User类上使用@Value来进行注入!

@Component //注册bean
@PropertySource(value = "classpath:user.properties")
public class User {
    //直接使用@value
    @Value("${user.name}") //从配置文件中取值
    private String name;
    @Value("#{9*2}")  // #{SPEL} Spring表达式
    private int age;
    @Value("男")  // 字面量
    private String sex;
}

四、Springboot测试

user1.name=kuangshen
user1.age=18
user1.sex=男

结果正常输出:

3.八、对比小结

@Value这个使用起来并不友好!咱们须要为每一个属性单独注解赋值,比较麻烦;咱们来看个功能对比图

一、@ConfigurationProperties只须要写一次便可 , @Value则须要每一个字段都添加

二、松散绑定:这个什么意思呢? 好比个人yml中写的last-name,这个和lastName是同样的, - 后面跟着的字母默认是大写的。这就是松散绑定。能够测试一下

三、JSR303数据校验 , 这个就是咱们能够在字段是增长一层过滤器验证 , 能够保证数据的合法性

四、复杂类型封装,yml中能够封装对象 , 使用value就不支持

结论:

配置yml和配置properties均可以获取到值 , 强烈推荐 yml;

若是咱们在某个业务中,只须要获取配置文件中的某个值,能够使用一下 @value;

若是说,咱们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

四、SpringBoot:JSR303数据校验及多环境切换

JSR303数据校验

4.一、先看看如何使用

Springboot中能够用@validated来校验数据,若是数据异常则会统一抛出异常,方便异常中心统一处理。咱们这里来写个注解让咱们的name只能支持Email格式;

@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {

    @Email(message="邮箱格式错误") //name必须是邮箱格式
    private String name;
}

运行结果 :default message [不是一个合法的电子邮件地址];

使用数据校验,能够保证数据的正确性;

4.二、常见参数

@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null       验证对象是否为null
@NotNull    验证对象是否不为null, 没法查检长度为0的字符串
@NotBlank   检查约束字符串是否是Null还有被Trim的长度是否大于0,只对字符串,且会去掉先后空格.
@NotEmpty   检查约束元素是否为NULL或者是EMPTY.
    
Booelan检查
@AssertTrue     验证 Boolean 对象是否为 true  
@AssertFalse    验证 Boolean 对象是否为 false  
    
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围以内  
@Length(min=, max=) string is between min and max included.

日期检查
@Past       验证 Date 和 Calendar 对象是否在当前时间以前  
@Future     验证 Date 和 Calendar 对象是否在当前时间以后  
@Pattern    验证 String 对象是否符合正则表达式的规则

.......等等
除此之外,咱们还能够自定义一些数据校验规则

多环境切换

profile是Spring对不一样环境提供不一样配置功能的支持,能够经过激活不一样的环境版本,实现快速切换环境;

4.三、多配置文件

咱们在主配置文件编写的时候,文件名能够是 application-{profile}.properties/yml , 用来指定多个环境版本;

例如:

application-test.properties 表明测试环境配置

application-dev.properties 表明开发环境配置

可是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件

咱们须要经过一个配置来选择须要激活的环境:

#好比在配置文件中指定使用dev环境,咱们能够经过设置不一样的端口号进行测试;
#咱们启动SpringBoot,就能够看到已经切换到dev下的配置了;
spring.profiles.active=dev

4.四、yaml的多文档块

和properties配置文件中同样,可是使用yml去实现不须要建立多个配置文件,更加方便了 !

server:
  port: 8081
#选择要激活那个环境块
spring:
  profiles:
    active: prod

---
server:
  port: 8083
spring:
  profiles: dev #配置环境的名称


---

server:
  port: 8084
spring:
  profiles: prod  #配置环境的名称

注意:若是yml和properties同时都配置了端口,而且没有激活其余环境 , 默认会使用properties配置文件的!

4.五、配置文件加载位置

外部加载配置文件的方式十分多,咱们选择最经常使用的便可,在开发的资源文件中进行配置!

官方外部配置文件说明参考文档

springboot 启动会扫描如下位置的application.properties或者application.yml文件做为Spring boot的默认配置文件:

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件

优先级由高到底,高优先级的配置会覆盖低优先级的配置;

SpringBoot会从这四个位置所有加载主配置文件;互补配置;

咱们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;

#配置项目的访问路径
server.servlet.context-path=/kuang

4.六、拓展,运维小技巧

指定位置加载配置文件

咱们还能够经过spring.config.location来改变默认的配置文件位置

项目打包好之后,咱们能够使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种状况,通常是后期运维作的多,相同配置,外部指定的配置文件优先级最高

五、SpringBoot:自动配置原理

自动配置原理

配置文件到底能写什么?怎么写?

SpringBoot官方文档中有大量的配置,咱们没法所有记住

5.一、分析自动配置原理

咱们以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;

//表示这是一个配置类,和之前编写的配置文件同样,也能够给容器中添加组件;
@Configuration 

//启动指定类的ConfigurationProperties功能;
  //进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
  //并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 

//Spring底层@Conditional注解
  //根据不一样的条件判断,若是知足指定的条件,整个配置类里面的配置就会生效;
  //这里的意思就是判断当前应用是不是web应用,若是是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
  //若是不存在,判断也是成立的
  //即便咱们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
    //他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    //只有一个有参构造器的状况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }
    
    //给容器中添加一个组件,这个组件的某些值须要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }
    //。。。。。。。
}

一句话总结 :根据当前不一样的条件判断,决定这个配置类是否生效!

  • 一但这个配置类生效;这个配置类就会给容器中添加各类组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每个属性又是和配置文件绑定的;
  • 全部在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就能够参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http") 
public class HttpProperties {
    // .....
}

咱们去配置文件里面试试前缀,看提示!

这就是自动装配的原理!

5.二、精髓

一、SpringBoot启动会加载大量的自动配置类

二、咱们看咱们须要的功能有没有在SpringBoot默认写好的自动配置类当中;

三、咱们再来看这个自动配置类中到底配置了哪些组件;(只要咱们要用的组件存在在其中,咱们就不须要再手动配置了)

四、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。咱们只须要在配置文件中指定这些属性的值便可;

xxxxAutoConfigurartion:自动配置类;给容器中添加组件

xxxxProperties:封装配置文件中相关属性;

5.三、了解:@Conditional

了解完自动装配的原理后,咱们来关注一个细节问题,自动配置类必须在必定的条件下才能生效;

@Conditional派生注解(Spring注解版原生的@Conditional做用)

做用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的全部内容才生效;

那么多的自动配置类,必须在必定的条件下才能生效;也就是说,咱们加载了这么多的配置类,但不是全部的都生效了。

咱们怎么知道哪些自动配置类生效?

咱们能够经过启用 debug=true属性;来让控制台打印自动配置报告,这样咱们就能够很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug=true

Positive matches:(自动配置类启用的:正匹配)

Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (没有条件的类)

【演示:查看输出的日志】

掌握吸取理解原理,便可以不变应万变!

六、SpringBoot:自定义starter

咱们分析完毕了源码以及自动装配的过程,咱们能够尝试自定义一个启动器来玩玩!

6.一、说明

启动器模块是一个 空 jar 文件,仅提供辅助性依赖管理,这些依赖可能用于自动装配或者其余类库;

命名归约:

官方命名:

  • 前缀:spring-boot-starter-xxx
  • 好比:spring-boot-starter-web....

自定义命名:

  • xxx-spring-boot-starter
  • 好比:mybatis-spring-boot-starter

6.二、编写启动器

一、在IDEA中新建一个空项目 spring-boot-starter-diy

二、新建一个普通Maven模块:kuang-spring-boot-starter

三、新建一个Springboot模块:kuang-spring-boot-starter-autoconfigure

四、点击apply便可,基本结构

五、在咱们的 starter 中 导入 autoconfigure 的依赖!

<!-- 启动器 -->
<dependencies>
    <!--  引入自动配置模块 -->
    <dependency>
        <groupId>com.kuang</groupId>
        <artifactId>kuang-spring-boot-starter-autoconfigure</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

六、将 autoconfigure 项目下多余的文件都删掉,Pom中只留下一个 starter,这是全部的启动器基本配置!

七、咱们编写一个本身的服务

package com.kuang;

public class HelloService {

    HelloProperties helloProperties;

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    public String sayHello(String name){
        return helloProperties.getPrefix() + name + helloProperties.getSuffix();
    }

}

八、编写HelloProperties 配置类

package com.kuang;

import org.springframework.boot.context.properties.ConfigurationProperties;

// 前缀 kuang.hello
@ConfigurationProperties(prefix = "kuang.hello")
public class HelloProperties {

    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

九、编写咱们的自动配置类并注入bean,测试!

package com.kuang;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnWebApplication //web应用生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    @Autowired
    HelloProperties helloProperties;

    @Bean
    public HelloService helloService(){
        HelloService service = new HelloService();
        service.setHelloProperties(helloProperties);
        return service;
    }

}

十、在resources编写一个本身的 META-INF\spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.kuang.HelloServiceAutoConfiguration

十一、编写完成后,能够安装到maven仓库中!

6.三、新建项目测试咱们本身写的启动器

一、新建一个SpringBoot 项目

二、导入咱们本身写的启动器

<dependency>
    <groupId>com.kuang</groupId>
    <artifactId>kuang-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

三、编写一个 HelloController 进行测试咱们本身的写的接口!

package com.kuang.controller;

@RestController
public class HelloController {

    @Autowired
    HelloService helloService;

    @RequestMapping("/hello")
    public String hello(){
        return helloService.sayHello("zxc");
    }

}

四、编写配置文件 application.properties

kuang.hello.prefix="ppp"
kuang.hello.suffix="sss"

五、启动项目进行测试,结果成功 !

七、SpringBoot整合JDBC

7.一、SpringData简介

对于数据访问层,不管是 SQL(关系型数据库) 仍是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。

Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各类数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。

Sping Data 官网:https://spring.io/projects/spring-data

数据库相关的启动器 :能够参考官方文档:

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

整合JDBC

7.二、建立测试项目测试数据源

一、我去新建一个项目测试:springboot-data-jdbc ; 引入相应的模块!基础模块

二、项目建好以后,发现自动帮咱们导入了以下的启动器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

三、编写yaml配置文件链接数据库;

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

四、配置完这一些东西后,咱们就能够直接去使用了,由于SpringBoot已经默认帮咱们进行了自动配置;去测试类测试一下

@SpringBootTest
class SpringbootDataJdbcApplicationTests {

    //DI注入数据源
    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下默认数据源
        System.out.println(dataSource.getClass());
        //得到链接
        Connection connection =   dataSource.getConnection();
        System.out.println(connection);
        //关闭链接
        connection.close();
    }
}

结果:咱们能够看到他默认给咱们配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 咱们并无手动配置

咱们来全局搜索一下,找到数据源的全部自动配置都在 :DataSourceAutoConfiguration文件:

@Import(
    {Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class}
)
protected static class PooledDataSourceConfiguration {
    protected PooledDataSourceConfiguration() {
    }
}

这里导入的类都在 DataSourceConfiguration 配置类下,能够看出 Spring Boot 2.2.5 默认使用HikariDataSource 数据源,而之前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 做为数据源;

HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等链接池更加优秀;

能够使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的链接池实现的彻底限定名。

关于数据源咱们并不作介绍,有了数据库链接,显然就能够 CRUD 操做数据库了。可是咱们须要先了解一个对象 JdbcTemplate

7.三、JDBCTemplate

一、有了数据源(com.zaxxer.hikari.HikariDataSource),而后能够拿到数据库链接(java.sql.Connection),有了链接,就能够使用原生的 JDBC 语句来操做数据库;

二、即便不使用第三方第数据库操做框架,如 MyBatis等,Spring 自己也对原生的JDBC 作了轻量级的封装,即JdbcTemplate。

三、数据库操做的全部 CRUD 方法都在 JdbcTemplate 中。

四、Spring Boot 不只提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需本身注入便可使用

五、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类

JdbcTemplate主要提供如下几类方法:

  • execute方法:能够用于执行任何SQL语句,通常用于执行DDL语句;
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
  • query方法及queryForXXX方法:用于执行查询相关语句;
  • call方法:用于执行存储过程、函数相关语句。

7.四、测试

编写一个Controller,注入 jdbcTemplate,编写测试方法进行访问测试;

package com.kuang.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/jdbc")
public class JdbcController {

    /**
     * Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
     * JdbcTemplate 中会本身注入数据源,用于简化 JDBC操做
     * 还能避免一些常见的错误,使用起来也不用再本身来关闭数据库链接
     */
    @Autowired
    JdbcTemplate jdbcTemplate;

    //查询employee表中全部数据
    //List 中的1个 Map 对应数据库的 1行数据
    //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
    @GetMapping("/list")
    public List<Map<String, Object>> userList(){
        String sql = "select * from employee";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }
    
    //新增一个用户
    @GetMapping("/add")
    public String addUser(){
        //插入语句,注意时间问题
        String sql = "insert into employee(last_name, email,gender,department,birth)" +
                " values ('狂神说','24736743@qq.com',1,101,'"+ new Date().toLocaleString() +"')";
        jdbcTemplate.update(sql);
        //查询
        return "addOk";
    }

    //修改用户信息
    @GetMapping("/update/{id}")
    public String updateUser(@PathVariable("id") int id){
        //插入语句
        String sql = "update employee set last_name=?,email=? where id="+id;
        //数据
        Object[] objects = new Object[2];
        objects[0] = "秦疆";
        objects[1] = "24736743@sina.com";
        jdbcTemplate.update(sql,objects);
        //查询
        return "updateOk";
    }

    //删除用户
    @GetMapping("/delete/{id}")
    public String delUser(@PathVariable("id") int id){
        //插入语句
        String sql = "delete from employee where id=?";
        jdbcTemplate.update(sql,id);
        //查询
        return "deleteOk";
    }
    
}

测试请求,结果正常;

到此,CURD的基本操做,使用 JDBC 就搞定了。https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#using-boot-starter)

7.五、原理探究 :

org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration 数据源配置类做用 :根据逻辑判断以后,添加数据源;

SpringBoot默认支持如下数据源:

com.zaxxer.hikari.HikariDataSource (Spring Boot 2.0 以上,默认使用此数据源)

org.apache.tomcat.jdbc.pool.DataSource

org.apache.commons.dbcp2.BasicDataSource

能够使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的链接池实现的彻底限定名。默认状况下,它是从类路径自动检测的。

@Configuration
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"}
    )
    static class Generic {
        Generic() {
        }

        @Bean
        public DataSource dataSource(DataSourceProperties properties) {
            return properties.initializeDataSourceBuilder().build();
        }
    }

八、SpringBoot整合Druid

8.一、Druid简介

Java程序很大一部分要操做数据库,为了提升性能操做数据库的时候,又不得不使用数据库链接池。

Druid 是阿里巴巴开源平台上一个数据库链接池实现,结合了 C3P0、DBCP 等 DB 池的优势,同时加入了日志监控。

Druid 能够很好的监控 DB 池链接和 SQL 的执行状况,天生就是针对监控而生的 DB 链接池。

Druid已经在阿里巴巴部署了超过600个应用,通过一年多生产环境大规模部署的严苛考验。

Spring Boot 2.0 以上默认使用 Hikari 数据源,能够说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,咱们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。

Github地址:https://github.com/alibaba/druid/

com.alibaba.druid.pool.DruidDataSource 基本配置参数以下:

配置 缺省值 说明
name 配置这个属性的意义在于,若是存在多个数据源,监控的时候能够经过名字来区分开来。 若是没有配置,将会生成一个名字,格式是:"DataSource-" + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起做用的,强行设置name会出错 详情-点此处
url 链接数据库的url,不一样数据库不同。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 链接数据库的用户名
password 链接数据库的密码。若是你不但愿密码直接写在配置文件中,能够使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter
driverClassName 根据url自动识别 这一项可配可不配,若是不配置druid会根据url自动识别dbType,而后选择相应的driverClassName
initialSize 0 初始化时创建物理链接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大链接池数量
maxIdle 8 已经再也不使用,配置了也没效果
minIdle 最小链接池数量
maxWait 获取链接时最大等待时间,单位毫秒。配置了maxWait以后,缺省启用公平锁,并发效率会有所降低,若是须要能够经过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提高巨大,好比说oracle。在mysql下建议关闭。
maxOpenPreparedStatements -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改成true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,能够把这个数值配置大一些,好比说100
validationQuery 用来检测链接是否有效的sql,要求是一个查询语句。若是validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其做用。
validationQueryTimeout 单位:秒,检测链接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法
testOnBorrow true 申请链接时执行validationQuery检测链接是否有效,作了这个配置会下降性能。
testOnReturn false 归还链接时执行validationQuery检测链接是否有效,作了这个配置会下降性能
testWhileIdle false 建议配置为true,不影响性能,而且保证安全性。申请链接的时候检测,若是空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测链接是否有效。
timeBetweenEvictionRunsMillis 1分钟(1.0.14) 有两个含义: 1) Destroy线程会检测链接的间隔时间,若是链接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理链接 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 再也不使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis 30分钟(1.0.14) 链接保持空闲而不被驱逐的最长时间
connectionInitSqls 物理链接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃链接
filters 属性类型是字符串,经过别名的方式配置扩展插件,经常使用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防护sql注入的filter:wall
proxyFilters 类型是List<com.alibaba.druid.filter.Filter>,若是同时配置了filters和proxyFilters,是组合关系,并不是替换关系

8.二、配置数据源

一、添加上 Druid 数据源依赖。

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>

二、切换数据源;以前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但能够 经过 spring.datasource.type 指定数据源。

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源

三、数据源切换以后,在测试类中注入 DataSource,而后获取到它,输出一看便知是否成功切换;

四、切换成功!既然切换成功,就能够设置数据源链接初始化大小、最大链接数、等待时间、最小链接数 等设置项;能够查看源码

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默认是不注入这些属性值的,须要本身绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防护sql注入
    #若是容许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖便可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

五、导入Log4j 的依赖

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

六、如今须要程序员本身为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而再也不使用 Spring Boot 的自动生成了;咱们须要 本身添加 DruidDataSource 组件到容器中,并绑定属性;

package com.kuang.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DruidConfig {

    /*
       将自定义的 Druid数据源添加到容器中,再也不让 Spring Boot 自动建立
       绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
       @ConfigurationProperties(prefix = "spring.datasource"):做用就是将 全局配置文件中
       前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
     */
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

}

七、去测试类中测试一下;看是否成功!

@SpringBootTest
class SpringbootDataJdbcApplicationTests {

    //DI注入数据源
    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下默认数据源
        System.out.println(dataSource.getClass());
        //得到链接
        Connection connection =   dataSource.getConnection();
        System.out.println(connection);

        DruidDataSource druidDataSource = (DruidDataSource) dataSource;
        System.out.println("druidDataSource 数据源最大链接数:" + druidDataSource.getMaxActive());
        System.out.println("druidDataSource 数据源初始化链接数:" + druidDataSource.getInitialSize());

        //关闭链接
        connection.close();
    }
}

输出结果 :可见配置参数已经生效!

8.三、配置Druid数据源监控

Druid 数据源具备监控的功能,并提供了一个 web 界面方便用户查看,相似安装 路由器 时,人家也提供了一个默认的 web 页面。

因此第一步须要设置 Druid 的后台管理页面,好比 登陆帐号、密码 等;配置后台管理;

//配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,因此使用 Spring Boot 的注册 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
    ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

    // 这些参数能够在 com.alibaba.druid.support.http.StatViewServlet 
    // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
    Map<String, String> initParams = new HashMap<>();
    initParams.put("loginUsername", "admin"); //后台管理界面的登陆帐号
    initParams.put("loginPassword", "123456"); //后台管理界面的登陆密码

    //后台容许谁能够访问
    //initParams.put("allow", "localhost"):表示只有本机能够访问
    //initParams.put("allow", ""):为空或者为null时,表示容许全部访问
    initParams.put("allow", "");
    //deny:Druid 后台拒绝谁访问
    //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问

    //设置初始化参数
    bean.setInitParameters(initParams);
    return bean;
}

配置完毕后,咱们能够选择访问 :http://localhost:8080/druid/login.html

进入以后

配置 Druid web 监控 filter 过滤器

//配置 Druid 监控 之  web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new WebStatFilter());

    //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
    Map<String, String> initParams = new HashMap<>();
    initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
    bean.setInitParameters(initParams);

    //"/*" 表示过滤全部请求
    bean.setUrlPatterns(Arrays.asList("/*"));
    return bean;
}

平时在工做中,按需求进行配置便可,主要用做监

九、SpringBoot 整合mybatis

9.一、导入mybatis所须要的依赖

<!-- 引入 myBatis,这是 MyBatis官方提供的适配 Spring Boot 的,而不是Spring Boot本身的-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

9.二、配置数据库链接信息

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

9.三、咱们这里就是用默认的数据源了;先去测试一下链接是否成功!

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootDemoMybatisApplicationTests {

    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {

        System.out.println("数据源>>>>>>" + dataSource.getClass());
        Connection connection = dataSource.getConnection();
        System.out.println("链接>>>>>>>>>" + connection);
        System.out.println("链接地址>>>>>" + connection.getMetaData().getURL());
        connection.close();

    }

}

查看输出结果,数据库配置OK!

9.四、建立实体类

package com.kuang.mybatis.pojo;

public class User {

    private int id;
    private String name;
    private String pwd;

    public User() {
    }

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }

}

9.五、配置Mapper接口类

package com.kuang.mybatis.pojo.mapper;

import com.kuang.mybatis.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

//@Mapper : 表示本类是一个 MyBatis 的 Mapper,等价于之前 Spring 整合 MyBatis 时的 Mapper 接口
@Mapper
@Repository
public interface UserMapper {

    //选择所有用户
    List<User> selectUser();
    //根据id选择用户
    User selectUserById(int id);
    //添加一个用户
    int addUser(User user);
    //修改一个用户
    int updateUser(User user);
    //根据id删除用户
    int deleteUser(int id);

}

9.六、对应Mapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kuang.mybatis.pojo.mapper.UserMapper">

    <select id="selectUser" resultType="User">
    select * from user
  </select>

    <select id="selectUserById" resultType="User">
    select * from user where id = #{id}
</select>

    <insert id="addUser" parameterType="User">
    insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>

    <update id="updateUser" parameterType="User">
    update user set name=#{name},pwd=#{pwd} where id = #{id}
</update>

    <delete id="deleteUser" parameterType="int">
    delete from user where id = #{id}
</delete>
</mapper>

9.七、maven配置资源过滤问题

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
</resources>

9.八、SpringBoot 整合!

之前 MyBatis 未与 spring 整合时,配置数据源、事务、链接数据库的帐号、密码等都是在 myBatis 核心配置文件中进行的myBatis 与 spring 整合后,配置数据源、事务、链接数据库的帐号、密码等就交由 spring 管理。所以,在这里咱们即便不使用mybatis配置文件也彻底ok!
既然已经提供了 myBatis 的映射配置文件,天然要告诉 spring boot 这些文件的位置

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#指定myBatis的核心配置文件与Mapper映射文件
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
# 注意:对应实体类的路径
mybatis.type-aliases-package=com.kuang.mybatis.pojo

已经说过 spring boot 官方并无提供 myBaits 的启动器,是 myBatis 官方提供的开发包来适配的 spring boot,从 pom.xml 文件中的依赖包名也能看出来,并不是是以 spring-boot 开头的;

同理上面全局配置文件中的这两行配置也是以 mybatis 开头 而非 spring 开头也充分说明这些都是 myBatis 官方提供的

能够从 org.mybatis.spring.boot.autoconfigure.MybatisProperties 中查看全部配置项

@ConfigurationProperties(
    prefix = "mybatis"
)
public class MybatisProperties {
    public static final String MYBATIS_PREFIX = "mybatis";
    private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
    private String configLocation;
    private String[] mapperLocations;
    private String typeAliasesPackage;
    private Class<?> typeAliasesSuperType;
    private String typeHandlersPackage;
    private boolean checkConfigLocation = false;
    private ExecutorType executorType;
    private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
    private Properties configurationProperties;
    @NestedConfigurationProperty
    private Configuration configuration;

也能够直接去查看 官方文档

9.九、编写controller

package com.kuang.mybatis.controller;

import com.kuang.mybatis.pojo.User;
import com.kuang.mybatis.pojo.mapper.UserMapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    @Autowired
    private UserMapper userMapper;

    //选择所有用户
    @GetMapping("/selectUser")
    public String selectUser(){
        List<User> users = userMapper.selectUser();
        for (User user : users) {
            System.out.println(user);
        }

        return "ok";
    }
    //根据id选择用户
    @GetMapping("/selectUserById")
    public String selectUserById(){
        User user = userMapper.selectUserById(1);
        System.out.println(user);
        return "ok";
    }
    //添加一个用户
    @GetMapping("/addUser")
    public String addUser(){
        userMapper.addUser(new User(5,"阿毛","456789"));
        return "ok";
    }
    //修改一个用户
    @GetMapping("/updateUser")
    public String updateUser(){
        userMapper.updateUser(new User(5,"阿毛","421319"));
        return "ok";
    }
    //根据id删除用户
    @GetMapping("/deleteUser")
    public String deleteUser(){
        userMapper.deleteUser(5);
        return "ok";
    }

}

9.十、启动项目访问进行测试!

步骤:

Mybatis整合包

mybatis-spring-boot-starter

1.导入包

2.配置文件

3.mybatis配置

4.编写sql

5.service层调用dao层

6.controller调用service层

注:配置数据库链接信息(不变)

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默认是不注入这些属性值的,须要本身绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防护sql注入
    #若是容许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖便可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

十、SpringBoot:Web开发静态资源处理

Web开发探究

10.一、简介

好的,同窗们,那么接下来呢,咱们开始学习SpringBoot与Web开发,从这一章日后,就属于咱们实战部分的内容了;

其实SpringBoot的东西用起来很是简单,由于SpringBoot最大的特色就是自动装配。

使用SpringBoot的步骤:

一、建立一个SpringBoot应用,选择咱们须要的模块,SpringBoot就会默认将咱们的须要的模块自动配置好

二、手动在配置文件中配置部分配置项目就能够运行起来了

三、专一编写业务代码,不须要考虑之前那样一大堆的配置了。

要熟悉掌握开发,以前学习的自动配置的原理必定要搞明白!

好比SpringBoot到底帮咱们配置了什么?咱们能不能修改?咱们能修改哪些配置?咱们能不能扩展?

  • 向容器中自动配置组件 :*** Autoconfiguration
  • 自动配置类,封装配置文件的内容:***Properties

没事就找找类,看看自动装配原理!

咱们以后来进行一个单体项目的小项目测试,让你们可以快速上手开发!

静态资源处理

10.二、静态资源映射规则

首先,咱们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!

写请求很是简单,那咱们要引入咱们前端资源,咱们项目中有许多的静态资源,好比css,js等文件,这个SpringBoot怎么处理呢?

若是咱们是一个web应用,咱们的main下会有一个webapp,咱们之前都是将全部的页面导在这里面的,对吧!可是咱们如今的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给咱们写页面呢?固然是能够的,可是SpringBoot对于静态资源放置的位置,是有规定的!

咱们先来聊聊这个静态资源映射规则:

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;

咱们能够去看看 WebMvcAutoConfigurationAdapter 中有不少配置方法;

有一个方法:addResourceHandlers 添加资源处理

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        // 已禁用默认资源处理
        logger.debug("Default resource handling disabled");
        return;
    }
    // 缓存控制
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    // webjars 配置
    if (!registry.hasMappingForPattern("/webjars/**")) {
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                                             .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
    // 静态资源配置
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                             .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}

读一下源代码:好比全部的 /webjars/** , 都须要去 classpath:/META-INF/resources/webjars/ 找对应的资源;

10.三、什么是webjars 呢?

Webjars本质就是以jar包的方式引入咱们的静态资源 , 咱们之前要导入一个静态资源文件,直接导入便可。

使用SpringBoot须要使用Webjars,咱们能够去搜索一下:

网站:https://www.webjars.org

要使用jQuery,咱们只要要引入jQuery对应版本的pom依赖便可!

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

导入完毕,查看webjars目录结构,并访问Jquery.js文件!

访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,咱们这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

10.四、第二种静态资源映射规则

那咱们项目中要是使用本身的静态资源该怎么导入呢?咱们看下一行代码;

咱们去找staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,咱们能够点进去看一下分析:

// 进入方法
public String[] getStaticLocations() {
    return this.staticLocations;
}
// 找到对应的值
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
// 找到路径
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { 
    "classpath:/META-INF/resources/",
  "classpath:/resources/", 
    "classpath:/static/", 
    "classpath:/public/" 
};

ResourceProperties 能够设置和咱们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。

因此得出结论,如下四个目录存放的静态资源能够被咱们识别:

"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"

咱们能够在resources根目录下新建对应的文件夹,均可以存放咱们的静态文件;

好比咱们访问 http://localhost:8080/1.js , 他就会去这些文件夹中寻找对应的静态资源文件;

10.五、自定义静态资源路径

咱们也能够本身经过配置文件来指定一下,哪些文件夹是须要咱们放静态资源文件的,在application.properties中配置;

spring.resources.static-locations=classpath:/coding/,classpath:/kuang/

一旦本身定义了静态文件夹的路径,原来的自动配置就都会失效了!

首页处理

静态资源文件夹说完后,咱们继续向下看源码!能够看到一个欢迎页的映射,就是咱们的首页!

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService,ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
        new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), // getWelcomePage 得到欢迎页
        this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    return welcomePageHandlerMapping;
}

点进去继续看

private Optional<Resource> getWelcomePage() {
    String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
    // ::是java8 中新引入的运算符
    // Class::function的时候function是属于Class的,应该是静态方法。
    // this::function的funtion是属于这个对象的。
    // 简而言之,就是一种语法糖而已,是一种简写
    return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
// 欢迎页就是一个location下的的 index.html 而已
private Resource getIndexHtml(String location) {
    return this.resourceLoader.getResource(location + "index.html");
}

欢迎页,静态资源文件夹下的全部 index.html 页面;被 /** 映射。

好比我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html

新建一个 index.html ,在咱们上面的3个目录中任意一个;而后访问测试 http://localhost:8080/ 看结果!

关于网站图标说明

与其余静态资源同样,Spring Boot在配置的静态内容位置中查找 favicon.ico。若是存在这样的文件,它将自动用做应用程序的favicon。

一、关闭SpringBoot默认图标

#关闭默认图标spring.mvc.favicon.enabled=false

二、本身放一个图标在静态资源目录下,我放在 public 目录下

三、清除浏览器缓存!刷新网页,发现图标已经变成本身的了!

十一、SpringBoot:Thymeleaf模板引擎

11.一、模板引擎

前端交给咱们的页面,是html页面。若是是咱们之前开发,咱们须要把他们转成jsp页面,jsp好处就是当咱们查出一些数据转发到JSP页面之后,咱们能够用jsp轻松实现数据的显示,及交互等。

jsp支持很是强大的功能,包括能写Java代码,可是呢,咱们如今的这种状况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,咱们用的仍是嵌入式的Tomcat,因此呢,他如今默认是不支持jsp的

那不支持jsp,若是咱们直接用纯静态页面的方式,那给咱们开发会带来很是大的麻烦,那怎么办呢?

SpringBoot推荐你能够来使用模板引擎:

模板引擎,咱们其实你们听到不少,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给咱们推荐的Thymeleaf,模板引擎有很是多,但再多的模板引擎,他们的思想都是同样的,什么样一个思想呢咱们来看一下这张图:

模板引擎的做用就是咱们来写一个页面模板,好比有些值呢,是动态的,咱们写一些表达式。而这些值,从哪来呢,就是咱们在后台封装一些数据。而后把这个模板和这个数据交给咱们模板引擎,模板引擎按照咱们这个数据帮你把这表达式解析、填充到咱们指定的位置,而后把这个数据最终生成一个咱们想要的内容给咱们写出去,这就是咱们这个模板引擎,无论是jsp仍是其余模板引擎,都是这个思想。只不过呢,就是说不一样模板引擎之间,他们可能这个语法有点不同。其余的我就不介绍了,我主要来介绍一下SpringBoot给咱们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。并且呢,功能更强大。

咱们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,咱们来看SpringBoot里边怎么用。

11.二、引入Thymeleaf

怎么引入呢,对于springboot来讲,什么事情不都是一个start的事情嘛,咱们去在项目中引入一下。给你们三个网址:

Thymeleaf 官网:https://www.thymeleaf.org/

Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf

Spring官方文档:找到咱们对应的版本

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

找到对应的pom依赖:能够适当点进源码看下原本的包!

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

Maven会自动下载jar包,咱们能够去看下下载的东西;

11.三、Thymeleaf分析

前面呢,咱们已经引入了Thymeleaf,那这个要怎么使用呢?

咱们首先得按照SpringBoot的自动配置原理看一下咱们这个Thymeleaf的自动配置规则,在按照那个规则,咱们进行使用。

咱们去找一下Thymeleaf的自动配置类:ThymeleafPropert

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
}

咱们能够在其中看到默认的前缀和后缀!

咱们只须要把咱们的html页面放在类路径下的templates下,thymeleaf就能够帮咱们自动渲染了。

使用thymeleaf什么都不须要配置,只须要将他放在指定的文件夹下便可!

测试

一、编写一个TestController

@Controller
public class TestController {
    
    @RequestMapping("/t1")
    public String test1(){
        //classpath:/templates/test.html
        return "test";
    }
    
}

二、编写一个测试页面 test.html 放在 templates 目录下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>测试页面</h1>

</body>
</html>

三、启动项目请求测试

11.四、Thymeleaf 语法学习

要学习语法,仍是参考官网文档最为准确,咱们找到对应的版本看一下;

Thymeleaf 官网:https://www.thymeleaf.org/ , 简单看一下官网!咱们去下载Thymeleaf的官方文档!

咱们作个最简单的练习 :咱们须要查出一些数据,在页面中展现

一、修改测试请求,增长数据传输;

@RequestMapping("/t1")
public String test1(Model model){
    //存入数据
    model.addAttribute("msg","Hello,Thymeleaf");
    //classpath:/templates/test.html
    return "test";
}

二、咱们要使用thymeleaf,须要在html文件中导入命名空间的约束,方便提示。

咱们能够去官方文档的#3中看一下命名空间拿来过来:

xmlns:th="http://www.thymeleaf.org"

三、咱们去编写下前端页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>狂神说</title>
</head>
<body>
<h1>测试页面</h1>

<!--th:text就是将div中的内容设置为它指定的值,和以前学习的Vue同样-->
<div th:text="${msg}"></div>
</body>
</html>

四、启动测试!

OK,入门搞定,咱们来认真研习一下Thymeleaf的使用语法!

一、咱们能够使用任意的 th:attr 来替换Html中原生属性的值!

二、咱们能写哪些表达式呢?

Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL;
    1)、获取对象的属性、调用方法
    2)、使用内置的基本对象:#18
         #ctx : the context object.
         #vars: the context variables.
         #locale : the context locale.
         #request : (only in Web Contexts) the HttpServletRequest object.
         #response : (only in Web Contexts) the HttpServletResponse object.
         #session : (only in Web Contexts) the HttpSession object.
         #servletContext : (only in Web Contexts) the ServletContext object.

    3)、内置的一些工具对象:
      #execInfo : information about the template being processed.
      #uris : methods for escaping parts of URLs/URIs
      #conversions : methods for executing the configured conversion service (if any).
      #dates : methods for java.util.Date objects: formatting, component extraction, etc.
      #calendars : analogous to #dates , but for java.util.Calendar objects.
      #numbers : methods for formatting numeric objects.
      #strings : methods for String objects: contains, startsWith, prepending/appending, etc.
      #objects : methods for objects in general.
      #bools : methods for boolean evaluation.
      #arrays : methods for arrays.
      #lists : methods for lists.
      #sets : methods for sets.
      #maps : methods for maps.
      #aggregates : methods for creating aggregates on arrays or collections.
==================================================================================

  Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是同样;
  Message Expressions: #{...}:获取国际化内容
  Link URL Expressions: @{...}:定义URL;
  Fragment Expressions: ~{...}:片断引用表达式

Literals(字面量)
      Text literals: 'one text' , 'Another one!' ,…
      Number literals: 0 , 34 , 3.0 , 12.3 ,…
      Boolean literals: true , false
      Null literal: null
      Literal tokens: one , sometext , main ,…
      
Text operations:(文本操做)
    String concatenation: +
    Literal substitutions: |The name is ${name}|
    
Arithmetic operations:(数学运算)
    Binary operators: + , - , * , / , %
    Minus sign (unary operator): -
    
Boolean operations:(布尔运算)
    Binary operators: and , or
    Boolean negation (unary operator): ! , not
    
Comparisons and equality:(比较运算)
    Comparators: > , < , >= , <= ( gt , lt , ge , le )
    Equality operators: == , != ( eq , ne )
    
Conditional operators:条件运算(三元运算符)
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else)
    Default: (value) ?: (defaultvalue)
    
Special tokens:
    No-Operation: _

练习测试:

一、 咱们编写一个Controller,放一些数据

@RequestMapping("/t2")
public String test2(Map<String,Object> map){
    //存入数据
    map.put("msg","<h1>Hello</h1>");
    map.put("users", Arrays.asList("qinjiang","kuangshen"));
    //classpath:/templates/test.html
    return "test";
}

二、测试页面取出数据

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>狂神说</title>
</head>
<body>
<h1>测试页面</h1>

<div th:text="${msg}"></div>
<!--不转义-->
<div th:utext="${msg}"></div>

<!--遍历数据-->
<!--th:each每次遍历都会生成当前这个标签:官网#9-->
<h4 th:each="user :${users}" th:text="${user}"></h4>

<h4>
    <!--行内写法:官网#12-->
    <span th:each="user:${users}">[[${user}]]</span>
</h4>

</body>
</html>

三、启动项目测试!

咱们看完语法,不少样式,咱们即便如今学习了,也会忘记,因此咱们在学习过程当中,须要使用什么,根据官方文档来查询,才是最重要的,要熟练使用官方文档!

十二、SpringBoot:MVC自动配置原理

12.一、官网阅读

在进行项目编写前,咱们还须要知道一个东西,就是SpringBoot对咱们的SpringMVC还作了哪些配置,包括如何扩展,如何定制。

只有把这些都搞清楚了,咱们在以后使用才会更加驾轻就熟。途径一:源码分析,途径二:官方文档!

地址 :https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration

Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它能够很好地与大多数应用程序一块儿工做。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了如下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars 
// 自动注册了Converter:
// 转换器,这就是咱们网页提交数据到后台自动封装成为对象的东西,好比把"1"字符串自动转换为int类型
// Formatter:【格式化器,好比页面给咱们了一个2019-8-10,它会给咱们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的的,好比咱们要把一个User对象转换为JSON字符串,能够去看官网文档解释;
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:帮咱们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

/*
若是您但愿保留Spring Boot MVC功能,而且但愿添加其余MVC配置(拦截器、格式化程序、视图控制器和其余功能),则能够添加本身
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。若是但愿提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则能够声明WebMVCregistrationAdapter实例来提供此类组件。
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration 
(interceptors, formatters, view controllers, and other features), you can add your own 
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide 
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or 
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

// 若是您想彻底控制Spring MVC,能够添加本身的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

咱们来仔细对照,看一下它怎么实现的,它告诉咱们SpringBoot已经帮咱们自动配置好了SpringMVC,而后自动配置了哪些东西呢?

12.二、ContentNegotiatingViewResolver 内容协商视图解析器

自动配置了ViewResolver,就是咱们以前学习的SpringMVC的视图解析器;

即根据方法的返回值取得视图对象(View),而后由视图对象决定如何渲染(转发,重定向)。

咱们去看看这里的源码:咱们找到 WebMvcAutoConfiguration , 而后搜索ContentNegotiatingViewResolver。找到以下方法!

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
    // ContentNegotiatingViewResolver使用全部其余视图解析器来定位视图,所以它应该具备较高的优先级
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}

咱们能够点进这类看看!找到对应的解析视图的代码;

@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
    if (requestedMediaTypes != null) {
        // 获取候选的视图对象
        List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
        // 选择一个最适合的视图对象,而后把这个对象返回
        View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
        if (bestView != null) {
            return bestView;
        }
    }
    // .....
}

咱们继续点进去看,他是怎么得到候选的视图的呢?

getCandidateViews中看到他是把全部的视图解析器拿来,进行while循环,挨个解析!

Iterator var5 = this.viewResolvers.iterator();

因此得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合全部的视图解析器的

咱们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的!

protected void initServletContext(ServletContext servletContext) {
    // 这里它是从beanFactory工具中获取容器中的全部视图解析器
    // ViewRescolver.class 把全部的视图解析器来组合的
    Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
    ViewResolver viewResolver;
    if (this.viewResolvers == null) {
        this.viewResolvers = new ArrayList(matchingBeans.size());
    }
    // ...............
}

既然它是在容器中去找视图解析器,咱们是否能够猜测,咱们就能够去实现一个视图解析器了呢?

咱们能够本身给容器中去添加一个视图解析器;这个类就会帮咱们自动的将它组合进来;咱们去实现一下

一、咱们在咱们的主程序中去写一个视图解析器来试试;

@Bean //放到bean中
public ViewResolver myViewResolver(){
    return new MyViewResolver();
}

//咱们写一个静态内部类,视图解析器就须要实现ViewResolver接口
private static class MyViewResolver implements ViewResolver{
    @Override
    public View resolveViewName(String s, Locale locale) throws Exception {
        return null;
    }
}

二、怎么看咱们本身写的视图解析器有没有起做用呢?

咱们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,由于全部的请求都会走到这个方法中

三、咱们启动咱们的项目,而后随便访问一个页面,看一下Debug信息;

找到this

找到视图解析器,咱们看到咱们本身定义的就在这里了;

因此说,咱们若是想要使用本身定制化的东西,咱们只须要给容器中添加这个组件就行了!剩下的事情SpringBoot就会帮咱们作了!

12.三、转换器和格式化器

找到格式化转换器:

@Bean
@Override
public FormattingConversionService mvcConversionService() {
    // 拿到配置文件中的格式化规则
    WebConversionService conversionService = 
        new WebConversionService(this.mvcProperties.getDateFormat());
    addFormatters(conversionService);
    return conversionService;
}

点击去:

public String getDateFormat() {
    return this.dateFormat;
}

/**
* Date format to use. For instance, `dd/MM/yyyy`. 默认的
 */
private String dateFormat;

能够看到在咱们的Properties文件中,咱们能够进行自动配置它!

若是配置了本身的格式化方式,就会注册到Bean中生效,咱们能够在配置文件中配置日期格式化的规则:

其他的就不一一举例了,你们能够下去多研究探讨便可!

12.四、修改SpringBoot的默认配置

这么多的自动配置,原理都是同样的,经过这个WebMVC的自动配置原理分析,咱们要学会一种学习方式,经过源码探究,得出结论;这个结论必定是属于本身的,并且一通百通。

SpringBoot的底层,大量用到了这些设计细节思想,因此,没事须要多阅读源码!得出结论;

SpringBoot在自动配置不少组件的时候,先看容器中有没有用户本身配置的(若是用户本身配置@bean),若是有就用用户配置的,若是没有就用自动配置的;

若是有些组件能够存在多个,好比咱们的视图解析器,就将用户配置的和本身默认的组合起来!

扩展使用SpringMVC 官方文档以下:

If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

咱们要作的就是编写一个@Configuration注解类,而且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;咱们去本身写一个;咱们新建一个包叫config,写一个类MyMvcConfig;

//应为类型要求为WebMvcConfigurer,因此咱们实现其接口
//能够使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 浏览器发送/test , 就会跳转到test页面;
        registry.addViewController("/test").setViewName("test");
    }
}

咱们去浏览器访问一下:

确实也跳转过来了!因此说,咱们要扩展SpringMVC,官方就推荐咱们这么去使用,既保SpringBoot留全部的自动配置,也能用咱们扩展的配置!

咱们能够去分析一下原理:

一、WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter

二、这个类上有一个注解,在作其余自动配置时会导入:@Import(EnableWebMvcConfiguration.class)

三、咱们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration

这个父类中有这样一段代码:

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
    
  // 从容器中获取全部的webmvcConfigurer
    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }
}

四、咱们能够在这个类中去寻找一个咱们刚才设置的viewController当作参考,发现它调用了一个

protected void addViewControllers(ViewControllerRegistry registry) {
    this.configurers.addViewControllers(registry);
}

五、咱们点进去看一下

public void addViewControllers(ViewControllerRegistry registry) {
    Iterator var2 = this.delegates.iterator();

    while(var2.hasNext()) {
        // 将全部的WebMvcConfigurer相关配置来一块儿调用!包括咱们本身配置的和Spring给咱们配置的
        WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
        delegate.addViewControllers(registry);
    }

}

因此得出结论:全部的WebMvcConfiguration都会被做用,不止Spring本身的配置类,咱们本身的配置类固然也会被调用;

12.五、全面接管SpringMVC

官方文档:

If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.

全面接管即:SpringBoot对SpringMVC的自动配置不须要了,全部都是咱们本身去配置!

只需在咱们的配置类中要加一个@EnableWebMvc。

咱们看下若是咱们全面接管了SpringMVC了,咱们以前SpringBoot给咱们配置的静态资源映射必定会无效,咱们能够去测试一下;

不加注解以前,访问首页:

给配置类加上注解:@EnableWebMvc

咱们发现全部的SpringMVC自动配置都失效了!回归到了最初的样子;

固然,咱们开发中,不推荐使用全面接管SpringMVC

思考问题?为何加了一个注解,自动配置就失效了!咱们看下源码:

一、这里发现它是导入了一个类,咱们能够继续进去看

@Import({DelegatingWebMvcConfiguration.class})public @interface EnableWebMvc {}

二、它继承了一个父类 WebMvcConfigurationSupport

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {  // ......}

三、咱们来回顾一下Webmvc自动配置类

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    
}

总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;

而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!

在SpringBoot中会有很是多的扩展配置,只要看见了这个,咱们就应该多留心注意~

1三、SpringBoot:页面国际化

有的时候,咱们的网站会去涉及中英文甚至多语言的切换,这时候咱们就须要学习国际化了!

13.一、准备工做

先在IDEA中统一设置properties的编码问题!

编写国际化配置文件,抽取页面须要显示的国际化页面消息。咱们能够去登陆页面查看一下,哪些内容咱们须要编写国际化的配置!

13.二、配置文件编写

一、咱们在resources资源文件下新建一个i18n目录,存放国际化配置文件

二、创建一个login.properties文件,还有一个login_zh_CN.properties;发现IDEA自动识别了咱们要作国际化操做;文件夹变了!

三、咱们能够在这上面去新建一个文件;

弹出以下页面:咱们再添加一个英文的;

这样就快捷多了!

四、接下来,咱们就来编写配置,咱们能够看到idea下面有另一个视图;

这个视图咱们点击 + 号就能够直接添加属性了;咱们新建一个login.tip,能够看到边上有三个文件框能够输入

咱们添加一下首页的内容!

而后依次添加其余页面内容便可!

而后去查看咱们的配置文件;

login.properties :默认

login.btn=登陆
login.password=密码
login.remember=记住我
login.tip=请登陆
login.username=用户名

英文:

login.btn=Sign in
login.password=Password
login.remember=Remember me
login.tip=Please sign in
login.username=Username

中文:

login.btn=登陆
login.password=密码
login.remember=记住我
login.tip=请登陆
login.username=用户名

OK,配置文件步骤搞定!

13.三、配置文件生效探究

咱们去看一下SpringBoot对国际化的自动配置!这里又涉及到一个类:MessageSourceAutoConfiguration

里面有一个方法,这里发现SpringBoot已经自动配置好了管理咱们国际化资源文件的组件 ResourceBundleMessageSource;

// 获取 properties 传递过来的值进行判断
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    if (StringUtils.hasText(properties.getBasename())) {
        // 设置国际化文件的基础名(去掉语言国家代码的)
        messageSource.setBasenames(
            StringUtils.commaDelimitedListToStringArray(
                                       StringUtils.trimAllWhitespace(properties.getBasename())));
    }
    if (properties.getEncoding() != null) {
        messageSource.setDefaultEncoding(properties.getEncoding().name());
    }
    messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
    Duration cacheDuration = properties.getCacheDuration();
    if (cacheDuration != null) {
        messageSource.setCacheMillis(cacheDuration.toMillis());
    }
    messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
    messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
    return messageSource;
}

咱们真实 的状况是放在了i18n目录下,因此咱们要去配置这个messages的路径;

spring.messages.basename=i18n.login

13.四、配置页面国际化值

去页面获取国际化的值,查看Thymeleaf的文档,找到message取值操做为:#{...}。咱们去页面测试下:

IDEA还有提示,很是智能的!

咱们能够去启动项目,访问一下,发现已经自动识别为中文的了!

可是咱们想要更好!能够根据按钮自动切换中文英文!

13.五、配置国际化解析

在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫作LocaleResolver (获取区域信息对象)的解析器!

咱们去咱们webmvc自动配置文件,寻找一下!看到SpringBoot默认配置:

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
    // 容器中没有就本身配,有的话就用用户配置的
    if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
        return new FixedLocaleResolver(this.mvcProperties.getLocale());
    }
    // 接收头国际化分解
    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
    localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
    return localeResolver;
}

AcceptHeaderLocaleResolver 这个类中有一个方法

public Locale resolveLocale(HttpServletRequest request) {
    Locale defaultLocale = this.getDefaultLocale();
    // 默认的就是根据请求头带来的区域信息获取Locale进行国际化
    if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
        return defaultLocale;
    } else {
        Locale requestLocale = request.getLocale();
        List<Locale> supportedLocales = this.getSupportedLocales();
        if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
            Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
            if (supportedLocale != null) {
                return supportedLocale;
            } else {
                return defaultLocale != null ? defaultLocale : requestLocale;
            }
        } else {
            return requestLocale;
        }
    }
}

那假如咱们如今想点击连接让咱们的国际化资源生效,就须要让咱们本身的Locale生效!

咱们去本身写一个本身的LocaleResolver,能够在连接上携带区域信息!

修改一下前端页面的跳转链接:

<!-- 这里传入参数不须要使用 ?使用 (key=value)-->
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>

咱们去写一个处理的组件类!

package com.kuang.component;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

//能够在连接上携带区域信息
public class MyLocaleResolver implements LocaleResolver {

    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {

        String language = request.getParameter("l");
        Locale locale = Locale.getDefault(); // 若是没有获取到就使用系统默认的
        //若是请求连接不为空
        if (!StringUtils.isEmpty(language)){
            //分割请求参数
            String[] split = language.split("_");
            //国家,地区
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

    }
}

为了让咱们的区域化信息可以生效,咱们须要再配置一下这个组件!在咱们本身的MvcConofig下添加bean;

@Beanpublic LocaleResolver localeResolver(){    return new MyLocaleResolver();}

咱们重启项目,来访问一下,发现点击按钮能够实现成功切换!搞定收工!

1四、SpringBoot:集成Swagger终极版

学习目标:

  • 了解Swagger的概念及做用
  • 掌握在项目中集成Swagger自动生成API文档

14.一、Swagger简介

先后端分离

  • 前端 -> 前端控制层、视图层
  • 后端 -> 后端控制层、服务层、数据访问层
  • 先后端经过API进行交互
  • 先后端相对独立且松耦合

产生的问题

  • 先后端集成,前端或者后端没法作到“及时协商,尽早解决”,最终致使问题集中爆发

解决方案

  • 首先定义schema [ 计划的提纲 ],并实时跟踪最新的API,下降集成风险

Swagger

  • 号称世界上最流行的API框架
  • Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
  • 直接运行,在线测试API
  • 支持多种语言 (如:Java,PHP等)
  • 官网:https://swagger.io/

14.二、SpringBoot集成Swagger

SpringBoot集成Swagger => springfox,两个jar包

  • Springfox-swagger2
  • swagger-springmvc

使用Swagger

要求:jdk 1.8 + 不然swagger2没法运行

步骤:

一、新建一个SpringBoot-web项目

二、添加Maven依赖

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.9.2</version>
</dependency>

三、编写HelloController,测试确保运行成功!

四、要使用Swagger,咱们须要编写一个配置类-SwaggerConfig来配置 Swagger

@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {  
}j

五、访问测试 :http://localhost:8080/swagger-ui.html ,能够看到swagger的界面;

14.三、配置Swagger

一、Swagger实例Bean是Docket,因此经过配置Docket实例来配置Swaggger。

@Bean //配置docket以配置Swagger具体参数
public Docket docket() {
   return new Docket(DocumentationType.SWAGGER_2);
}

二、能够经过apiInfo()属性配置文档信息

//配置文档信息
private ApiInfo apiInfo() {
   Contact contact = new Contact("联系人名字", "http://xxx.xxx.com/联系人访问连接", "联系人邮箱");
   return new ApiInfo(
           "Swagger学习", // 标题
           "学习演示如何配置Swagger", // 描述
           "v1.0", // 版本
           "http://terms.service.url/组织连接", // 组织连接
           contact, // 联系人信息
           "Apach 2.0 许可", // 许可
           "许可连接", // 许可链接
           new ArrayList<>()// 扩展
  );
}

三、Docket 实例关联上 apiInfo()

@Bean
public Docket docket() {
   return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}

四、重启项目,访问测试 http://localhost:8080/swagger-ui.html 看下效果;

14.四、配置扫描接口

一、构建Docket时经过select()方法配置怎么扫描接口。

any() // 扫描全部,项目中的全部接口都会被扫描到
none() // 不扫描接口
// 经过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
withMethodAnnotation(final Class<? extends Annotation> annotation)
// 经过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
withClassAnnotation(final Class<? extends Annotation> annotation)
basePackage(final String basePackage) // 根据包路径扫描接口

二、重启项目测试,因为咱们配置根据包的路径扫描接口,因此咱们只能看到一个类

三、除了经过包路径配置扫描接口外,还能够经过配置其余方式扫描接口,这里注释一下全部的配置方式:

any() // 扫描全部,项目中的全部接口都会被扫描到
none() // 不扫描接口
// 经过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
withMethodAnnotation(final Class<? extends Annotation> annotation)
// 经过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
withClassAnnotation(final Class<? extends Annotation> annotation)
basePackage(final String basePackage) // 根据包路径扫描接口

四、除此以外,咱们还能够配置接口扫描过滤:

@Bean
public Docket docket() {
   return new Docket(DocumentationType.SWAGGER_2)
      .apiInfo(apiInfo())
      .select()// 经过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
      .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
       // 配置如何经过path过滤,即这里只扫描请求以/kuang开头的接口
      .paths(PathSelectors.ant("/kuang/**"))
      .build();
}

五、这里的可选值还有

any() // 任何请求都扫描
none() // 任何请求都不扫描
regex(final String pathRegex) // 经过正则表达式控制
ant(final String antPattern) // 经过ant()控制

14.五、配置Swagger开关

一、经过enable()方法配置是否启用swagger,若是是false,swagger将不能在浏览器中访问了

@Bean
public Docket docket() {
   return new Docket(DocumentationType.SWAGGER_2)
      .apiInfo(apiInfo())
      .enable(false) //配置是否启用Swagger,若是是false,在浏览器将没法访问
      .select()// 经过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
      .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
       // 配置如何经过path过滤,即这里只扫描请求以/kuang开头的接口
      .paths(PathSelectors.ant("/kuang/**"))
      .build();
}

二、如何动态配置当项目处于test、dev环境时显示swagger,处于prod时不显示?

@Bean
public Docket docket(Environment environment) {
   // 设置要显示swagger的环境
   Profiles of = Profiles.of("dev", "test");
   // 判断当前是否处于该环境
   // 经过 enable() 接收此参数判断是否要显示
   boolean b = environment.acceptsProfiles(of);
   
   return new Docket(DocumentationType.SWAGGER_2)
      .apiInfo(apiInfo())
      .enable(b) //配置是否启用Swagger,若是是false,在浏览器将没法访问
      .select()// 经过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口
      .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller"))
       // 配置如何经过path过滤,即这里只扫描请求以/kuang开头的接口
      .paths(PathSelectors.ant("/kuang/**"))
      .build();
}

三、能够在项目中增长一个dev的配置文件查看效果!

14.六、配置API分组

一、若是没有配置分组,默认是default。经过groupName()方法便可配置分组:

@Bean
public Docket docket(Environment environment) {
   return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
      .groupName("hello") // 配置分组
       // 省略配置....
}

二、重启项目查看分组

三、如何配置多个分组?配置多个分组只须要配置多个docket便可:

@Bean
public Docket docket1(){
   return new Docket(DocumentationType.SWAGGER_2).groupName("group1");
}
@Bean
public Docket docket2(){
   return new Docket(DocumentationType.SWAGGER_2).groupName("group2");
}
@Bean
public Docket docket3(){
   return new Docket(DocumentationType.SWAGGER_2).groupName("group3");
}

四、重启项目查看便可

14.七、实体配置

一、新建一个实体类

@ApiModel("用户实体")
public class User {
   @ApiModelProperty("用户名")
   public String username;
   @ApiModelProperty("密码")
   public String password;
}

二、只要这个实体在请求接口的返回值上(即便是泛型),都能映射到实体项中:

@RequestMapping("/getUser")
public User getUser(){   
	return new User();
}

三、重启查看测试

注:并非由于@ApiModel这个注解让实体显示在这里了,而是只要出如今接口方法的返回值上的实体都会显示在这里,而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的。

@ApiModel为类添加注释

@ApiModelProperty为类属性添加注释

14.八、经常使用注解

Swagger的全部注解定义在io.swagger.annotations包下

下面列一些常常用到的,未列举出来的能够另行查阅说明:

Swagger注解 简单说明
@Api(tags = "xxx模块说明") 做用在模块类上
@ApiOperation("xxx接口说明") 做用在接口方法上
@ApiModel("xxxPOJO说明") 做用在模型类上:如VO、BO
@ApiModelProperty(value = "xxx属性说明",hidden = true) 做用在类方法和属性上,hidden设置为true能够隐藏该属性
@ApiParam("xxx参数说明") 做用在参数、方法和字段上,相似@ApiModelProperty

咱们也能够给请求的接口配置一些注释

@ApiOperation("狂神的接口")
@PostMapping("/kuang")
@ResponseBody
public String kuang(@ApiParam("这个名字会被返回")String username){
   return username;
}

这样的话,能够给一些比较难理解的属性或者接口,增长一些配置信息,让人更容易阅读!

相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操做,不须要额外说明文档(写得好自己就是文档)并且更不容易出错,只须要录入数据而后点击Execute,若是再配合自动化框架,能够说基本就不须要人为操做了。

Swagger是个优秀的工具,如今国内已经有不少的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合如今的快速迭代开发行情。固然了,提醒下你们在正式环境要记得关闭Swagger,一来出于安全考虑二来也能够节省运行时内存。

14.九、拓展:其余皮肤

咱们能够导入不一样的包实现不一样的皮肤定义:

一、默认的 访问 http://localhost:8080/swagger-ui.html

<dependency> 
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.9.2</version>
</dependency>

二、bootstrap-ui 访问 http://localhost:8080/doc.html

<!-- 引入swagger-bootstrap-ui包 /doc.html-->
<dependency>
   <groupId>com.github.xiaoymin</groupId>
   <artifactId>swagger-bootstrap-ui</artifactId>
   <version>1.9.1</version>
</dependency>

三、Layui-ui 访问 http://localhost:8080/docs.html

<!-- 引入swagger-ui-layer包 /docs.html-->
<dependency>
   <groupId>com.github.caspar-chen</groupId>
   <artifactId>swagger-ui-layer</artifactId>
   <version>1.1.3</version>
</dependency>

四、mg-ui 访问 http://localhost:8080/document.html

<!-- 引入swagger-ui-layer包 /document.html-->
<dependency>
   <groupId>com.zyplayer</groupId>
   <artifactId>swagger-mg-ui</artifactId>
   <version>1.0.6</version>
</dependency>

1五、SpringBoot:异步、定时、邮件任务

前言

在咱们的工做中,经常会用到异步处理任务,好比咱们在网站上发送邮件,后台会去发送邮件,此时前台会形成响应不动,直到邮件发送完毕,响应才会成功,因此咱们通常会采用多线程的方式去处理这些任务。还有一些定时任务,好比须要在天天凌晨的时候,分析一次前一天的日志信息。还有就是邮件的发送,微信的前身也是邮件服务呢?这些东西都是怎么实现的呢?其实SpringBoot都给咱们提供了对应的支持,咱们上手使用十分的简单,只须要开启一些注解支持,配置一些配置文件便可!那咱们来看看吧~

最后编辑于2020.3.26 做者:狂神说

15.一、异步任务

一、建立一个service包

二、建立一个类AsyncService

异步处理仍是很是经常使用的,好比咱们在网站上发送邮件,后台会去发送邮件,此时前台会形成响应不动,直到邮件发送完毕,响应才会成功,因此咱们通常会采用多线程的方式去处理这些任务。

编写方法,伪装正在处理数据,使用线程设置一些延时,模拟同步等待的状况;

@Service
public class AsyncService {

   public void hello(){
       try {
           Thread.sleep(3000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println("业务进行中....");
  }
}

三、编写controller包

四、编写AsyncController类

咱们去写一个Controller测试一下

@RestController
public class AsyncController {

   @Autowired
   AsyncService asyncService;

   @GetMapping("/hello")
   public String hello(){
       asyncService.hello();
       return "success";
  }

}

五、访问http://localhost:8080/hello进行测试,3秒后出现success,这是同步等待的状况。

问题:咱们若是想让用户直接获得消息,就在后台使用多线程的方式进行处理便可,可是每次都须要本身手动去编写多线程的实现的话,太麻烦了,咱们只须要用一个简单的办法,在咱们的方法上加一个简单的注解便可,以下:

六、给hello方法添加@Async注解;

//告诉Spring这是一个异步方法
@Async
public void hello(){
   try {
       Thread.sleep(3000);
  } catch (InterruptedException e) {
       e.printStackTrace();
  }
   System.out.println("业务进行中....");
}

SpringBoot就会本身开一个线程池,进行调用!可是要让这个注解生效,咱们还须要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;

@EnableAsync //开启异步注解功能
@SpringBootApplication
public class SpringbootTaskApplication {

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

}

七、重启测试,网页瞬间响应,后台代码依旧执行!

15.二、定时任务

项目开发中常常须要执行一些定时任务,好比须要在天天凌晨的时候,分析一次前一天的日志信息,Spring为咱们提供了异步执行任务调度的方式,提供了两个接口。

  • TaskExecutor接口
  • TaskScheduler接口

两个注解:

  • @EnableScheduling
  • @Scheduled

cron表达式:

测试步骤:

一、建立一个ScheduledService

咱们里面存在一个hello方法,他须要定时执行,怎么处理呢?

@Service
public class ScheduledService {
   
   //秒   分   时     日   月   周几
   //0 * * * * MON-FRI
   //注意cron表达式的用法;
   @Scheduled(cron = "0 * * * * 0-7")
   public void hello(){
       System.out.println("hello.....");
  }
}

二、这里写完定时任务以后,咱们须要在主程序上增长@EnableScheduling 开启定时任务功能

@EnableAsync //开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {

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

}

三、咱们来详细了解下cron表达式;

http://www.bejson.com/othertools/cron/

四、经常使用的表达式

(1)0/2 * * * * ?   表示每2秒 执行任务
(1)0 0/2 * * * ?   表示每2分钟 执行任务
(1)0 0 2 1 * ?   表示在每个月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI   表示周一到周五天天上午10:15执行做业
(3)0 15 10 ? 6L 2002-2006   表示2002-2006年的每月的最后一个星期五上午10:15执行做
(4)0 0 10,14,16 * * ?   天天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ?   朝九晚五工做时间内每半小时
(6)0 0 12 ? * WED   表示每一个星期三中午12点
(7)0 0 12 * * ?   天天中午12点触发
(8)0 15 10 ? * *   天天上午10:15触发
(9)0 15 10 * * ?     天天上午10:15触发
(10)0 15 10 * * ?   天天上午10:15触发
(11)0 15 10 * * ? 2005   2005年的天天上午10:15触发
(12)0 * 14 * * ?     在天天下午2点到下午2:59期间的每1分钟触发
(13)0 0/5 14 * * ?   在天天下午2点到下午2:55期间的每5分钟触发
(14)0 0/5 14,18 * * ?     在天天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(15)0 0-5 14 * * ?   在天天下午2点到下午2:05期间的每1分钟触发
(16)0 10,44 14 ? 3 WED   每一年三月的星期三的下午2:10和2:44触发
(17)0 15 10 ? * MON-FRI   周一至周五的上午10:15触发
(18)0 15 10 15 * ?   每个月15日上午10:15触发
(19)0 15 10 L * ?   每个月最后一日的上午10:15触发
(20)0 15 10 ? * 6L   每个月的最后一个星期五上午10:15触发
(21)0 15 10 ? * 6L 2002-2005   2002年至2005年的每个月的最后一个星期五上午10:15触发
(22)0 15 10 ? * 6#3   每个月的第三个星期五上午10:15触发

15.三、邮件任务

邮件发送,在咱们的平常开发中,也很是的多,Springboot也帮咱们作了支持

  • 邮件发送须要引入spring-boot-start-mail
  • SpringBoot 自动配置MailSenderAutoConfiguration
  • 定义MailProperties内容,配置在application.yml中
  • 自动装配JavaMailSender
  • 测试邮件发送

测试:

一、引入pom依赖

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

看它引入的依赖,能够看到 jakarta.mail

<dependency>
   <groupId>com.sun.mail</groupId>
   <artifactId>jakarta.mail</artifactId>
   <version>1.6.4</version>
   <scope>compile</scope>
</dependency>

二、查看自动配置类:MailSenderAutoConfiguration

这个类中存在bean,JavaMailSenderImpl

而后咱们去看下配置文件

@ConfigurationProperties(
   prefix = "spring.mail"
)
public class MailProperties {
   private static final Charset DEFAULT_CHARSET;
   private String host;
   private Integer port;
   private String username;
   private String password;
   private String protocol = "smtp";
   private Charset defaultEncoding;
   private Map<String, String> properties;
   private String jndiName;
}

三、配置文件:

spring.mail.username=24736743@qq.comspring.mail.password=你的qq受权码spring.mail.host=smtp.qq.com# qq须要配置sslspring.mail.properties.mail.smtp.ssl.enable=true

获取受权码:在QQ邮箱中的设置->帐户->开启pop3和smtp服务

四、Spring单元测试

@Autowired
JavaMailSenderImpl mailSender;

@Test
public void contextLoads() {
   //邮件设置1:一个简单的邮件
   SimpleMailMessage message = new SimpleMailMessage();
   message.setSubject("通知-明天来狂神这听课");
   message.setText("今晚7:30开会");

   message.setTo("24736743@qq.com");
   message.setFrom("24736743@qq.com");
   mailSender.send(message);
}

@Test
public void contextLoads2() throws MessagingException {
   //邮件设置2:一个复杂的邮件
   MimeMessage mimeMessage = mailSender.createMimeMessage();
   MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

   helper.setSubject("通知-明天来狂神这听课");
   helper.setText("<b style='color:red'>今天 7:30来开会</b>",true);

   //发送附件
   helper.addAttachment("1.jpg",new File(""));
   helper.addAttachment("2.jpg",new File(""));

   helper.setTo("24736743@qq.com");
   helper.setFrom("24736743@qq.com");

   mailSender.send(mimeMessage);
}

查看邮箱,邮件接收成功!

咱们只须要使用Thymeleaf进行先后端结合便可开发本身网站邮件收发功能了!

1六、SpringBoot:富文本编辑器

16.一、简介

思考:咱们平时在博客园,或者CSDN等平台进行写做的时候,有同窗思考过他们的编辑器是怎么实现的吗?

在博客园后台的选项设置中,能够看到一个文本编辑器的选项:

其实这个就是富文本编辑器,市面上有许多很是成熟的富文本编辑器,好比:

  • Editor.md——功能很是丰富的编辑器,左端编辑,右端预览,很是方便,彻底免费

  • wangEditor——基于javascript和css开发的 Web富文本编辑器, 轻量、简洁、界面美观、易用、开源免费。

  • TinyMCE——TinyMCE是一个轻量级的基于浏览器的所见即所得编辑器,由JavaScript写成。它对IE6+和Firefox1.5+都有着很是良好的支持。功能齐全,界面美观,就是文档是英文的,对开发人员英文水平有必定要求。

  • 百度ueditor——UEditor是由百度web前端研发部开发所见即所得富文本web编辑器,具备轻量,功能齐全,可定制,注重用户体验等特色,开源基于MIT协议,容许自由使用和修改代码,缺点是已经没有更新了

  • kindeditor——界面经典。

  • Textbox——Textbox是一款极简但功能强大的在线文本编辑器,支持桌面设备和移动设备。主要功能包含内置的图像处理和存储、文件拖放、拼写检查和自动更正。此外,该工具还实现了屏幕阅读器等辅助技术,并符合WAI-ARIA可访问性标准。

  • CKEditor——国外的,界面美观。

  • quill——功能强大,还能够编辑公式等

  • simditor——界面美观,功能较全。

  • summernote——UI好看,精美

  • jodit——功能齐全

  • froala Editor——界面很是好看,功能很是强大,很是好用(非免费)

总之,目前可用的富文本编辑器有不少......这只是其中的一部分

16.二、Editor.md

我这里使用的就是Editor.md,做为一个资深码农,Mardown必然是咱们程序猿最喜欢的格式,看下面,就爱上了!

咱们能够在官网下载它:https://pandao.github.io/editor.md/ , 获得它的压缩包!

解压之后,在examples目录下面,能够看到他的不少案例使用!学习,其实就是看人家怎么写的,而后进行模仿就行了!

咱们能够将整个解压的文件倒入咱们的项目,将一些无用的测试和案例删掉便可!

16.三、基础工程搭建

数据库设计

article:文章表

字段 备注
id int 文章的惟一ID
author varchar 做者
title varchar 标题
content longtext 文章的内容

建表SQL:

CREATE TABLE `article` (
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'int文章的惟一ID',
`author` varchar(50) NOT NULL COMMENT '做者',
`title` varchar(100) NOT NULL COMMENT '标题',
`content` longtext NOT NULL COMMENT '文章的内容',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

基础项目搭建

一、建一个SpringBoot项目配置

spring:
datasource:
  username: root
  password: 123456
  #?serverTimezone=UTC解决时区的报错
  url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
  driver-class-name: com.mysql.cj.jdbc.Driver
<resources>
   <resource>
       <directory>src/main/java</directory>
       <includes>
           <include>**/*.xml</include>
       </includes>
       <filtering>true</filtering>
   </resource>
</resources>

二、实体类:

//文章类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Article implements Serializable {

   private int id; //文章的惟一ID
   private String author; //做者名
   private String title; //标题
   private String content; //文章的内容

}

三、mapper接口:

@Mapper
@Repository
public interface ArticleMapper {
   //查询全部的文章
   List<Article> queryArticles();

   //新增一个文章
   int addArticle(Article article);

   //根据文章id查询文章
   Article getArticleById(int id);

   //根据文章id删除文章
   int deleteArticleById(int id);

}

既然已经提供了 myBatis 的映射配置文件,天然要告诉 spring boot 这些文件的位置

mybatis:
mapper-locations: classpath:com/kuang/mapper/*.xml
type-aliases-package: com.kuang.pojo

编写一个Controller测试下,是否ok;

16.四、文章编辑整合(重点)

一、导入 editor.md 资源 ,删除多余文件

二、编辑文章页面 editor.html、须要引入 jQuery;

<!DOCTYPE html>
<html class="x-admin-sm" lang="zh" xmlns:th="http://www.thymeleaf.org">

<head>
   <meta charset="UTF-8">
   <title>秦疆'Blog</title>
   <meta name="renderer" content="webkit">
   <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
   <meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" />
   <!--Editor.md-->
   <link rel="stylesheet" th:href="@{/editormd/css/editormd.css}"/>
   <link rel="shortcut icon" href="https://pandao.github.io/editor.md/favicon.ico" type="image/x-icon" />
</head>

<body>

<div class="layui-fluid">
   <div class="layui-row layui-col-space15">
       <div class="layui-col-md12">
           <!--博客表单-->
           <form name="mdEditorForm">
               <div>
                  标题:<input type="text" name="title">
               </div>
               <div>
                  做者:<input type="text" name="author">
               </div>
               <div id="article-content">
                   <textarea name="content" id="content" style="display:none;"> </textarea>
               </div>
           </form>

       </div>
   </div>
</div>
</body>

<!--editormd-->
<script th:src="@{/editormd/lib/jquery.min.js}"></script>
<script th:src="@{/editormd/editormd.js}"></script>

<script type="text/javascript">
   var testEditor;

   //window.onload = function(){ }
   $(function() {
       testEditor = editormd("article-content", {
           width : "95%",
           height : 400,
           syncScrolling : "single",
           path : "../editormd/lib/",
           saveHTMLToTextarea : true,    // 保存 HTML 到 Textarea
           emoji: true,
           theme: "dark",//工具栏主题
           previewTheme: "dark",//预览主题
           editorTheme: "pastel-on-dark",//编辑主题
           tex : true,                   // 开启科学公式TeX语言支持,默认关闭
           flowChart : true,             // 开启流程图支持,默认关闭
           sequenceDiagram : true,       // 开启时序/序列图支持,默认关闭,
           //图片上传
           imageUpload : true,
           imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
           imageUploadURL : "/article/file/upload",
           onload : function() {
               console.log('onload', this);
          },
           /*指定须要显示的功能按钮*/
           toolbarIcons : function() {
               return ["undo","redo","|",
                   "bold","del","italic","quote","ucwords","uppercase","lowercase","|",
                   "h1","h2","h3","h4","h5","h6","|",
                   "list-ul","list-ol","hr","|",
                   "link","reference-link","image","code","preformatted-text",
                   "code-block","table","datetime","emoji","html-entities","pagebreak","|",
                   "goto-line","watch","preview","fullscreen","clear","search","|",
                   "help","info","releaseIcon", "index"]
          },

           /*自定义功能按钮,下面我自定义了2个,一个是发布,一个是返回首页*/
           toolbarIconTexts : {
               releaseIcon : "<span bgcolor=\"gray\">发布</span>",
               index : "<span bgcolor=\"red\">返回首页</span>",
          },

           /*给自定义按钮指定回调函数*/
           toolbarHandlers:{
               releaseIcon : function(cm, icon, cursor, selection) {
                   //表单提交
                   mdEditorForm.method = "post";
                   mdEditorForm.action = "/article/addArticle";//提交至服务器的路径
                   mdEditorForm.submit();
              },
               index : function(){
                   window.location.href = '/';
              },
          }
      });
  });
</script>

</html>

三、编写Controller,进行跳转,以及保存文章

@Controller
@RequestMapping("/article")
public class ArticleController {

   @GetMapping("/toEditor")
   public String toEditor(){
       return "editor";
  }
   
   @PostMapping("/addArticle")
   public String addArticle(Article article){
       articleMapper.addArticle(article);
       return "editor";
  }
   
}

图片上传问题

一、前端js中添加配置

//图片上传
imageUpload : true,
imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
imageUploadURL : "/article/file/upload", // //这个是上传图片时的访问地址

二、后端请求,接收保存这个图片, 须要导入 FastJson 的依赖!

//博客图片上传问题
@RequestMapping("/file/upload")
@ResponseBody
public JSONObject fileUpload(@RequestParam(value = "editormd-image-file", required = true) MultipartFile file, HttpServletRequest request) throws IOException {
   //上传路径保存设置

   //得到SpringBoot当前项目的路径:System.getProperty("user.dir")
   String path = System.getProperty("user.dir")+"/upload/";

   //按照月份进行分类:
   Calendar instance = Calendar.getInstance();
   String month = (instance.get(Calendar.MONTH) + 1)+"月";
   path = path+month;

   File realPath = new File(path);
   if (!realPath.exists()){
       realPath.mkdir();
  }

   //上传文件地址
   System.out.println("上传文件保存地址:"+realPath);

   //解决文件名字问题:咱们使用uuid;
   String filename = "ks-"+UUID.randomUUID().toString().replaceAll("-", "");
   //经过CommonsMultipartFile的方法直接写文件(注意这个时候)
   file.transferTo(new File(realPath +"/"+ filename));

   //给editormd进行回调
   JSONObject res = new JSONObject();
   res.put("url","/upload/"+month+"/"+ filename);
   res.put("success", 1);
   res.put("message", "upload success!");

   return res;
}

三、解决文件回显显示的问题,设置虚拟目录映射!在咱们本身拓展的MvcConfig中进行配置便可!

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

   // 文件保存在真实目录/upload/下,
   // 访问的时候使用虚路径/upload,好比文件名为1.png,就直接/upload/1.png就ok了。
   @Override
   public void addResourceHandlers(ResourceHandlerRegistry registry) {
       registry.addResourceHandler("/upload/**")
          .addResourceLocations("file:"+System.getProperty("user.dir")+"/upload/");
  }

}

表情包问题

本身手动下载,emoji 表情包,放到图片路径下:

修改editormd.js文件

// Emoji graphics files url path
editormd.emoji     = {
   path : "../editormd/plugins/emoji-dialog/emoji/",
   ext   : ".png"
};

16.五、文章展现

一、Controller 中增长方法

@GetMapping("/{id}")
public String show(@PathVariable("id") int id,Model model){
   Article article = articleMapper.getArticleById(id);
   model.addAttribute("article",article);
   return "article";
}

二、编写页面 article.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
   <title th:text="${article.title}"></title>
</head>
<body>

<div>
   <!--文章头部信息:标题,做者,最后更新日期,导航-->
   <h2 style="margin: auto 0" th:text="${article.title}"></h2>
  做者:<span style="float: left" th:text="${article.author}"></span>
   <!--文章主体内容-->
   <div id="doc-content">
       <textarea style="display:none;" placeholder="markdown" th:text="${article.content}"></textarea>
   </div>

</div>

<link rel="stylesheet" th:href="@{/editormd/css/editormd.preview.css}" />
<script th:src="@{/editormd/lib/jquery.min.js}"></script>
<script th:src="@{/editormd/lib/marked.min.js}"></script>
<script th:src="@{/editormd/lib/prettify.min.js}"></script>
<script th:src="@{/editormd/lib/raphael.min.js}"></script>
<script th:src="@{/editormd/lib/underscore.min.js}"></script>
<script th:src="@{/editormd/lib/sequence-diagram.min.js}"></script>
<script th:src="@{/editormd/lib/flowchart.min.js}"></script>
<script th:src="@{/editormd/lib/jquery.flowchart.min.js}"></script>
<script th:src="@{/editormd/editormd.js}"></script>

<script type="text/javascript">
   var testEditor;
   $(function () {
       testEditor = editormd.markdownToHTML("doc-content", {//注意:这里是上面DIV的id
           htmlDecode: "style,script,iframe",
           emoji: true,
           taskList: true,
           tocm: true,
           tex: true, // 默认不解析
           flowChart: true, // 默认不解析
           sequenceDiagram: true, // 默认不解析
           codeFold: true
      });});
</script>
</body>
</html>

重启项目,访问进行测试!大功告成!

小结:

有了富文本编辑器,咱们网站的功能就会又多一项,你们到了这里彻底能够有时间写一个属于本身的博客网站了,根据所学的知识是彻底没有任何问题的!

1七、SpringBoot:Dubbo和Zookeeper集成

分布式理论

17.一、什么是分布式系统?

在《分布式系统原理与范型》一书中有以下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来讲就像单个相关系统”;

分布式系统是由一组经过网络进行通讯、为了完成共同的任务而协调工做的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机没法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据

分布式系统(distributed system)是创建在网络之上的软件系统。

首先须要明确的是,只有当单个节点的处理能力没法知足日益增加的计算、存储任务的时候,且硬件的提高(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,咱们才须要考虑分布式系统。由于,分布式系统要解决的问题自己就是和单机系统同样的,而因为分布式系统多节点、经过网络通讯的拓扑结构,会引入不少单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。

17.二、Dubbo文档

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已没法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进。

在Dubbo的官网文档有这样一张图

**单一应用架构****

当网站流量很小时,只需一个应用,将全部功能都部署在一块儿,以减小部署节点和成本。此时,用于简化增删改查工做量的数据访问框架(ORM)是关键。

适用于小型网站,小型管理系统,将全部功能都部署到一个功能里,简单易用。

缺点:

一、性能扩展比较难

二、协同开发问题

三、不利于升级维护

****垂直应用架构******

当访问量逐渐增大,单一应用增长机器带来的加速度愈来愈小,将应用拆成互不相干的几个应用,以提高效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

经过切分业务来实现各个模块独立部署,下降了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。

缺点:公用模块没法重复利用,开发性的浪费

******分布式服务架构********

当垂直应用愈来愈多,应用之间交互不可避免,将核心业务抽取出来,做为独立的服务,逐渐造成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提升业务复用及整合的分布式服务框架(RPC)是关键。

********流动计算架构**********

当服务愈来愈多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增长一个调度中心基于访问压力实时管理集群容量,提升集群利用率。此时,用于提升机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。

什么是RPC

RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通讯方式,他是一种技术的思想,而不是规范。它容许程序调用另外一个地址空间(一般是共享网络的另外一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员不管是调用本地的仍是远程的函数,本质上编写的调用代码基本相同。

也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,因为不在一个内存空间,不能直接调用,须要经过网络来表达调用的语义和传达调用的数据。为何要用RPC呢?就是没法在一个进程内,甚至一个计算机内经过本地调用的方式完成的需求,好比不一样的系统间的通信,甚至不一样的组织间的通信,因为计算能力须要横向扩展,须要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数同样去调远程函数;

推荐阅读文章:https://www.jianshu.com/p/2accc2840a1b

RPC基本原理

步骤解析:

RPC两个核心模块:通信,序列化。

测试环境搭建

********Dubbo**********

Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

dubbo官网 http://dubbo.apache.org/zh-cn/index.html

1.了解Dubbo的特性

2.查看官方文档

dubbo基本概念

服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册本身提供的服务。

服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅本身所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,若是调用失败,再选另外一台调用。

注册中心(Registry):注册中心返回服务提供者地址列表给消费者,若是有变动,注册中心将基于长链接推送变动数据给消费者

监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

调用关系说明

l 服务容器负责启动,加载,运行服务提供者。

l 服务提供者在启动时,向注册中心注册本身提供的服务。

l 服务消费者在启动时,向注册中心订阅本身所需的服务。

l 注册中心返回服务提供者地址列表给消费者,若是有变动,注册中心将基于长链接推送变动数据给消费者。

l 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,若是调用失败,再选另外一台调用。

l 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

********Dubbo环境搭建**********

点进dubbo官方文档,推荐咱们使用Zookeeper 注册中心

什么是zookeeper呢?能够查看官方文档

********Window下安装zookeeper**********

一、下载zookeeper :地址, 咱们下载3.4.14 , 最新版!解压zookeeper

二、运行/bin/zkServer.cmd ,初次运行会报错,没有zoo.cfg配置文件;

可能遇到问题:闪退 !

解决方案:编辑zkServer.cmd文件末尾添加pause 。这样运行出错就不会退出,会提示错误信息,方便找到缘由。

三、修改zoo.cfg配置文件

将conf文件夹下面的zoo_sample.cfg复制一份更名为zoo.cfg便可。

注意几个重要位置:

dataDir=./ 临时数据存储的目录(可写相对路径)

clientPort=2181 zookeeper的端口号

修改完成后再次启动zookeeper

四、使用zkCli.cmd测试

ls /:列出zookeeper根下保存的全部节点

[zk: 127.0.0.1:2181(CONNECTED) 4] ls /[zookeeper]

create –e /kuangshen 123:建立一个kuangshen节点,值为123

get /kuangshen:获取/kuangshen节点的值

咱们再来查看一下节点

********window下安装dubbo-admin**********

dubbo自己并非一个服务软件。它其实就是一个jar包,可以帮你的java程序链接到zookeeper,并利用zookeeper消费、提供服务。

可是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即便不装也不影响使用。

咱们这里来安装一下:

一、下载dubbo-admin

地址 :https://github.com/apache/dubbo-admin/tree/master

二、解压进入目录

修改 dubbo-admin\src\main\resources \application.properties 指定zookeeper地址

server.port=7001spring.velocity.cache=falsespring.velocity.charset=UTF-8spring.velocity.layout-url=/templates/default.vmspring.messages.fallback-to-system-locale=falsespring.messages.basename=i18n/messagespring.root.password=rootspring.guest.password=guestdubbo.registry.address=zookeeper://127.0.0.1:2181

三、在项目目录下打包dubbo-admin

mvn clean package -Dmaven.test.skip=true

第一次打包的过程有点慢,须要耐心等待!直到成功!

四、执行 dubbo-admin\target 下的dubbo-admin-0.0.1-SNAPSHOT.jar

java -jar dubbo-admin-0.0.1-SNAPSHOT.jar

【注意:zookeeper的服务必定要打开!】

执行完毕,咱们去访问一下 http://localhost:7001/ , 这时候咱们须要输入登陆帐户和密码,咱们都是默认的root-root;

登陆成功后,查看界面

安装完成!

17.三、SpringBoot + Dubbo + zookeeper

********框架搭建**********

1. 启动zookeeper !

2. IDEA建立一个空项目;

3.建立一个模块,实现服务提供者:provider-server , 选择web依赖便可

4.项目建立完毕,咱们写一个服务,好比卖票的服务;

编写接口

package com.kuang.provider.service;

public interface TicketService {
   public String getTicket();
}

编写实现类

package com.kuang.provider.service;

public class TicketServiceImpl implements TicketService {
   @Override
   public String getTicket() {
       return "《狂神说Java》";
  }
}

5.建立一个模块,实现服务消费者:consumer-server , 选择web依赖便可

6.项目建立完毕,咱们写一个服务,好比用户的服务;

编写service

package com.kuang.consumer.service;

public class UserService {
   //咱们须要去拿去注册中心的服务
}

需求:如今咱们的用户想使用买票的服务,这要怎么弄呢 ?

********服务提供者**********

一、将服务提供者注册到注册中心,咱们须要整合Dubbo和zookeeper,因此须要导包

咱们从dubbo官网进入github,看下方的帮助文档,找到dubbo-springboot,找到依赖包

<!-- Dubbo Spring Boot Starter -->
<dependency>
   <groupId>org.apache.dubbo</groupId>
   <artifactId>dubbo-spring-boot-starter</artifactId>
   <version>2.7.3</version>
</dependency>

zookeeper的包咱们去maven仓库下载,zkclient;

<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
   <groupId>com.github.sgroschupf</groupId>
   <artifactId>zkclient</artifactId>
   <version>0.1</version>
</dependency>

【新版的坑】zookeeper及其依赖包,解决日志冲突,还须要剔除日志依赖;

<!-- 引入zookeeper -->
<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-framework</artifactId>
   <version>2.12.0</version>
</dependency>
<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-recipes</artifactId>
   <version>2.12.0</version>
</dependency>
<dependency>
   <groupId>org.apache.zookeeper</groupId>
   <artifactId>zookeeper</artifactId>
   <version>3.4.14</version>
   <!--排除这个slf4j-log4j12-->
   <exclusions>
       <exclusion>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-log4j12</artifactId>
       </exclusion>
   </exclusions>
</dependency>

二、在springboot配置文件中配置dubbo相关属性!

#当前应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#扫描指定包下服务
dubbo.scan.base-packages=com.kuang.provider.service

三、在service的实现类中配置服务注解,发布服务!注意导包问题

import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;

@Service //将服务发布出去
@Component //放在容器中
public class TicketServiceImpl implements TicketService {
   @Override
   public String getTicket() {
       return "《狂神说Java》";
  }
}

逻辑理解 :应用启动起来,dubbo就会扫描指定的包下带有@component注解的服务,将它发布在指定的注册中心中!

********服务消费者**********

一、导入依赖,和以前的依赖同样;

<!--dubbo-->
<!-- Dubbo Spring Boot Starter -->
<dependency>
   <groupId>org.apache.dubbo</groupId>
   <artifactId>dubbo-spring-boot-starter</artifactId>
   <version>2.7.3</version>
</dependency>
<!--zookeeper-->
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
   <groupId>com.github.sgroschupf</groupId>
   <artifactId>zkclient</artifactId>
   <version>0.1</version>
</dependency>
<!-- 引入zookeeper -->
<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-framework</artifactId>
   <version>2.12.0</version>
</dependency>
<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-recipes</artifactId>
   <version>2.12.0</version>
</dependency>
<dependency>
   <groupId>org.apache.zookeeper</groupId>
   <artifactId>zookeeper</artifactId>
   <version>3.4.14</version>
   <!--排除这个slf4j-log4j12-->
   <exclusions>
       <exclusion>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-log4j12</artifactId>
       </exclusion>
   </exclusions>
</dependency>

二、配置参数

#当前应用名字
dubbo.application.name=consumer-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

3. 原本正常步骤是须要将服务提供者的接口打包,而后用pom文件导入,咱们这里使用简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同;

4. 完善消费者的服务类

package com.kuang.consumer.service;

import com.kuang.provider.service.TicketService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;

@Service //注入到容器中
public class UserService {

   @Reference //远程引用指定的服务,他会按照全类名进行匹配,看谁给注册中心注册了这个全类名
   TicketService ticketService;

   public void bugTicket(){
       String ticket = ticketService.getTicket();
       System.out.println("在注册中心买到"+ticket);
  }

}

5. 测试类编写;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ConsumerServerApplicationTests {

   @Autowired
   UserService userService;

   @Test
   public void contextLoads() {

       userService.bugTicket();

  }

}

********启动测试**********

1. 开启zookeeper

2. 打开dubbo-admin实现监控【能够不用作】

3. 开启服务者

4. 消费者消费测试,结果:

监控中心 :

ok , 这就是SpingBoot + dubbo + zookeeper实现分布式开发的应用,其实就是一个服务拆分的思想;

1八、SpringBoot:集成SpringSecurity

18.一、安全简介

在 Web 开发中,安全一直是很是重要的一个方面。安全虽然属于应用的非功能性需求,可是应该在应用开发的初期就考虑进来。若是在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,没法知足用户的要求,并可能形成用户的隐私数据被攻击者窃取;另外一方面,应用的基本架构已经肯定,要修复安全漏洞,可能须要对系统的架构作出比较重大的调整,于是须要更多的开发时间,影响应用的发布进程。所以,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程当中。

市面上存在比较有名的:Shiro,Spring Security !

这里须要阐述一下的是,每个框架的出现都是为了解决某一问题而产生了,那么Spring Security框架的出现是为了解决什么问题呢?

首先咱们看下它的官网介绍:Spring Security官网地址

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它其实是保护基于spring的应用程序的标准。

Spring Security是一个框架,侧重于为Java应用程序提供身份验证和受权。与全部Spring项目同样,Spring安全性的真正强大之处在于它能够轻松地扩展以知足定制需求

从官网的介绍中能够知道这是一个权限框架。想咱们以前作项目是没有使用框架是怎么控制权限的?对于权限 通常会细分为功能权限,访问权限,和菜单权限。代码会写的很是的繁琐,冗余。

怎么解决以前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而Spring Scecurity就是其中的一种。

Spring 是一个很是流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。通常来讲,Web 应用的安全性包括用户认证(Authentication)和用户受权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户可否访问该系统。用户认证通常要求用户提供用户名和密码。系统经过校验用户名和密码来完成认证过程。用户受权指的是验证某个用户是否有权限执行某个操做。在一个系统中,不一样用户所具备的权限是不一样的。好比对一个文件来讲,有的用户只能进行读取,而有的用户能够进行修改。通常来讲,系统会为不一样的用户分配不一样的角色,而每一个角色则对应一系列的权限。

对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户受权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),能够对应用中的领域对象进行细粒度的控制。

18.二、实战测试

18.2.一、实验环境搭建

一、新建一个初始的springboot项目web模块,thymeleaf模块

二、导入静态资源

welcome.html
|views
|level1
1.html
2.html
3.html
|level2
1.html
2.html
3.html
|level3
1.html
2.html
3.html
Login.html

三、controller跳转!

package com.kuang.controller;

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

@Controller
public class RouterController {

   @RequestMapping({"/","/index"})
   public String index(){
       return "index";
  }

   @RequestMapping("/toLogin")
   public String toLogin(){
       return "views/login";
  }

   @RequestMapping("/level1/{id}")
   public String level1(@PathVariable("id") int id){
       return "views/level1/"+id;
  }

   @RequestMapping("/level2/{id}")
   public String level2(@PathVariable("id") int id){
       return "views/level2/"+id;
  }

   @RequestMapping("/level3/{id}")
   public String level3(@PathVariable("id") int id){
       return "views/level3/"+id;
  }

}

四、测试实验环境是否OK!

18.2.二、认识SpringSecurity

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他能够实现强大的Web安全控制,对于安全控制,咱们仅须要引入spring-boot-starter-security 模块,进行少许的配置,便可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式

Spring Security的两个主要目标是 “认证” 和 “受权”(访问控制)。

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证一般经过用户名和密码完成,有时与身份验证因素结合使用。

“受权” (Authorization)

受权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的彻底权限。

这个概念是通用的,而不是只在Spring Security 中存在。

18.2.三、认证和受权

目前,咱们的测试环境,是谁均可以访问的,咱们使用 Spring Security 增长上认证和受权的功能

一、引入 Spring Security 模块

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

二、编写 Spring Security 配置类

参考官网:https://spring.io/projects/spring-security

查看咱们本身项目中的版本,找到对应的帮助文档:

https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5 #servlet-applications 8.16.4

三、编写基础配置类

package com.kuang.config;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity // 开启WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       
  }
}

四、定制请求的受权规则

@Override
protected void configure(HttpSecurity http) throws Exception {
   // 定制请求的受权规则
   // 首页全部人能够访问
   http.authorizeRequests().antMatchers("/").permitAll()
  .antMatchers("/level1/**").hasRole("vip1")
  .antMatchers("/level2/**").hasRole("vip2")
  .antMatchers("/level3/**").hasRole("vip3");
}

五、测试一下:发现除了首页都进不去了!由于咱们目前没有登陆的角色,由于请求须要登陆的角色拥有对应的权限才能够!

六、在configure()方法中加入如下配置,开启自动配置的登陆功能!

// 开启自动配置的登陆功能
// /login 请求来到登陆页
// /login?error 重定向到这里表示登陆失败
http.formLogin();

七、测试一下:发现,没有权限的时候,会跳转到登陆的页面!

八、查看刚才登陆页的注释信息;

咱们能够定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法

//定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   
   //在内存中定义,也能够在jdbc中去拿....
   auth.inMemoryAuthentication()
          .withUser("kuangshen").password("123456").roles("vip2","vip3")
          .and()
          .withUser("root").password("123456").roles("vip1","vip2","vip3")
          .and()
          .withUser("guest").password("123456").roles("vip1","vip2");
}

九、测试,咱们能够使用这些帐号登陆进行测试!发现会报错!

There is no PasswordEncoder mapped for the id “null”

十、缘由,咱们要将前端传过来的密码进行某种方式加密,不然就没法登陆,修改代码

//定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   //在内存中定义,也能够在jdbc中去拿....
   //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
   //要想咱们的项目还可以正常登录,须要修改一下configure中的代码。咱们要将前端传过来的密码进行某种方式加密
   //spring security 官方推荐的是使用bcrypt加密方式。
   
   auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
          .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
          .and()
          .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
          .and()
          .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}

十一、测试,发现,登陆成功,而且每一个角色只能访问本身认证下的规则!搞定

18.2.四、权限控制和注销

一、开启自动配置的注销的功能

//定制请求的受权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
   //....
   //开启自动配置的注销的功能
      // /logout 注销请求
   http.logout();
}

二、咱们在前端,增长一个注销的按钮,index.html 导航栏中

<a class="item" th:href="@{/logout}">
   <i class="address card icon"></i> 注销
</a>

三、咱们能够去测试一下,登陆成功后点击注销,发现注销完毕会跳转到登陆页面!

四、可是,咱们想让他注销成功后,依旧能够跳转到首页,该怎么处理呢?

// .logoutSuccessUrl("/"); 注销成功来到首页
http.logout().logoutSuccessUrl("/");

五、测试,注销完毕后,发现跳转到首页OK

六、咱们如今又来一个需求:用户没有登陆的时候,导航栏上只显示登陆按钮,用户登陆以后,导航栏能够显示登陆的用户信息及注销按钮!还有就是,好比kuangshen这个用户,它只有 vip2,vip3功能,那么登陆则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站状况了!该如何作呢?

咱们须要结合thymeleaf中的一些功能

sec:authorize="isAuthenticated()":是否定证登陆!来显示不一样的页面

Maven依赖:

<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
   <version>3.0.4.RELEASE</version>
</dependency>

七、修改咱们的 前端页面

  1. 导入命名空间

  2. xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
  3. 修改导航栏,增长认证判断

  4. <!--登陆注销-->
    <div class="right menu">
    
       <!--若是未登陆-->
       <div sec:authorize="!isAuthenticated()">
           <a class="item" th:href="@{/login}">
               <i class="address card icon"></i> 登陆
           </a>
       </div>
    
       <!--若是已登陆-->
       <div sec:authorize="isAuthenticated()">
           <a class="item">
               <i class="address card icon"></i>
              用户名:<span sec:authentication="principal.username"></span>
              角色:<span sec:authentication="principal.authorities"></span>
           </a>
       </div>
    
       <div sec:authorize="isAuthenticated()">
           <a class="item" th:href="@{/logout}">
               <i class="address card icon"></i> 注销
           </a>
       </div>
    </div>

八、重启测试,咱们能够登陆试试看,登陆成功后确实,显示了咱们想要的页面;

九、若是注销404了,就是由于它默认防止csrf跨站请求伪造,由于会产生安全问题,咱们能够将请求改成post表单提交,或者在spring security中关闭csrf功能;咱们试试:在 配置中增长 http.csrf().disable();

http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能经过post方式提交logout请求
http.logout().logoutSuccessUrl("/");

十、咱们继续将下面的角色功能块认证完成!

<!-- sec:authorize="hasRole('vip1')" -->
<div class="column" sec:authorize="hasRole('vip1')">
   <div class="ui raised segment">
       <div class="ui">
           <div class="content">
               <h5 class="content">Level 1</h5>
               <hr>
               <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
               <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
               <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
           </div>
       </div>
   </div>
</div>

<div class="column" sec:authorize="hasRole('vip2')">
   <div class="ui raised segment">
       <div class="ui">
           <div class="content">
               <h5 class="content">Level 2</h5>
               <hr>
               <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
               <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
               <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
           </div>
       </div>
   </div>
</div>

<div class="column" sec:authorize="hasRole('vip3')">
   <div class="ui raised segment">
       <div class="ui">
           <div class="content">
               <h5 class="content">Level 3</h5>
               <hr>
               <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
               <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
               <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
           </div>
       </div>
   </div>
</div>

十一、测试一下!

十二、权限控制和注销搞定!

18.2.五、记住我

如今的状况,咱们只要登陆以后,关闭浏览器,再登陆,就会让咱们从新登陆,可是不少网站的状况,就是有一个记住密码的功能,这个该如何实现呢?很简单

一、开启记住我功能

//定制请求的受权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
//。。。。。。。。。。。
   //记住我
   http.rememberMe();
}

二、咱们再次启动项目测试一下,发现登陆页多了一个记住我功能,咱们登陆以后关闭 浏览器,而后从新打开浏览器访问,发现用户依旧存在!

思考:如何实现的呢?其实很是简单

咱们能够查看浏览器的cookie

三、咱们点击注销的时候,能够发现,spring security 帮咱们自动删除了这个 cookie


四、结论:登陆成功后,将cookie发送给浏览器保存,之后登陆带上这个cookie,只要经过检查就能够免登陆了。若是点击注销,则会删除这个cookie,具体的原理咱们在JavaWeb阶段都讲过了,这里就不在多说了!

18.2.六、定制登陆页

如今这个登陆页面都是spring security 默认的,怎么样能够使用咱们本身写的Login界面呢?

一、在刚才的登陆页配置后面指定 loginpage

http.formLogin().loginPage("/toLogin");

二、而后前端也须要指向咱们本身定义的 login请求

<a class="item" th:href="@{/toLogin}">
   <i class="address card icon"></i> 登陆
</a>

三、咱们登陆,须要将这些信息发送到哪里,咱们也须要配置,login.html 配置提交请求及方式,方式必须为post:

在 loginPage()源码中的注释上有写明:

<form th:action="@{/login}" method="post">
   <div class="field">
       <label>Username</label>
       <div class="ui left icon input">
           <input type="text" placeholder="Username" name="username">
           <i class="user icon"></i>
       </div>
   </div>
   <div class="field">
       <label>Password</label>
       <div class="ui left icon input">
           <input type="password" name="password">
           <i class="lock icon"></i>
       </div>
   </div>
   <input type="submit" class="ui blue submit button"/>
</form>

四、这个请求提交上来,咱们还须要验证处理,怎么作呢?咱们能够查看formLogin()方法的源码!咱们配置接收登陆的用户名和密码的参数!

http.formLogin()
  .usernameParameter("username")
  .passwordParameter("password")
  .loginPage("/toLogin")
  .loginProcessingUrl("/login"); // 登录表单提交请求

五、在登陆页增长记住个人多选框

<input type="checkbox" name="remember"> 记住我

六、后端验证处理!

//定制记住个人参数!
http.rememberMe().rememberMeParameter("remember");

七、测试,OK

18.三、完整配置代码

package com.kuang.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   //定制请求的受权规则
   @Override
   protected void configure(HttpSecurity http) throws Exception {

       http.authorizeRequests().antMatchers("/").permitAll()
      .antMatchers("/level1/**").hasRole("vip1")
      .antMatchers("/level2/**").hasRole("vip2")
      .antMatchers("/level3/**").hasRole("vip3");


       //开启自动配置的登陆功能:若是没有权限,就会跳转到登陆页面!
           // /login 请求来到登陆页
           // /login?error 重定向到这里表示登陆失败
       http.formLogin()
          .usernameParameter("username")
          .passwordParameter("password")
          .loginPage("/toLogin")
          .loginProcessingUrl("/login"); // 登录表单提交请求

       //开启自动配置的注销的功能
           // /logout 注销请求
           // .logoutSuccessUrl("/"); 注销成功来到首页

       http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能经过post方式提交logout请求
       http.logout().logoutSuccessUrl("/");

       //记住我
       http.rememberMe().rememberMeParameter("remember");
  }

   //定义认证规则
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       //在内存中定义,也能够在jdbc中去拿....
       //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
       //要想咱们的项目还可以正常登录,须要修改一下configure中的代码。咱们要将前端传过来的密码进行某种方式加密
       //spring security 官方推荐的是使用bcrypt加密方式。

       auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
              .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
              .and()
              .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
              .and()
              .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
  }
}

去进行一波源码分析!**

相关文章
相关标签/搜索